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 @@
+
+
+
+
+
+
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);
+}