From 29a89849ff2d476b3320c56dc9a70a22747996ad Mon Sep 17 00:00:00 2001 From: Derek Dai Date: Wed, 22 Mar 2017 11:12:43 +0800 Subject: [PATCH] add a demostration for wifid + dispd --- CMakeLists.txt | 7 +- demo/CMakeLists.txt | 70 +++ ...org.freedesktop.NetworkManager.Devices.xml | 50 ++ demo/dbus/org.freedesktop.miracle.wfd.xml | 36 ++ demo/dbus/org.freedesktop.miracle.wifi.xml | 60 +++ demo/res/wfdctl-res.xml | 6 + demo/res/wfdctl.ui | 11 + demo/wfdctl.vala | 431 ++++++++++++++++++ 8 files changed, 670 insertions(+), 1 deletion(-) create mode 100644 demo/CMakeLists.txt create mode 100644 demo/dbus/org.freedesktop.NetworkManager.Devices.xml create mode 100644 demo/dbus/org.freedesktop.miracle.wfd.xml create mode 100644 demo/dbus/org.freedesktop.miracle.wifi.xml create mode 100644 demo/res/wfdctl-res.xml create mode 100644 demo/res/wfdctl.ui create mode 100644 demo/wfdctl.vala diff --git a/CMakeLists.txt b/CMakeLists.txt index 10bc586..beff6c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ SET(BUILD_BINDIR "${CMAKE_INSTALL_PREFIX}/bin") OPTION(BUILD_ENABLE_DEBUG "Enable Debug" ON ) OPTION(RELY_UDEV "Rely in udev tag to select device" OFF ) OPTION(BUILD_TESTS "Enable TEST" ON ) +OPTION(BUILD_DEMO "Enable DEMO" OFF ) if(BUILD_ENABLE_DEBUG) add_definitions(-DBUILD_ENABLE_DEBUG) @@ -26,10 +27,10 @@ pkg_check_modules (GSTREAMER_BASE REQUIRED gstreamer-base-1.0) include(CheckCCompilerFlag) check_c_compiler_flag(-fstack-protector-strong HAS_STACK_PROTCTOR_STRONG) +check_c_compiler_flag(-fsanitize=undefined HAS_SANITIZE_UNDEFINED) if(HAS_STACK_PROTCTOR_STRONG) set(CMAKE_C_FLAGS "-fstack-protector-strong ${CMAKE_C_FLAGS}") endif() -check_c_compiler_flag(-fsanitize=undefined HAS_SANITIZE_UNDEFINED) if(HAS_SANITIZE_UNDEFINED) set(CMAKE_C_FLAGS "-fsanitize=undefined ${CMAKE_C_FLAGS}") endif() @@ -50,3 +51,7 @@ add_subdirectory(src) add_subdirectory(res) add_subdirectory(test) +if(BUILD_DEMO) + add_subdirectory(demo) +endif() + diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt new file mode 100644 index 0000000..4bc258f --- /dev/null +++ b/demo/CMakeLists.txt @@ -0,0 +1,70 @@ +find_program(VALAC valac) +if(NOT VALAC) + message(FATAL_ERROR "valac not found") +endif() +find_program(VALA_DBUS_BINDING_TOOL vala-dbus-binding-tool) +if(NOT VALA_DBUS_BINDING_TOOL) + message(FATAL_ERROR "vala-dbus-binding-tool not found") +endif() +find_library(READLINE REQUIRED) + +pkg_check_modules(GIO2 REQUIRED gio-2.0) + +set(RES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/res) +set(DBUS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/dbus) + +add_custom_command(OUTPUT org-freedesktop-networkmanager.vala + org-freedesktop-miracle-wifi.vala + org-freedesktop-miracle-wfd.vala + COMMAND ${VALA_DBUS_BINDING_TOOL} + --gdbus + --no-synced + --rename-namespace=org:Org + --rename-namespace=freedesktop:Freedesktop + --rename-namespace=miracle:Miracle + --rename-namespace=wifi:Wifi + --rename-namespace=wfd:Wfd + --api-path=${DBUS_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +add_custom_command(OUTPUT wfdctl.c + wfdctl.h + org-freedesktop-networkmanager.c + org-freedesktop-miracle-wifi.c + org-freedesktop-miracle-wfd.c + COMMAND ${VALAC} --target-glib=2.50 -H wfdctl.h --use-header -C + --pkg=gio-2.0 + ${CMAKE_CURRENT_SOURCE_DIR}/wfdctl.vala + org-freedesktop-networkmanager.vala + org-freedesktop-miracle-wifi.vala + org-freedesktop-miracle-wfd.vala + DEPENDS wfdctl.vala + ${CMAKE_CURRENT_BINARY_DIR}/org-freedesktop-networkmanager.vala + ${CMAKE_CURRENT_BINARY_DIR}/org-freedesktop-miracle-wifi.vala + ${CMAKE_CURRENT_BINARY_DIR}/org-freedesktop-miracle-wfd.vala + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +add_custom_command(OUTPUT wfdctl-res.c + COMMAND ${GLIB_COMPILE_RESOURCES} + --target=${CMAKE_CURRENT_BINARY_DIR}/wfdctl-res.c + --generate-source ${RES_DIR}/wfdctl-res.xml + DEPENDS ${RES_DIR}/wfdctl.ui + ${RES_DIR}/wfdctl-res.xml + WORKING_DIRECTORY ${RES_DIR}) + +include_directories(${GIO2_INCLUDE_DIRS}) + +# silent C compiler warning about valac generated code, bad practice +set(CMAKE_C_FLAGS "-Wno-unused-label ${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS "-Wno-incompatible-pointer-types ${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS "-Wno-deprecated-declarations ${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS "-Wno-unused-but-set-variable ${CMAKE_C_FLAGS}") + +add_executable(miracle-wfdctl wfdctl + wfdctl-res.c + org-freedesktop-networkmanager.c + org-freedesktop-miracle-wifi.c + org-freedesktop-miracle-wfd.c) + +target_link_libraries(miracle-wfdctl ${GIO2_LIBRARIES}) + diff --git a/demo/dbus/org.freedesktop.NetworkManager.Devices.xml b/demo/dbus/org.freedesktop.NetworkManager.Devices.xml new file mode 100644 index 0000000..db46a1e --- /dev/null +++ b/demo/dbus/org.freedesktop.NetworkManager.Devices.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/dbus/org.freedesktop.miracle.wfd.xml b/demo/dbus/org.freedesktop.miracle.wfd.xml new file mode 100644 index 0000000..2a1004c --- /dev/null +++ b/demo/dbus/org.freedesktop.miracle.wfd.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/dbus/org.freedesktop.miracle.wifi.xml b/demo/dbus/org.freedesktop.miracle.wifi.xml new file mode 100644 index 0000000..03cff73 --- /dev/null +++ b/demo/dbus/org.freedesktop.miracle.wifi.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/res/wfdctl-res.xml b/demo/res/wfdctl-res.xml new file mode 100644 index 0000000..a49a89e --- /dev/null +++ b/demo/res/wfdctl-res.xml @@ -0,0 +1,6 @@ + + + + wfdctl.ui + + diff --git a/demo/res/wfdctl.ui b/demo/res/wfdctl.ui new file mode 100644 index 0000000..cd0633d --- /dev/null +++ b/demo/res/wfdctl.ui @@ -0,0 +1,11 @@ + + + + + + False + + + + + diff --git a/demo/wfdctl.vala b/demo/wfdctl.vala new file mode 100644 index 0000000..97d8da7 --- /dev/null +++ b/demo/wfdctl.vala @@ -0,0 +1,431 @@ +using Org.Freedesktop.NetworkManager; +using Org.Freedesktop.Miracle.Wifi; +using Org.Freedesktop.Miracle.Wfd; + +const string BUS_NAME_NETWORK_MANAGER = "org.freedesktop.NetworkManager"; +const string BUS_NAME_WIFID = "org.freedesktop.miracle.wifi"; +const string BUS_NAME_DISPD = "org.freedesktop.miracle.wfd"; +const string OBJ_PATH_DEVICE = "/org/freedesktop/NetworkManager/Devices"; +const string OBJ_PATH_LINK = "/org/freedesktop/miracle/wifi/link"; +const string OBJ_PATH_PEER = "/org/freedesktop/miracle/wifi/peer"; +const string OBJ_PATH_SINK = "/org/freedesktop/miracle/wfd/sink"; +const string OBJ_PATH_SESSION = "/org/freedesktop/miracle/wfd/session"; + +errordomain WfdCtlError +{ + NO_SUCH_NIC, +} + +DBusObjectManagerClient nm; +DBusObjectManagerClient wifi; +DBusObjectManagerClient wfd; + +HashTable devices; +HashTable links; +HashTable peers; +HashTable sinks; +HashTable sessions; + +string curr_sink_id; +int retry_count = 0; + +string opt_iface; +string opt_wfd_subelems; +string opt_peer_mac; + +const GLib.OptionEntry[] option_entries = { + { "interface", 'i', 0, OptionArg.STRING, ref opt_iface, "name of wireless network interface", "WNIC name" }, + { "wfd-subelems", 'w', 0, OptionArg.STRING, ref opt_wfd_subelems, "device infomation. default: 000600111c4400c8", "device info subelems" }, + { "peer-mac", 'p', 0, OptionArg.STRING, ref opt_peer_mac, "MAC address of target peer", "peer MAC" }, + { null }, +}; + +void print(string format, ...) +{ + var argv = va_list(); + stderr.printf("%s: ", Environment.get_prgname()); + stderr.vprintf(format, argv); + stderr.printf("\n"); +} + +unowned Device? find_device_by_name(string nic_name) +{ + foreach(var d in devices.get_values()) { + if(nic_name == d.interface) { + return d; + } + } + + return null; +} + +unowned Link? find_link_by_name(string nic_name) +{ + foreach(var l in links.get_values()) { + if(nic_name == l.interface_name) { + return l; + } + } + + return null; +} + +unowned Sink? find_sink_by_label(string id) +{ + return sinks.lookup(id); +} + +bool is_wnic(string nic_name) +{ + return find_link_by_name(nic_name) != null; +} + +// to deal with sd_bus_path_encode/decode()ed path +string decode_path(string s) +{ + char c; + StringBuilder d = new StringBuilder(); + for(var i = 0; i < s.length; i ++) { + if(s.data[i] == (uint8) '_') { + c = (char) s.substring(i + 1, 2).to_long(null, 16); + i += 2; + } + else { + c = (char) s.data[i]; + } + d.append_c(c); + } + + return d.str; +} + +private async void wait_prop_changed(DBusProxy o, string name) +{ + ulong id = o.g_properties_changed.connect((props) => { + string k; + Variant v; + foreach(var prop in props) { + prop.get("{sv}", out k, out v); + if(k == name) { + wait_prop_changed.callback(); + break; + } + } + }); + + yield; + + o.disconnect(id); +} + +async void add_object(DBusObject o) throws Error +{ + unowned string path = o.get_object_path(); + int sep = path.last_index_of_char('/'); + string prefix = path.substring(0, sep); + string key = path.substring(sep + 1); + switch(prefix) { + case OBJ_PATH_DEVICE: + Device dev = yield Bus.get_proxy(BusType.SYSTEM, + BUS_NAME_NETWORK_MANAGER, + path); + if(!is_wnic(dev.interface)) { + break; + } + devices.insert(dev.interface, dev); + break; + case OBJ_PATH_LINK: + Link link = yield Bus.get_proxy(BusType.SYSTEM, + BUS_NAME_WIFID, + path); + links.insert(key, link); + info("found wnic: %s\n", link.interface_name); + break; + case OBJ_PATH_PEER: + key = decode_path(key); + if(!peers.contains(key)) { + Peer peer = yield Bus.get_proxy(BusType.SYSTEM, + BUS_NAME_WIFID, + path); + peers.insert(key, peer); + info("found peer: %s\n", key); + } + break; + case OBJ_PATH_SINK: + key = decode_path(key); + Sink sink = yield Bus.get_proxy(BusType.SYSTEM, + BUS_NAME_DISPD, + path); + sinks.insert(key, sink); + info("found sink: %s", key); + form_p2p_group.begin(key, sink); + break; + case OBJ_PATH_SESSION: + break; + } +} + +void on_object_added(DBusObjectManager m, DBusObject o) +{ + try { + add_object.begin(o); + } + catch(Error e) { + warning("error occured while adding newly created DBus object: %s", + e.message); + } +} + +void on_object_removed(DBusObjectManager m, DBusObject o) +{ + //try { + //remove_object.begin(o); + //} + //catch(Error e) { + //warning("error occured while removing newly created DBus object: %s", + //e.message); + //} +} + +async void fetch_info_from_dbus() throws Error +{ + wifi = yield DBusObjectManagerClient.new_for_bus( + BusType.SYSTEM, + DBusObjectManagerClientFlags.NONE, + BUS_NAME_WIFID, + "/org/freedesktop/miracle/wifi", + null, + null); + foreach(var o in wifi.get_objects()) { + yield add_object(o); + } + wifi.object_added.connect(on_object_added); + wifi.object_removed.connect(on_object_removed); + + nm = yield DBusObjectManagerClient.new_for_bus( + BusType.SYSTEM, + DBusObjectManagerClientFlags.NONE, + BUS_NAME_NETWORK_MANAGER, + "/org/freedesktop", + null, + null); + foreach(var o in nm.get_objects()) { + yield add_object(o); + } + nm.object_added.connect(on_object_added); + nm.object_removed.connect(on_object_removed); + + wfd = yield DBusObjectManagerClient.new_for_bus( + BusType.SYSTEM, + DBusObjectManagerClientFlags.NONE, + BUS_NAME_DISPD, + "/org/freedesktop/miracle/wfd", + null, + null); + foreach(var o in wfd.get_objects()) { + yield add_object(o); + } + wfd.object_added.connect(on_object_added); + wfd.object_removed.connect(on_object_removed); +} + +async void initiate_session() throws Error +{ + unowned Sink sink = find_sink_by_label(curr_sink_id); + + unowned string xauth = Environment.get_variable("XAUTHORITY"); + if(null == xauth) { + error("no environment variable XAUTHORITY specified"); + } + unowned string display = Environment.get_variable("DISPLAY"); + if(null == display) { + error("no environment variable DISPLAY specified"); + } + info(@"establishing display session..."); + sink.start_session(xauth, + @"x://$(display).0", + 0, 0, 1920, 1080, + "alsa_output.pci-0000_00_1b.0.analog-stereo.monitor"); +} + +async void form_p2p_group(string id, Sink sink) throws Error +{ + if(null != curr_sink_id) { + print("already hang out with sink: %s", curr_sink_id); + return; + } + + if(!id.has_prefix(opt_peer_mac)) { + print("not the sink we are waiting for: %s", id); + return; + } + + curr_sink_id = id; + + Peer p = peers.lookup(id); + if(null == p) { + p = yield Bus.get_proxy(BusType.SYSTEM, + BUS_NAME_WIFID, + sink.peer); + peers.insert(id, p); + } + + info("forming P2P group with %s (%s)...", p.p2_p_mac, p.friendly_name); + + uint timeout_id = Timeout.add_seconds(20, () => { + p.disconnect(); + form_p2p_group.callback(); + return false; + }); + ulong prop_changed_id = (p as DBusProxy).g_properties_changed.connect((props) => { + foreach(var prop in props) { + string k; + Variant v; + prop.get("{sv}", out k, out v); + if("Connected" == k) { + form_p2p_group.callback(); + break; + } + } + }); + + p.connect("auto", ""); + yield; + + Source.remove(timeout_id); + (p as DBusProxy).disconnect(prop_changed_id); + + if(!p.connected) { + ++ retry_count; + if(3 == retry_count) { + print("tried our best to form P2P group but with failure, bye"); + } + + print("failed to form P2P group with %s, try again", p.p2_p_mac); + + curr_sink_id = null; + + form_p2p_group.begin(id, sink); + return; + } + + yield initiate_session(); +} + +async void start_p2p_scan() throws Error +{ + Device d = find_device_by_name(opt_iface); + if(null == d) { + throw new WfdCtlError.NO_SUCH_NIC("no such wireless adapter: %s", + opt_iface); + } + + if(d.managed) { + info("tell NetworkManager do not touch %s anymore", opt_iface); + + d.managed = false; + yield wait_prop_changed(d as DBusProxy, "Managed"); + } + + Link l = find_link_by_name(opt_iface); + if(null == l) { + throw new WfdCtlError.NO_SUCH_NIC("no such wireless adapter: %s", + opt_iface); + } + + if(!l.managed) { + info("let wifid manage %s", opt_iface); + + l.manage(); + yield wait_prop_changed(l as DBusProxy, "Managed"); + } + + if(l.wfd_subelements != opt_wfd_subelems) { + info("update wfd_subelems to broadcast what kind of device we are"); + + l.wfd_subelements = opt_wfd_subelems; + yield wait_prop_changed(l as DBusProxy, "WfdSubelements"); + } + + if(!l.p2_p_scanning) { + info("start P2P scanning..."); + l.p2_p_scanning = true; + } + + info("wait for peer '%s'...", opt_peer_mac); +} + +async void start_wireless_display() throws Error +{ + yield fetch_info_from_dbus(); + yield start_p2p_scan(); +} + +void app_activate(Application app) +{ + if(null == opt_iface) { + opt_iface = "wlan0"; + print("no wireless adapter specified by -i, use '%s' instead", + opt_iface); + } + + if(null == opt_wfd_subelems) { + opt_wfd_subelems = "000600111c4400c8"; + print("no wfd_subelems specified by -w, use '%s' instead", + opt_wfd_subelems); + } + + if(null == opt_peer_mac) { + print("no peer MAC specified by -p, bye"); + app.release(); + return; + } + + start_wireless_display.begin((o, r) => { + try { + start_wireless_display.end(r); + } + catch(Error e) { + print("failed to fetch device information from DBus"); + app.release(); + } + }); +} + +void app_startup(Application app) +{ + app.hold(); +} + +int main(string[]? argv) +{ + Intl.setlocale(); + Environment.set_prgname(Path.get_basename(argv[0])); + + devices = new HashTable(str_hash, str_equal); + links = new HashTable(str_hash, str_equal); + peers = new HashTable(str_hash, str_equal); + sinks = new HashTable(str_hash, str_equal); + sessions = new HashTable(str_hash, str_equal); + + var options = new OptionContext("- WfdCtl"); + options.set_help_enabled(true); + options.add_main_entries(option_entries, null); + + Application app = new Application("org.freedesktop.miracle.WfdCtl", + ApplicationFlags.FLAGS_NONE); + app.set_default(); + app.startup.connect(app_startup); + app.activate.connect(app_activate); + + try { + options.parse(ref argv); + app.register(); + } + catch(Error e) { + print("failed to startup: %s", e.message); + return 1; + } + + return app.run(argv); +}