1
0
Fork 0
mirror of https://github.com/albfan/miraclecast.git synced 2025-03-09 23:38:56 +00:00

move miracle-wfdctl from demo/ to res/

Change-Id: I37df75135e4713a73d3c63353c9a17dc248e2d7e
This commit is contained in:
Derek Dai 2017-05-08 15:30:28 +08:00
parent 1129625c66
commit 6d1591de00
No known key found for this signature in database
GPG key ID: E109CC97553EF009
16 changed files with 115 additions and 147 deletions

View file

@ -1,69 +1,3 @@
########### install files ###############
execute_process(
COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=system_bus_services_dir dbus-1
OUTPUT_VARIABLE DBUS_SYSTEM_SERVICES_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=systemdsystemunitdir systemd
OUTPUT_VARIABLE SYSTEMD_SYSTEM_UNIT_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
configure_file(
miracle-wifid.service.cmake
miracle-wifid.service
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/miracle-wifid.service
DESTINATION ${SYSTEMD_SYSTEM_UNIT_DIR}/
)
install(
CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink miracle-wifid.service \$ENV{DESTDIR}/${SYSTEMD_SYSTEM_UNIT_DIR}/dbus-org.freedesktop.miracle.wifi.service)"
)
configure_file(
miracle-dispd.service.cmake
miracle-dispd.service
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/miracle-dispd.service
DESTINATION ${SYSTEMD_SYSTEM_UNIT_DIR}/
)
install(
CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink miracle-dispd.service \$ENV{DESTDIR}/${SYSTEMD_SYSTEM_UNIT_DIR}/dbus-org.freedesktop.miracle.wfd.service)"
)
configure_file(
org.freedesktop.miracle.wifi.service.cmake
org.freedesktop.miracle.wifi.service
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.miracle.wifi.service
DESTINATION ${DBUS_SYSTEM_SERVICES_DIR}/
)
configure_file(
org.freedesktop.miracle.wfd.service.cmake
org.freedesktop.miracle.wfd.service
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.miracle.wfd.service
DESTINATION ${DBUS_SYSTEM_SERVICES_DIR}/
)
install(
PROGRAMS miracle-gst gstplayer uibc-viewer
DESTINATION bin
)
install(
FILES org.freedesktop.miracle.conf
DESTINATION /etc/dbus-1/system.d
)
include(ValaPrecompile)
pkg_check_modules(GIO2 REQUIRED gio-2.0)
pkg_check_modules(GDK3 REQUIRED gdk-3.0)
@ -73,11 +7,15 @@ include_directories(
${GST1_INCLUDE_DIRS}
${GDK3_INCLUDE_DIRS}
${GIO_INCLUDE_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}
)
set(CMAKE_C_FLAGS "-Wno-deprecated-declarations ${CMAKE_C_FLAGS}")
set(CMAKE_C_FLAGS "-Wno-unused-but-set-variable ${CMAKE_C_FLAGS}")
set(CMAKE_C_FLAGS "-Wno-missing-braces ${CMAKE_C_FLAGS}")
if(VALA_VERSION VERSION_GREATER 0.34.0 AND NOT GDK_VERSION_NEWER_THEN_3_22)
list(APPEND VALA_EXTRA_OPTIONS -D GDK3_HAS_MONITOR_CLASS)
endif()
vala_precompile(
GSTENCODER_SRC gstencoder
@ -89,7 +27,6 @@ vala_precompile(
gio-2.0
posix
)
add_executable(gstencoder ${GSTENCODER_SRC})
target_link_libraries(
gstencoder
@ -98,4 +35,92 @@ target_link_libraries(
${GIO2_LIBRARIES}
)
vala_precompile(
WFDCTL_SRC wfdctl
wfdctl.vala
networkmanager.vala
miracle-wifi.vala
miracle-wfd.vala
GENERATE_HEADER
wfdctl.h
CUSTOM_VAPIS
sigint.vapi
OPTIONS
--target-glib=2.50
${VALA_EXTRA_OPTIONS}
PACKAGES
gio-2.0
gdk-3.0
)
add_executable(miracle-wfdctl ${WFDCTL_SRC} sigint.c)
target_link_libraries(miracle-wfdctl ${GIO2_LIBRARIES} ${GDK3_LIBRARIES})
########### install files ###############
install(TARGETS gstencoder DESTINATION bin)
install(TARGETS miracle-wfdctl DESTINATION bin)
execute_process(
COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=system_bus_services_dir dbus-1
OUTPUT_VARIABLE DBUS_SYSTEM_SERVICES_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=systemdsystemunitdir systemd
OUTPUT_VARIABLE SYSTEMD_SYSTEM_UNIT_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
configure_file(
miracle-wifid.service.cmake
miracle-wifid.service
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/miracle-wifid.service
DESTINATION ${SYSTEMD_SYSTEM_UNIT_DIR}/
)
install(
CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink miracle-wifid.service \$ENV{DESTDIR}${SYSTEMD_SYSTEM_UNIT_DIR}/dbus-org.freedesktop.miracle.wifi.service)"
)
configure_file(
miracle-dispd.service.cmake
miracle-dispd.service
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/miracle-dispd.service
DESTINATION ${SYSTEMD_SYSTEM_UNIT_DIR}/
)
install(
CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink miracle-dispd.service \$ENV{DESTDIR}${SYSTEMD_SYSTEM_UNIT_DIR}/dbus-org.freedesktop.miracle.wfd.service)"
)
configure_file(
org.freedesktop.miracle.wifi.service.cmake
org.freedesktop.miracle.wifi.service
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.miracle.wifi.service
DESTINATION $ENV{DESTDIR}${DBUS_SYSTEM_SERVICES_DIR}/
)
configure_file(
org.freedesktop.miracle.wfd.service.cmake
org.freedesktop.miracle.wfd.service
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.miracle.wfd.service
DESTINATION $ENV{DESTDIR}${DBUS_SYSTEM_SERVICES_DIR}/
)
install(
PROGRAMS miracle-gst gstplayer uibc-viewer
DESTINATION bin
)
install(
FILES org.freedesktop.miracle.conf
DESTINATION $ENV{DESTDIR}/etc/dbus-1/system.d
)

View file

@ -43,11 +43,32 @@ install_data('miracle-gst', 'gstplayer', 'uibc-viewer',
install_dir: get_option('bindir'),
install_mode: 'rwxr-xr-x')
add_languages('vala')
gio2 = dependency('gio-2.0')
gdk3 = dependency('gdk-3.0')
gst1 = dependency('gstreamer-1.0')
gst1_base = dependency('gstreamer-base-1.0')
add_languages('vala')
valac = meson.get_compiler('vala')
valac_extra_args = []
if valac.version().version_compare('>=0.34') and gdk3.version().version_compare('>=3.22')
valac_extra_args += ['-D', 'GDK3_HAS_MONITOR_CLASS']
endif
executable('gstencoder', 'gstencoder.vala',
dependencies: [gst1, gst1_base, gio2],
install: true,
vala_args: ['--pkg=posix'])
miracle_wfdctl_src = ['wfdctl.vala',
'sigint.vapi',
'sigint.c',
'networkmanager.vala',
'miracle-wifi.vala',
'miracle-wfd.vala'
]
executable('miracle-wfdctl', miracle_wfdctl_src,
dependencies: [gio2, gdk3],
vala_args: valac_extra_args,
install: true
)

50
res/miracle-wfd.vala Normal file
View file

@ -0,0 +1,50 @@
/* Generated by vala-dbus-binding-tool 0.4.0. Do not modify! */
/* Generated with: /usr/bin/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=/home/derekdai/Projects/miraclecast/demo/dbus */
using GLib;
namespace Org {
namespace Freedesktop {
namespace Miracle {
namespace Wfd {
[DBus (name = "org.freedesktop.miracle.wfd.Sink")]
public interface Sink : GLib.Object {
[DBus (name = "StartSession", timeout = 120000)]
public abstract GLib.ObjectPath start_session(string param0, string param1, uint param2, uint param3, uint param4, uint param5, string param6) throws DBusError, IOError;
[DBus (name = "Session", timeout = 120000)]
public abstract GLib.ObjectPath session { owned get; }
[DBus (name = "Peer", timeout = 120000)]
public abstract GLib.ObjectPath peer { owned get; }
}
[DBus (name = "org.freedesktop.miracle.wfd.Session")]
public interface Session : GLib.Object {
[DBus (name = "Resume", timeout = 120000)]
public abstract void resume() throws DBusError, IOError;
[DBus (name = "Pause", timeout = 120000)]
public abstract void pause() throws DBusError, IOError;
[DBus (name = "Teardown", timeout = 120000)]
public abstract void teardown() throws DBusError, IOError;
[DBus (name = "Sink", timeout = 120000)]
public abstract GLib.ObjectPath sink { owned get; }
[DBus (name = "Url", timeout = 120000)]
public abstract string url { owned get; }
[DBus (name = "State", timeout = 120000)]
public abstract int state { get; }
}
}
}
}
}

92
res/miracle-wifi.vala Normal file
View file

@ -0,0 +1,92 @@
/* Generated by vala-dbus-binding-tool 0.4.0. Do not modify! */
/* Generated with: /usr/bin/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=/home/derekdai/Projects/miraclecast/demo/dbus */
using GLib;
namespace Org {
namespace Freedesktop {
namespace Miracle {
namespace Wifi {
[DBus (name = "org.freedesktop.miracle.wifi.Peer")]
public interface Peer : GLib.Object {
[DBus (name = "Connect", timeout = 120000)]
public abstract void connect(string param0, string param1) throws DBusError, IOError;
[DBus (name = "Disconnect", timeout = 120000)]
public abstract void disconnect() throws DBusError, IOError;
[DBus (name = "Link", timeout = 120000)]
public abstract GLib.ObjectPath link { owned get; }
[DBus (name = "P2PMac", timeout = 120000)]
public abstract string p2p_mac { owned get; }
[DBus (name = "FriendlyName", timeout = 120000)]
public abstract string friendly_name { owned get; }
[DBus (name = "Connected", timeout = 120000)]
public abstract bool connected { get; }
[DBus (name = "Interface", timeout = 120000)]
public abstract string interface { owned get; }
[DBus (name = "LocalAddress", timeout = 120000)]
public abstract string local_address { owned get; }
[DBus (name = "RemoteAddress", timeout = 120000)]
public abstract string remote_address { owned get; }
[DBus (name = "WfdSubelements", timeout = 120000)]
public abstract string wfd_subelements { owned get; }
[DBus (name = "ProvisionDiscovery")]
public signal void provision_discovery(string param0, string param1);
[DBus (name = "GoNegRequest")]
public signal void go_neg_request(string param0, string param1);
[DBus (name = "FormationFailure")]
public signal void formation_failure(string param0);
}
[DBus (name = "org.freedesktop.miracle.wifi.Link")]
public interface Link : GLib.Object {
[DBus (name = "Manage", timeout = 120000)]
public abstract void manage() throws DBusError, IOError;
[DBus (name = "Unmanage", timeout = 120000)]
public abstract void unmanage() throws DBusError, IOError;
[DBus (name = "InterfaceIndex", timeout = 120000)]
public abstract uint interface_index { get; }
[DBus (name = "MACAddress", timeout = 120000)]
public abstract string m_a_c_address { owned get; }
[DBus (name = "InterfaceName", timeout = 120000)]
public abstract string interface_name { owned get; }
[DBus (name = "FriendlyName", timeout = 120000)]
public abstract string friendly_name { owned get; set; }
[DBus (name = "Managed", timeout = 120000)]
public abstract bool managed { get; }
[DBus (name = "P2PState", timeout = 120000)]
public abstract int p2p_state { get; }
[DBus (name = "P2PScanning", timeout = 120000)]
public abstract bool p2p_scanning { get; set; }
[DBus (name = "WfdSubelements", timeout = 120000)]
public abstract string wfd_subelements { owned get; set; }
}
}
}
}
}

114
res/networkmanager.vala Normal file
View file

@ -0,0 +1,114 @@
/* Generated by vala-dbus-binding-tool 0.4.0. Do not modify! */
/* Generated with: /usr/bin/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=/home/derekdai/Projects/miraclecast/demo/dbus */
using GLib;
namespace Org {
namespace Freedesktop {
namespace NetworkManager {
[DBus (name = "org.freedesktop.NetworkManager.Device")]
public interface Device : GLib.Object {
[DBus (name = "Reapply", timeout = 120000)]
public abstract void reapply(GLib.HashTable<string, GLib.HashTable<string, GLib.Variant>> connection, uint64 version_id, uint flags) throws DBusError, IOError;
[DBus (name = "GetAppliedConnection", timeout = 120000)]
public abstract void get_applied_connection(uint flags, out GLib.HashTable<string, GLib.HashTable<string, GLib.Variant>> connection, out uint64 version_id) throws DBusError, IOError;
[DBus (name = "Disconnect", timeout = 120000)]
public abstract void disconnect() throws DBusError, IOError;
[DBus (name = "Delete", timeout = 120000)]
public abstract void delete() throws DBusError, IOError;
[DBus (name = "StateChanged", timeout = 120000)]
public signal void state_changed(uint new_state, uint old_state, uint reason);
[DBus (name = "Udi", timeout = 120000)]
public abstract string udi { owned get; }
[DBus (name = "Interface", timeout = 120000)]
public abstract string interface { owned get; }
[DBus (name = "IpInterface", timeout = 120000)]
public abstract string ip_interface { owned get; }
[DBus (name = "Driver", timeout = 120000)]
public abstract string driver { owned get; }
[DBus (name = "DriverVersion", timeout = 120000)]
public abstract string driver_version { owned get; }
[DBus (name = "FirmwareVersion", timeout = 120000)]
public abstract string firmware_version { owned get; }
[DBus (name = "Capabilities", timeout = 120000)]
public abstract uint capabilities { get; }
[DBus (name = "Ip4Address", timeout = 120000)]
public abstract uint ip4_address { get; }
[DBus (name = "State", timeout = 120000)]
public abstract uint state { get; }
[DBus (name = "StateReason", timeout = 120000)]
public abstract DeviceStateReasonStruct state_reason { owned get; }
[DBus (name = "ActiveConnection", timeout = 120000)]
public abstract GLib.ObjectPath active_connection { owned get; }
[DBus (name = "Ip4Config", timeout = 120000)]
public abstract GLib.ObjectPath ip4_config { owned get; }
[DBus (name = "Dhcp4Config", timeout = 120000)]
public abstract GLib.ObjectPath dhcp4_config { owned get; }
[DBus (name = "Ip6Config", timeout = 120000)]
public abstract GLib.ObjectPath ip6_config { owned get; }
[DBus (name = "Dhcp6Config", timeout = 120000)]
public abstract GLib.ObjectPath dhcp6_config { owned get; }
[DBus (name = "Managed", timeout = 120000)]
public abstract bool managed { get; set; }
[DBus (name = "Autoconnect", timeout = 120000)]
public abstract bool autoconnect { get; set; }
[DBus (name = "FirmwareMissing", timeout = 120000)]
public abstract bool firmware_missing { get; }
[DBus (name = "NmPluginMissing", timeout = 120000)]
public abstract bool nm_plugin_missing { get; }
[DBus (name = "DeviceType", timeout = 120000)]
public abstract uint device_type { get; }
[DBus (name = "AvailableConnections", timeout = 120000)]
public abstract GLib.ObjectPath[] available_connections { owned get; }
[DBus (name = "PhysicalPortId", timeout = 120000)]
public abstract string physical_port_id { owned get; }
[DBus (name = "Mtu", timeout = 120000)]
public abstract uint mtu { get; }
[DBus (name = "Metered", timeout = 120000)]
public abstract uint metered { get; }
[DBus (name = "LldpNeighbors", timeout = 120000)]
public abstract GLib.HashTable<string, GLib.Variant>[] lldp_neighbors { owned get; }
[DBus (name = "Real", timeout = 120000)]
public abstract bool real { get; }
public struct DeviceStateReasonStruct {
public uint attr1;
public uint attr2;
}
}
}
}
}

64
res/sigint.c Normal file
View file

@ -0,0 +1,64 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* MiracleCast is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* MiracleCast is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/signalfd.h>
#include "sigint.h"
typedef struct _SigintDelegate SigintDelegate;
struct _SigintDelegate
{
SigintHandler handler;
gpointer user_data;
};
static gboolean sigint_on_signal(GIOChannel *c, GIOCondition e, gpointer d)
{
struct signalfd_siginfo siginfo;
g_io_channel_read_chars(c, (gchar *) &siginfo, sizeof(siginfo), NULL, NULL);
SigintDelegate *delegate = d;
(*delegate->handler)(delegate->user_data);
return FALSE;
}
void sigint_add_watch(SigintHandler handler, gpointer user_data)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmask(SIG_BLOCK, &mask, NULL);
SigintDelegate *d = g_malloc(sizeof(SigintDelegate));
*d = (SigintDelegate) {
handler = handler,
user_data = user_data
};
int fd = signalfd(-1, &mask, SFD_CLOEXEC);
GIOChannel *c = g_io_channel_unix_new(fd);
g_io_add_watch_full(c,
G_PRIORITY_DEFAULT,
G_IO_IN | G_IO_ERR | G_IO_HUP,
sigint_on_signal,
d,
g_free);
g_io_channel_set_encoding(c, NULL, NULL);
/*g_io_channel_unref(c);*/
}

29
res/sigint.h Normal file
View file

@ -0,0 +1,29 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* MiracleCast is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* MiracleCast is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
*/
#include <glib.h>
#ifndef DISP_SIGFD_H
#define DISP_SIGFD_H
typedef void (*SigintHandler)(gpointer user_data);
void sigint_add_watch(SigintHandler handler, gpointer user_data);
#endif /* DISP_SIGFD_H */

8
res/sigint.vapi Normal file
View file

@ -0,0 +1,8 @@
[CCode(cheader_filename="sigint.h")]
class Sigint
{
public delegate void Handler();
public static void add_watch(Handler handler);
public static void remove_watch();
}

798
res/wfdctl.vala Normal file
View file

@ -0,0 +1,798 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* MiracleCast is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* MiracleCast is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
*/
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";
const string IFACE_DEVICE = "org.freedesktop.NetworkManager.Device";
const string IFACE_LINK = "org.freedesktop.miracle.wifi.Link";
const string IFACE_PEER = "org.freedesktop.miracle.wifi.Peer";
const string IFACE_SINK = "org.freedesktop.miracle.wfd.Sink";
const string IFACE_SESSION = "org.freedesktop.miracle.wfd.Session";
errordomain WfdCtlError
{
NO_SUCH_NIC,
TIMEOUT,
MONITOR_GONE,
FORMATION_ERROR,
NO_P2P_SUPPORT,
}
private void print(string format, ...)
{
stderr.printf("%s: ", Environment.get_prgname());
stderr.vprintf(format, va_list());
stderr.printf("\n");
}
// to deal with sd_bus_path_encode/decode()ed path
private 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 class WfdCtl : GLib.Application
{
static string opt_iface;
static string opt_wfd_subelems;
static string opt_peer_mac;
static string opt_display;
static string opt_authority;
static int opt_monitor_num;
static string opt_audio_device;
static bool opt_dont_borrow_wnic = false;
static bool opt_dont_return_wnic = false;
protected signal void link_added(string index, Link link);
protected signal void link_removed(string index, Link link);
protected signal void peer_added(string label, Peer peer);
protected signal void peer_removed(string label, Peer peer);
protected signal void sink_added(string label, Sink sink);
protected signal void sink_removed(string label, Sink sink);
protected signal void session_added(string id, Session session);
protected signal void session_removed(string id, Session session);
DBusObjectManagerClient nm;
DBusObjectManagerClient wifi;
DBusObjectManagerClient wfd;
HashTable<string, Device> devices;
HashTable<string, Link> links;
HashTable<string, Peer> peers;
HashTable<string, Sink> sinks;
HashTable<string, Session> sessions;
string curr_sink_mac;
Gdk.Display display;
Peer curr_peer;
Sink curr_sink;
Session curr_session;
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: 000600001c4400c8", "device info subelems" },
{ "peer-mac", 'p', 0, OptionArg.STRING, ref opt_peer_mac, "MAC address of target peer", "peer MAC" },
{ "authority", 'x', 0, OptionArg.STRING, ref opt_authority, "authority to capture from display. default: XAUTHORITY environment variable", "display authority" },
{ "display", 'd', 0, OptionArg.STRING, ref opt_display, "display name. default: DISPLAY environment variable", "display name" },
{ "monitor-num", 'm', 0, OptionArg.INT, ref opt_monitor_num, "monitor number. default: -1, primary monitor", "monitor number" },
{ "audio-device", 'a', 0, OptionArg.STRING, ref opt_audio_device, "pulseaudio device name", "audio device name" },
{ "dont-borrow", 'b', 0, OptionArg.NONE, ref opt_dont_borrow_wnic, "do not acquire the ownership of WNIC before using it", "don't borrow WNIC" },
{ "dont-return", 'r', 0, OptionArg.NONE, ref opt_dont_return_wnic, "do not release the ownership of WNIC after using it", "don't release WNIC" },
{ null },
};
public WfdCtl()
{
Object(application_id: "org.freedesktop.miracle.WfdCtl",
flags: ApplicationFlags.FLAGS_NONE);
devices = new HashTable<string, Device>(str_hash, str_equal);
links = new HashTable<string, Link>(str_hash, str_equal);
peers = new HashTable<string, Peer>(str_hash, str_equal);
sinks = new HashTable<string, Sink>(str_hash, str_equal);
sessions = new HashTable<string, Session>(str_hash, str_equal);
add_main_option_entries(option_entries);
}
private DBusProxy? add_object(string path) throws Error
{
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 d = Bus.get_proxy_sync(BusType.SYSTEM,
BUS_NAME_NETWORK_MANAGER,
path);
if(is_wnic(d.interface) && !devices.contains(d.interface)) {
devices.insert(d.interface, d);
return d as DBusProxy;
}
break;
case OBJ_PATH_LINK:
key = decode_path(key);
Link l = links.lookup(key);
if(null == l) {
l = Bus.get_proxy_sync(BusType.SYSTEM,
BUS_NAME_WIFID,
path);
links.insert(key, l);
info("found wireless interface: %s", l.interface_name);
link_added(key, l);
}
return l as DBusProxy;
case OBJ_PATH_PEER:
key = decode_path(key);
Peer p = peers.lookup(key);
if(null == p) {
p = Bus.get_proxy_sync(BusType.SYSTEM,
BUS_NAME_WIFID,
path);
peers.insert(key, p);
info("peer added: %s (%s)", key, p.friendly_name);
peer_added(key, p);
}
return p as DBusProxy;
case OBJ_PATH_SINK:
key = decode_path(key);
Sink s = sinks.lookup(key);
if(null == s) {
s = Bus.get_proxy_sync(BusType.SYSTEM,
BUS_NAME_DISPD,
path);
sinks.insert(key, s);
info("sink added: %s", key);
sink_added(key, s);
}
return s as DBusProxy;
case OBJ_PATH_SESSION:
key = decode_path(key);
Session s = sessions.lookup(key);
if(null == s) {
s = Bus.get_proxy_sync(BusType.SYSTEM,
BUS_NAME_DISPD,
path);
sessions.insert(key, s);
info("session added: %s", key);
session_added(key, s);
}
return s as DBusProxy;
}
return null;
}
private void remove_object(string 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:
devices.remove(key);
break;
case OBJ_PATH_LINK:
key = decode_path(key);
Link l = links.lookup(key);
if(null == l) {
break;
}
links.remove(key);
link_removed(key, l);
break;
case OBJ_PATH_PEER:
key = decode_path(key);
Peer p = peers.lookup(key);
if(null == p) {
info("removing stray peer: %s", key);
break;
}
if(p == curr_peer) {
curr_peer = null;
}
peers.remove(key);
peer_removed(key, p);
break;
case OBJ_PATH_SINK:
key = decode_path(key);
Sink s = sinks.lookup(key);
if(null == s) {
info("removing stray sink: %s", key);
break;
}
if(s == curr_sink) {
curr_sink = null;
}
sinks.remove(key);
sink_removed(key, s);
break;
case OBJ_PATH_SESSION:
key = decode_path(key);
Session s = sessions.lookup(key);
if(null == s) {
info("removing stray session: %s", key);
break;
}
if(s == curr_session) {
curr_session = null;
}
sessions.remove(key);
session_removed(key, s);
break;
}
}
private void on_object_added(DBusObjectManager m, DBusObject o)
{
try {
add_object(o.get_object_path());
}
catch(Error e) {
print("failed to fetch information from DBus for object: %s",
o.get_object_path());
}
}
private void on_object_removed(DBusObjectManager m, DBusObject o)
{
remove_object(o.get_object_path());
}
private void fetch_info_from_dbus() throws Error
{
info("connecting to wifid...");
wifi = new DBusObjectManagerClient.for_bus_sync(
BusType.SYSTEM,
DBusObjectManagerClientFlags.NONE,
BUS_NAME_WIFID,
"/org/freedesktop/miracle/wifi",
null,
null);
wifi.object_added.connect(on_object_added);
wifi.object_removed.connect(on_object_removed);
info("connecting to nm...");
nm = new DBusObjectManagerClient.for_bus_sync(
BusType.SYSTEM,
DBusObjectManagerClientFlags.NONE,
BUS_NAME_NETWORK_MANAGER,
"/org/freedesktop",
null,
null);
nm.object_added.connect(on_object_added);
nm.object_removed.connect(on_object_removed);
info("connecting to dispd...");
wfd = new DBusObjectManagerClient.for_bus_sync(
BusType.SYSTEM,
DBusObjectManagerClientFlags.NONE,
BUS_NAME_DISPD,
"/org/freedesktop/miracle/wfd",
null,
null);
wfd.object_added.connect(on_object_added);
wfd.object_removed.connect(on_object_removed);
info("fetching from wifid...");
foreach(var o in wifi.get_objects()) {
add_object(o.get_object_path());
}
info("fetching from nm...");
foreach(var o in nm.get_objects()) {
add_object(o.get_object_path());
}
info("fetching from wfd...");
foreach(var o in wfd.get_objects()) {
add_object(o.get_object_path());
}
}
private async void acquire_wnic_ownership() throws Error
{
Device d = find_device_by_name(opt_iface);
if(null != d && d.managed) {
info("NetworkManager is releasing ownership of %s...", opt_iface);
d.managed = false;
yield wait_prop_changed(d, "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("wifid is releasing ownership of %s...", opt_iface);
l.unmanage();
yield wait_prop_changed(l, "Managed");
}
info("wifid is acquiring ownership of %s...", opt_iface);
l.manage();
yield wait_prop_changed(l, "Managed");
}
private async void start_p2p_scan() throws Error
{
Link? l = find_link_by_name(opt_iface);
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, "WfdSubelements");
}
if(-1 == l.p2p_state) {
throw new WfdCtlError.NO_P2P_SUPPORT("link %s has no P2P supporting", l.interface_name);
}
else if(0 == l.p2p_state) {
info("wait for P2P supporting status...");
yield wait_prop_changed(l, "P2PState", 3);
}
if(!l.p2p_scanning) {
info("start P2P scanning...");
l.p2p_scanning = true;
yield wait_prop_changed(l, "P2PScanning");
}
print("wait for peer '%s'...", opt_peer_mac);
}
private async void wait_for_target_sink()
{
if(null != find_sink_by_mac(opt_peer_mac)) {
return;
}
ulong id = sink_added.connect((l, s) => {
if(null != find_sink_by_mac(opt_peer_mac)) {
wait_for_target_sink.callback();
}
});
yield;
disconnect(id);
}
private async void form_p2p_group() throws Error
{
if(null != curr_sink_mac) {
print("already hang out with sink: %s", curr_sink_mac);
return;
}
Sink s = find_sink_by_mac(opt_peer_mac);
curr_sink_mac = opt_peer_mac;
string l = s.peer;
l = decode_path(l.substring(l.last_index_of_char('/') + 1));
Peer p = peers.lookup(l);
info("forming P2P group with %s (%s)...", p.p2p_mac, p.friendly_name);
ulong id = p.formation_failure.connect((r) => {
info("failed to form P2P group: %s", r);
});
p.connect("auto", "");
yield wait_prop_changed(p, "Connected", 20);
(p as Object).disconnect(id);
curr_peer = p;
info("P2P group formed");
}
#if GDK3_HAS_MONITOR_CLASS
private void get_monitor_geometry(out Gdk.Rectangle g) throws Error
{
Gdk.Monitor m;
if(-1 == opt_monitor_num) {
m = display.get_primary_monitor();
}
else {
m = display.get_monitor(opt_monitor_num);
}
if(null == m) {
throw new WfdCtlError.MONITOR_GONE("specified monitor disappeared");
}
g = m.geometry;
}
#else
private void get_monitor_geometry(out Gdk.Rectangle g) throws Error
{
var s = display.get_default_screen();
int m = (-1 == opt_monitor_num)
? s.get_primary_monitor()
: opt_monitor_num;
if(s.get_n_monitors() <= m) {
throw new WfdCtlError.MONITOR_GONE("specified monitor disappeared");
}
s.get_monitor_geometry(m, out g);
}
#endif
private unowned string session_state_to_str(int s)
{
switch(s) {
case 1:
return "connecting";
case 2:
return "capabilities exchanging";
case 3:
return "established";
case 4:
return "seting up session parameters";
case 5:
return "paused";
case 6:
return "playing";
case 7:
return "tearing down";
case 8:
return "destroyed";
}
return "unknown";
}
private async void establish_session() throws Error
{
Gdk.Rectangle g;
get_monitor_geometry(out g);
info("establishing display session...");
Sink sink = find_sink_by_mac(opt_peer_mac);
string path = sink.start_session(opt_authority,
@"x://$(opt_display)",
g.x,
g.y,
g.width,
g.height,
null == opt_audio_device ? "" : opt_audio_device);
curr_session = add_object(path) as Session;
(curr_session as DBusProxy).g_properties_changed.connect((props) => {
string k;
Variant v;
foreach(var prop in props) {
prop.get("{sv}", out k, out v);
if(k != "State") {
continue;
}
info("session status: %s", session_state_to_str(v.get_int32()));
if(6 == v.get_int32()) {
Idle.add(establish_session.callback);
}
break;
}
});
Error error = null;
var timeout_src = new TimeoutSource(10);
timeout_src.set_callback(() => {
error = new WfdCtlError.TIMEOUT("failed to establish session");
Idle.add(establish_session.callback);
return false;
});
yield;
timeout_src.destroy();
if(null != error) {
throw error;
}
curr_sink = sink;
}
private async void wait_for_session_ending()
{
info("wait for session ending");
ulong id = session_removed.connect((id, s) => {
wait_for_session_ending.callback();
});
yield;
disconnect(id);
info("session ended");
curr_session = null;
}
private async void release_wnic_ownership() throws Error
{
if(opt_dont_return_wnic) {
return;
}
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("wifid is releasing ownership of %s...", opt_iface);
l.unmanage();
yield wait_prop_changed(l, "Managed");
}
Device d = find_device_by_name(opt_iface);
if(null != d && !d.managed) {
info("NetworkManager is acquiring ownership of %s...", opt_iface);
d.managed = true;
yield wait_prop_changed(d, "Managed");
}
}
private async void start_wireless_display() throws Error
{
fetch_info_from_dbus();
if(!opt_dont_borrow_wnic) {
yield acquire_wnic_ownership();
}
yield start_p2p_scan();
yield wait_for_target_sink();
yield form_p2p_group();
yield establish_session();
yield wait_for_session_ending();
}
public void stop_wireless_display()
{
if(null != curr_session) {
info("tearing down wireless display...");
try {
curr_session.teardown();
}
catch(Error e) {
warning("failed to tearing down normally: %s", e.message);
}
wait_prop_changed.begin(curr_sink, "Session", 3, () => {
release_wnic_ownership.begin(quit);
});
}
else {
release_wnic_ownership.begin(quit);
}
}
private bool check_options()
{
if(null == opt_peer_mac) {
print("please specify a peer MAC with -p option");
return false;
}
print("peer-mac=%s", opt_peer_mac);
if(null == opt_display) {
opt_display = Environment.get_variable("DISPLAY");
}
print("display=%s", opt_display);
if(null == opt_authority) {
opt_authority = Environment.get_variable("XAUTHORITY");
}
print("authority=%s", opt_authority);
if(null == opt_iface) {
opt_iface = "wlan0";
}
print("interface=%s", opt_iface);
if(null == opt_wfd_subelems) {
opt_wfd_subelems = "000600001c4400c8";
}
print("wfd_subelemens=%s", opt_wfd_subelems);
display = Gdk.Display.open(opt_display);
if(null == display) {
print("invalid display option: %s", opt_display);
return false;
}
int n_monitors;
#if GDK3_HAS_MONITOR_CLASS
n_monitors = display.get_n_monitors();
#else
n_monitors = display.get_default_screen().get_n_monitors();
#endif
if(-1 > opt_monitor_num || opt_monitor_num >= n_monitors) {
print("invalid screen number option: %d", opt_monitor_num);
return false;
}
print("monitor-num=%d", opt_monitor_num);
return true;
}
protected override void activate()
{
if(!check_options()) {
return;
}
start_wireless_display.begin((o, r) => {
try {
start_wireless_display.end(r);
}
catch(Error e) {
print("failed to cast to wireless display: %s", e.message);
release();
}
release_wnic_ownership.begin((o, r) => {
try {
release_wnic_ownership.end(r);
}
catch(Error e) {
}
quit();
});
});
hold();
}
private 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;
}
private 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;
}
private unowned Sink? find_sink_by_mac(string m)
{
foreach(var l in sinks.get_keys()) {
if(l.has_prefix(m)) {
return sinks.lookup(l);
}
}
return null;
}
private bool is_wnic(string nic_name)
{
return find_link_by_name(nic_name) != null;
}
private async void wait_prop_changed<T>(T o,
string name,
uint timeout = 1) throws WfdCtlError
{
ulong prop_changed_id = (o as DBusProxy).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;
}
}
});
bool timed_out = false;
uint timeout_id = 0;
if(0 < timeout) {
timeout_id = Timeout.add_seconds(timeout,
() => {
timed_out = true;
wait_prop_changed.callback();
return false;
});
}
yield;
if(0 < timeout) {
Source.remove(timeout_id);
}
(o as DBusProxy).disconnect(prop_changed_id);
if(timed_out) {
throw new WfdCtlError.TIMEOUT("timeout to wait for property %s change",
name);
}
}
}
int main(string[]? argv)
{
Gdk.init(ref argv);
Intl.setlocale();
Environment.set_prgname(Path.get_basename(argv[0]));
Application app = new WfdCtl();
app.set_default();
Sigint.add_watch((app as WfdCtl).stop_wireless_display);
try {
app.register();
}
catch(Error e) {
print("failed to startup: %s", e.message);
return 1;
}
int r = app.run(argv);
print("Bye");
return r;
}