[gnome-network-displays] Add Miracast over Infrastructure (MICE) support
- From: Benjamin Berg <bberg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-network-displays] Add Miracast over Infrastructure (MICE) support
- Date: Fri, 8 Jul 2022 10:18:03 +0000 (UTC)
commit d3c36368adf005c305821dca7e66da84f328697d
Author: Christian Glombek <lorbus fedoraproject org>
Date: Sun Jul 3 01:16:13 2022 +0200
Add Miracast over Infrastructure (MICE) support
Closes: #58
src/meson.build | 8 +-
src/nd-wfd-mice-provider.c | 390 +++++++++++++++++++++++++++++++
src/nd-wfd-mice-provider.h | 37 +++
src/nd-wfd-mice-sink.c | 555 +++++++++++++++++++++++++++++++++++++++++++++
src/nd-wfd-mice-sink.h | 37 +++
src/nd-window.c | 50 ++++
6 files changed, 1075 insertions(+), 2 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 7e913b1..c50da56 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,6 +12,8 @@ gnome_nd_sources = [
'nd-provider.c',
'nd-meta-sink.c',
'nd-meta-provider.c',
+ 'nd-wfd-mice-sink.c',
+ 'nd-wfd-mice-provider.c',
'nd-wfd-p2p-sink.c',
'nd-wfd-p2p-provider.c',
'nd-nm-device-registry.c',
@@ -29,13 +31,15 @@ gnome_nd_sources += gnome.mkenums_simple(
)
gnome_nd_deps = [
+ dependency('avahi-client'),
+ dependency('avahi-gobject'),
dependency('gio-2.0', version: '>= 2.50'),
- dependency('gtk+-3.0', version: '>= 3.22'),
- dependency('libnm', version: '>= 1.15'),
dependency('gstreamer-1.0', version: '>= 1.14'),
dependency('gstreamer-pbutils-1.0', version: '>= 1.14'),
dependency('gstreamer-plugins-base-1.0'),
dependency('gstreamer-rtsp-server-1.0'),
+ dependency('gtk+-3.0', version: '>= 3.22'),
+ dependency('libnm', version: '>= 1.15'),
dependency('libpulse-mainloop-glib'),
]
diff --git a/src/nd-wfd-mice-provider.c b/src/nd-wfd-mice-provider.c
new file mode 100644
index 0000000..1b68d46
--- /dev/null
+++ b/src/nd-wfd-mice-provider.c
@@ -0,0 +1,390 @@
+/* nd-wfd-mice-provider.c
+ *
+ * Copyright 2022 Christian Glombek <lorbus fedoraproject org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <avahi-gobject/ga-service-browser.h>
+#include <avahi-gobject/ga-service-resolver.h>
+#include <avahi-common/address.h>
+#include "gnome-network-displays-config.h"
+#include "nd-wfd-mice-provider.h"
+#include "nd-sink.h"
+#include "nd-wfd-mice-sink.h"
+
+struct _NdWFDMiceProvider
+{
+ GObject parent_instance;
+
+ GPtrArray *sinks;
+ GaClient *avahi_client;
+
+ GSocketService *signalling_server;
+
+ gboolean discover;
+};
+
+enum {
+ PROP_CLIENT = 1,
+
+ PROP_DISCOVER,
+
+ PROP_LAST = PROP_DISCOVER,
+};
+
+static void nd_wfd_mice_provider_provider_iface_init (NdProviderIface *iface);
+static GList * nd_wfd_mice_provider_provider_get_sinks (NdProvider *provider);
+
+G_DEFINE_TYPE_EXTENDED (NdWFDMiceProvider, nd_wfd_mice_provider, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (ND_TYPE_PROVIDER,
+ nd_wfd_mice_provider_provider_iface_init);
+ )
+
+static GParamSpec * props[PROP_LAST] = { NULL, };
+
+static void
+nd_wfd_mice_provider_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NdWFDMiceProvider *provider = ND_WFD_MICE_PROVIDER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, provider->avahi_client);
+ break;
+
+ case PROP_DISCOVER:
+ g_value_set_boolean (value, provider->discover);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+nd_wfd_mice_provider_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NdWFDMiceProvider *provider = ND_WFD_MICE_PROVIDER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ /* Construct only */
+ provider->avahi_client = g_value_dup_object (value);
+ break;
+
+ case PROP_DISCOVER:
+ provider->discover = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+nd_wfd_mice_provider_finalize (GObject *object)
+{
+ NdWFDMiceProvider *provider = ND_WFD_MICE_PROVIDER (object);
+ g_clear_pointer (&provider->sinks, g_ptr_array_unref);
+ g_clear_object (&provider->avahi_client);
+ g_clear_object (&provider->signalling_server);
+
+ G_OBJECT_CLASS (nd_wfd_mice_provider_parent_class)->finalize (object);
+}
+
+static void
+nd_wfd_mice_provider_class_init (NdWFDMiceProviderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = nd_wfd_mice_provider_get_property;
+ object_class->set_property = nd_wfd_mice_provider_set_property;
+ object_class->finalize = nd_wfd_mice_provider_finalize;
+
+ props[PROP_CLIENT] =
+ g_param_spec_object ("client", "Client",
+ "The AvahiClient used to find sinks.",
+ GA_TYPE_CLIENT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, PROP_LAST, props);
+
+ g_object_class_override_property (object_class, PROP_DISCOVER, "discover");
+}
+
+static void
+resolver_found_cb (GaServiceResolver *resolver,
+ AvahiIfIndex iface,
+ GaProtocol proto,
+ gchar *name,
+ gchar *type,
+ gchar *domain,
+ gchar *hostname,
+ AvahiAddress *addr,
+ gint port,
+ AvahiStringList *txt,
+ GaLookupResultFlags flags,
+ NdWFDMiceProvider *provider)
+{
+ NdWFDMiceSink * sink = NULL;
+ gchar address[AVAHI_ADDRESS_STR_MAX];
+
+ g_debug ("NdWFDMiceProvider: Found sink %s at %s:%d on interface %i", name, hostname, port, iface);
+
+ if (avahi_address_snprint (address, sizeof (address), addr) == NULL)
+ g_warning ("NdWFDMiceProvider: Failed to convert AvahiAddress to string");
+
+ g_debug ("NdWFDMiceProvider: Resolved %s to %s", hostname, address);
+
+ sink = nd_wfd_mice_sink_new (name, address);
+
+ g_object_unref (resolver);
+
+ g_ptr_array_add (provider->sinks, sink);
+ g_signal_emit_by_name (provider, "sink-added", sink);
+}
+
+static void
+resolver_failure_cb (GaServiceResolver *resolver,
+ GError *error,
+ NdWFDMiceProvider *provider)
+{
+ g_warning ("NdWFDMiceProvider: Failed to resolve Avahi service: %s", error->message);
+ g_object_unref (resolver);
+}
+
+static void
+service_added_cb (GaServiceBrowser *browser,
+ AvahiIfIndex iface,
+ GaProtocol proto,
+ gchar *name,
+ gchar *type,
+ gchar *domain,
+ GaLookupResultFlags flags,
+ NdWFDMiceProvider *provider)
+{
+ GaServiceResolver *resolver;
+ GError *error = NULL;
+
+ resolver = ga_service_resolver_new (iface,
+ proto,
+ name,
+ type,
+ domain,
+ GA_PROTOCOL_INET,
+ GA_LOOKUP_NO_FLAGS);
+
+ g_signal_connect (resolver,
+ "found",
+ (GCallback) resolver_found_cb,
+ provider);
+
+ g_signal_connect (resolver,
+ "failure",
+ (GCallback) resolver_failure_cb,
+ provider);
+
+ if (!ga_service_resolver_attach (resolver,
+ provider->avahi_client,
+ &error))
+ {
+ g_warning ("NdWFDMiceProvider: Failed to attach Avahi resolver: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+service_removed_cb (GaServiceBrowser *browser,
+ AvahiIfIndex iface,
+ GaProtocol proto,
+ gchar *name,
+ gchar *type,
+ gchar *domain,
+ GaLookupResultFlags flags,
+ NdWFDMiceProvider *provider)
+{
+ g_debug ("NdWFDMiceProvider: mDNS service \"%s\" removed from interface %i", name, iface);
+
+ for (gint i = 0; i < provider->sinks->len; i++)
+ {
+ g_autoptr(NdWFDMiceSink) sink = g_object_ref (g_ptr_array_index (provider->sinks, i));
+
+ NdSinkState state = nd_wfd_mice_sink_get_state (sink);
+ if (state == ND_SINK_STATE_WAIT_STREAMING ||
+ state == ND_SINK_STATE_STREAMING)
+ continue;
+
+ gchar * remote_name = NULL;
+ g_object_get (sink, "name", &remote_name, NULL);
+ if (remote_name == name)
+ {
+ g_debug ("NdWFDMiceProvider: Removing sink");
+ g_ptr_array_remove_index (provider->sinks, i);
+ g_signal_emit_by_name (provider, "sink-removed", sink);
+ break;
+ }
+ }
+}
+
+static void
+signalling_incoming_cb (GSocketService *service,
+ GSocketConnection * connection,
+ GObject * source,
+ gpointer user_data)
+{
+ /*
+ * XXX: we should read the full, variable-length message,
+ * find the respective sink,
+ * and appropriately respond with sink's signalling client.
+ */
+
+ /* NdWFDMiceProvider * self = ND_WFD_MICE_PROVIDER (user_data); */
+
+ gchar buffer[1024];
+ GInputStream * istream;
+ GError * error;
+
+ istream = g_io_stream_get_input_stream (G_IO_STREAM (connection));
+
+ g_input_stream_read (istream, buffer, sizeof (buffer), NULL, &error);
+ if (error != NULL)
+ g_warning ("NdWFDMiceProvider: Failed to connect to signalling host: %s", error->message);
+
+ g_debug ("NdWFDMiceProvider: Received Message: %s", buffer);
+
+ return;
+}
+
+static void
+nd_wfd_mice_provider_init (NdWFDMiceProvider *provider)
+{
+ g_autoptr(GError) error = NULL;
+ GSocketService * server;
+
+ provider->discover = TRUE;
+ provider->sinks = g_ptr_array_new_with_free_func (g_object_unref);
+ server = g_socket_service_new ();
+
+ g_socket_listener_add_inet_port ((GSocketListener *) server,
+ 7250,
+ NULL,
+ &error);
+ if (error != NULL)
+ {
+ g_warning ("NdWFDMiceProvider: Error starting signal listener: %s", error->message);
+ return;
+ }
+
+ g_signal_connect (server,
+ "incoming",
+ G_CALLBACK (signalling_incoming_cb),
+ provider);
+
+ g_socket_service_start (server);
+
+ provider->signalling_server = server;
+}
+
+/******************************************************************
+* NdProvider interface implementation
+******************************************************************/
+
+static void
+nd_wfd_mice_provider_provider_iface_init (NdProviderIface *iface)
+{
+ iface->get_sinks = nd_wfd_mice_provider_provider_get_sinks;
+}
+
+static GList *
+nd_wfd_mice_provider_provider_get_sinks (NdProvider *provider)
+{
+ NdWFDMiceProvider *wfd_mice_provider = ND_WFD_MICE_PROVIDER (provider);
+ GList *res = NULL;
+
+ for (gint i = 0; i < wfd_mice_provider->sinks->len; i++)
+ res = g_list_prepend (res, g_ptr_array_index (wfd_mice_provider->sinks, i));
+
+ return res;
+}
+
+/******************************************************************
+* NdWFDMiceProvider public functions
+******************************************************************/
+
+GaClient *
+nd_wfd_mice_provider_get_client (NdWFDMiceProvider *provider)
+{
+ return provider->avahi_client;
+}
+
+GSocketService *
+nd_wfd_mice_provider_get_signalling_server (NdWFDMiceProvider *provider)
+{
+ return provider->signalling_server;
+}
+
+NdWFDMiceProvider *
+nd_wfd_mice_provider_new (GaClient *client)
+{
+ return g_object_new (ND_TYPE_WFD_MICE_PROVIDER,
+ "client", client,
+ NULL);
+}
+
+gboolean
+nd_wfd_mice_provider_browse (NdWFDMiceProvider *provider, GError * error)
+{
+ GaServiceBrowser * avahi_browser;
+
+ avahi_browser = ga_service_browser_new ("_display._tcp");
+
+ if (provider->avahi_client == NULL)
+ {
+ g_warning ("NdWFDMiceProvider: No Avahi client found");
+ return FALSE;
+ }
+
+ g_signal_connect (avahi_browser,
+ "new-service",
+ (GCallback) service_added_cb,
+ provider);
+
+ g_signal_connect (avahi_browser,
+ "removed-service",
+ (GCallback) service_removed_cb,
+ provider);
+
+ if (!ga_service_browser_attach (avahi_browser,
+ provider->avahi_client,
+ &error))
+ {
+ g_warning ("NdWFDMiceProvider: Failed to attach Avahi Service Browser: %s", error->message);
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/src/nd-wfd-mice-provider.h b/src/nd-wfd-mice-provider.h
new file mode 100644
index 0000000..de25074
--- /dev/null
+++ b/src/nd-wfd-mice-provider.h
@@ -0,0 +1,37 @@
+/* nd-wfd-mice-provider.h
+ *
+ * Copyright 2022 Christian Glombek <lorbus fedoraproject org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <avahi-gobject/ga-client.h>
+#include <gio/gio.h>
+#include "nd-provider.h"
+
+G_BEGIN_DECLS
+
+#define ND_TYPE_WFD_MICE_PROVIDER (nd_wfd_mice_provider_get_type ())
+G_DECLARE_FINAL_TYPE (NdWFDMiceProvider, nd_wfd_mice_provider, ND, WFD_MICE_PROVIDER, GObject)
+
+NdWFDMiceProvider * nd_wfd_mice_provider_new (GaClient * client);
+
+GaClient * nd_wfd_mice_provider_get_client (NdWFDMiceProvider *provider);
+
+gboolean nd_wfd_mice_provider_browse (NdWFDMiceProvider *provider,
+ GError * error);
+
+G_END_DECLS
diff --git a/src/nd-wfd-mice-sink.c b/src/nd-wfd-mice-sink.c
new file mode 100644
index 0000000..d91f6fd
--- /dev/null
+++ b/src/nd-wfd-mice-sink.c
@@ -0,0 +1,555 @@
+/* nd-wfd-mice-sink.c
+ *
+ * Copyright 2022 Christian Glombek <lorbus fedoraproject org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gnome-network-displays-config.h"
+#include "nd-wfd-mice-sink.h"
+#include "wfd/wfd-client.h"
+#include "wfd/wfd-media-factory.h"
+#include "wfd/wfd-server.h"
+
+struct _NdWFDMiceSink
+{
+ GObject parent_instance;
+
+ NdSinkState state;
+
+ GCancellable *cancellable;
+
+ GStrv missing_video_codec;
+ GStrv missing_audio_codec;
+ char *missing_firewall_zone;
+
+ gchar *remote_address;
+ gchar *remote_name;
+
+ GSocketClient *signalling_client;
+ GSocketConnection *signalling_client_conn;
+
+ WfdServer *server;
+ guint server_source_id;
+};
+
+enum {
+ PROP_CLIENT = 1,
+ PROP_NAME,
+ PROP_ADDRESS,
+
+ PROP_DISPLAY_NAME,
+ PROP_MATCHES,
+ PROP_PRIORITY,
+ PROP_STATE,
+ PROP_MISSING_VIDEO_CODEC,
+ PROP_MISSING_AUDIO_CODEC,
+ PROP_MISSING_FIREWALL_ZONE,
+
+ PROP_LAST = PROP_DISPLAY_NAME,
+};
+
+static void nd_wfd_mice_sink_sink_iface_init (NdSinkIface *iface);
+static NdSink * nd_wfd_mice_sink_sink_start_stream (NdSink *sink);
+static void nd_wfd_mice_sink_sink_stop_stream (NdSink *sink);
+
+static void nd_wfd_mice_sink_sink_stop_stream_int (NdWFDMiceSink *self);
+
+G_DEFINE_TYPE_EXTENDED (NdWFDMiceSink, nd_wfd_mice_sink, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (ND_TYPE_SINK,
+ nd_wfd_mice_sink_sink_iface_init);
+ )
+
+static GParamSpec * props[PROP_LAST] = { NULL, };
+
+static gchar msg_source_ready[] = {
+ 0x00, 0x29, /* Length (41 bytes) */
+ 0x01, /* MICE Protocol Version */
+ 0x01, /* Command SOURCE_READY */
+
+ 0x00, /* Friendly Name TLV */
+ 0x00, 0x0A, /* Length (10 bytes) */
+ /* GNOME (UTF-16-encoded) */
+ 0x47, 0x00, 0x4E, 0x00, 0x4F, 0x00, 0x4D, 0x00, 0x45, 0x00,
+
+ 0x02, /* RTSP Port TLV */
+ 0x00, 0x02, /* Length (2 bytes) */
+ 0x1C, 0x44, /* Port 7236 */
+
+ 0x03, /* Source ID TLV */
+ 0x00, 0x10, /* Length (16 bytes) */
+ /* Source ID GnomeMICEDisplay (ascii) */
+ 0x47, 0x6E, 0x6F, 0x6D, 0x65, 0x4D, 0x49, 0x43, 0x45, 0x44, 0x69, 0x73, 0x70, 0x6C, 0x61, 0x79
+};
+
+static void
+nd_wfd_mice_sink_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NdWFDMiceSink *sink = ND_WFD_MICE_SINK (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, sink->signalling_client);
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, sink->remote_name);
+ break;
+
+ case PROP_ADDRESS:
+ g_value_set_string (value, sink->remote_address);
+ break;
+
+ case PROP_DISPLAY_NAME:
+ g_object_get_property (G_OBJECT (sink), "name", value);
+ break;
+
+ case PROP_MATCHES:
+ {
+ g_autoptr(GPtrArray) res = NULL;
+ res = g_ptr_array_new_with_free_func (g_free);
+
+ if (sink->remote_name)
+ g_ptr_array_add (res, g_strdup (sink->remote_name));
+
+ g_value_take_boxed (value, g_steal_pointer (&res));
+ break;
+ }
+
+ case PROP_PRIORITY:
+ g_value_set_int (value, 200);
+ break;
+
+ case PROP_STATE:
+ g_value_set_enum (value, sink->state);
+ break;
+
+ case PROP_MISSING_VIDEO_CODEC:
+ g_value_set_boxed (value, sink->missing_video_codec);
+ break;
+
+ case PROP_MISSING_AUDIO_CODEC:
+ g_value_set_boxed (value, sink->missing_audio_codec);
+ break;
+
+ case PROP_MISSING_FIREWALL_ZONE:
+ g_value_set_string (value, sink->missing_firewall_zone);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+nd_wfd_mice_sink_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NdWFDMiceSink *sink = ND_WFD_MICE_SINK (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ /* Construct only */
+ sink->signalling_client = g_value_dup_object (value);
+ break;
+
+ case PROP_NAME:
+ sink->remote_name = g_value_dup_string (value);
+ g_object_notify (G_OBJECT (sink), "display-name");
+ break;
+
+ case PROP_ADDRESS:
+ g_assert (sink->remote_address == NULL);
+ sink->remote_address = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+void
+nd_wfd_mice_sink_finalize (GObject *object)
+{
+ NdWFDMiceSink *sink = ND_WFD_MICE_SINK (object);
+
+ g_debug ("NdWFDMiceSink: Finalizing");
+
+ nd_wfd_mice_sink_sink_stop_stream_int (sink);
+
+ g_cancellable_cancel (sink->cancellable);
+ g_clear_object (&sink->cancellable);
+ g_clear_object (&sink->signalling_client);
+
+ g_clear_pointer (&sink->missing_video_codec, g_strfreev);
+ g_clear_pointer (&sink->missing_audio_codec, g_strfreev);
+ g_clear_pointer (&sink->missing_firewall_zone, g_free);
+
+ G_OBJECT_CLASS (nd_wfd_mice_sink_parent_class)->finalize (object);
+}
+
+static void
+nd_wfd_mice_sink_class_init (NdWFDMiceSinkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = nd_wfd_mice_sink_get_property;
+ object_class->set_property = nd_wfd_mice_sink_set_property;
+ object_class->finalize = nd_wfd_mice_sink_finalize;
+
+ props[PROP_CLIENT] =
+ g_param_spec_object ("client", "Signalling Client",
+ "The GSocketClient used for MICE signalling.",
+ G_TYPE_SOCKET_CLIENT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_NAME] =
+ g_param_spec_string ("name", "Sink Name",
+ "The sink name found by the Avahi Client.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_ADDRESS] =
+ g_param_spec_string ("address", "Sink Address",
+ "The address the sink was found on.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, PROP_LAST, props);
+
+ g_object_class_override_property (object_class, PROP_DISPLAY_NAME, "display-name");
+ g_object_class_override_property (object_class, PROP_MATCHES, "matches");
+ g_object_class_override_property (object_class, PROP_PRIORITY, "priority");
+ g_object_class_override_property (object_class, PROP_STATE, "state");
+ g_object_class_override_property (object_class, PROP_MISSING_VIDEO_CODEC, "missing-video-codec");
+ g_object_class_override_property (object_class, PROP_MISSING_AUDIO_CODEC, "missing-audio-codec");
+ g_object_class_override_property (object_class, PROP_MISSING_FIREWALL_ZONE, "missing-firewall-zone");
+}
+
+static void
+nd_wfd_mice_sink_init (NdWFDMiceSink *sink)
+{
+ sink->state = ND_SINK_STATE_DISCONNECTED;
+ sink->cancellable = g_cancellable_new ();
+ sink->signalling_client = g_socket_client_new ();
+}
+
+/******************************************************************
+* NdSink interface implementation
+******************************************************************/
+
+static void
+nd_wfd_mice_sink_sink_iface_init (NdSinkIface *iface)
+{
+ iface->start_stream = nd_wfd_mice_sink_sink_start_stream;
+ iface->stop_stream = nd_wfd_mice_sink_sink_stop_stream;
+}
+
+static void
+play_request_cb (NdWFDMiceSink *sink, GstRTSPContext *ctx, WfdClient *client)
+{
+ g_debug ("NdWFDMiceSink: Got play request from client");
+
+ sink->state = ND_SINK_STATE_STREAMING;
+ g_object_notify (G_OBJECT (sink), "state");
+}
+
+gboolean
+signalling_client_send (NdWFDMiceSink * self,
+ const void * message,
+ gssize size,
+ GCancellable * cancellable,
+ GError * error)
+{
+ GOutputStream * ostream;
+
+ if (self->signalling_client == NULL)
+ self->signalling_client = g_socket_client_new ();
+
+ if (self->signalling_client_conn == NULL)
+ {
+ self->signalling_client_conn = g_socket_client_connect_to_host (self->signalling_client,
+ (gchar *) self->remote_address,
+ 7250,
+ NULL,
+ &error);
+ }
+
+ if (!self->signalling_client_conn || error != NULL)
+ {
+ if (error != NULL)
+ g_warning ("NdWFDMiceSink: Failed to write to signalling stream: %s", error->message);
+
+ return FALSE;
+
+ }
+
+ g_assert (G_IO_STREAM (self->signalling_client_conn));
+
+ g_debug ("NdWFDMiceSink: Client connection established");
+
+ ostream = g_io_stream_get_output_stream (G_IO_STREAM (self->signalling_client_conn));
+ if (!ostream)
+ {
+ g_warning ("NdWFDMiceSink: Could not get output stream");
+
+ return FALSE;
+ }
+
+ size = g_output_stream_write (ostream, message, size, cancellable, &error);
+ if (error != NULL)
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ {
+ g_warning ("NdWFDMiceSink: Signalling client socket send would block");
+ return FALSE;
+ }
+ else
+ {
+ g_warning ("NdWFDMiceSink: Error writing to client socket output stream: %s", error->message);
+ return FALSE;
+ }
+ }
+
+ g_debug ("NdWFDMiceSink: Sent %" G_GSSIZE_FORMAT " bytes of data", size);
+
+ return TRUE;
+}
+
+static void
+closed_cb (NdWFDMiceSink *sink, WfdClient *client)
+{
+ g_autoptr(GError) error = NULL;
+
+ /* Connection was closed, do a clean shutdown*/
+ nd_wfd_mice_sink_sink_stop_stream (ND_SINK (sink));
+}
+
+static void
+client_connected_cb (NdWFDMiceSink *sink, WfdClient *client, WfdServer *server)
+{
+ g_debug ("NdWFDMiceSink: Got client connection");
+
+ g_signal_handlers_disconnect_by_func (sink->server, client_connected_cb, sink);
+ sink->state = ND_SINK_STATE_WAIT_STREAMING;
+ g_object_notify (G_OBJECT (sink), "state");
+
+ /* XXX: connect to further events. */
+ g_signal_connect_object (client,
+ "play-request",
+ (GCallback) play_request_cb,
+ sink,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (client,
+ "closed",
+ (GCallback) closed_cb,
+ sink,
+ G_CONNECT_SWAPPED);
+}
+
+static GstElement *
+server_create_source_cb (NdWFDMiceSink *sink, WfdServer *server)
+{
+ GstElement *res;
+
+ g_signal_emit_by_name (sink, "create-source", &res);
+ g_debug ("NdWFDMiceSink: Create source signal emitted");
+ return res;
+}
+
+static GstElement *
+server_create_audio_source_cb (NdWFDMiceSink *sink, WfdServer *server)
+{
+ GstElement *res;
+
+ g_signal_emit_by_name (sink, "create-audio-source", &res);
+ g_debug ("NdWFDMiceSink: Create audio source signal emitted");
+
+ return res;
+}
+
+static NdSink *
+nd_wfd_mice_sink_sink_start_stream (NdSink *sink)
+{
+ g_autoptr(GError) error = NULL;
+ NdWFDMiceSink *self = ND_WFD_MICE_SINK (sink);
+ gboolean have_basic_codecs, send_ok;
+ GStrv missing_video, missing_audio;
+
+ g_return_val_if_fail (self->state == ND_SINK_STATE_DISCONNECTED, NULL);
+
+ g_assert (self->server == NULL);
+
+ have_basic_codecs = wfd_get_missing_codecs (&missing_video, &missing_audio);
+
+ g_clear_pointer (&self->missing_video_codec, g_strfreev);
+ g_clear_pointer (&self->missing_audio_codec, g_strfreev);
+
+ self->missing_video_codec = g_strdupv (missing_video);
+ self->missing_audio_codec = g_strdupv (missing_audio);
+
+ g_object_notify (G_OBJECT (self), "missing-video-codec");
+ g_object_notify (G_OBJECT (self), "missing-audio-codec");
+
+ if (!have_basic_codecs)
+ {
+ g_warning ("Essential codecs are missing!");
+ self->state = ND_SINK_STATE_ERROR;
+ g_object_notify (G_OBJECT (self), "state");
+
+ return g_object_ref (sink);
+ }
+
+ g_assert (self->server == NULL);
+ self->server = wfd_server_new ();
+ self->server_source_id = gst_rtsp_server_attach (GST_RTSP_SERVER (self->server), NULL);
+
+ if (self->server_source_id == 0 || self->remote_address == NULL)
+ {
+ self->state = ND_SINK_STATE_ERROR;
+ g_object_notify (G_OBJECT (self), "state");
+ g_clear_object (&self->server);
+
+ return g_object_ref (sink);
+ }
+
+ g_signal_connect_object (self->server,
+ "client-connected",
+ (GCallback) client_connected_cb,
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->server,
+ "create-source",
+ (GCallback) server_create_source_cb,
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->server,
+ "create-audio-source",
+ (GCallback) server_create_audio_source_cb,
+ self,
+ G_CONNECT_SWAPPED);
+
+ self->state = ND_SINK_STATE_WAIT_SOCKET;
+ g_object_notify (G_OBJECT (self), "state");
+
+ send_ok = signalling_client_send (self,
+ msg_source_ready,
+ sizeof (msg_source_ready),
+ NULL,
+ error);
+ if (!send_ok || error != NULL)
+ {
+ if (error != NULL)
+ g_warning ("NdWFDMiceSink: Failed to create MICE client: %s", error->message);
+ else
+ g_warning ("NdWFDMiceSink: Failed to create MICE client");
+
+ self->state = ND_SINK_STATE_ERROR;
+ g_object_notify (G_OBJECT (self), "state");
+ g_clear_object (&self->server);
+ }
+
+ return g_object_ref (sink);
+}
+
+static void
+nd_wfd_mice_sink_sink_stop_stream_int (NdWFDMiceSink *self)
+{
+ GError *error = NULL;
+ gboolean close_ok;
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+
+ self->cancellable = g_cancellable_new ();
+
+ /* Close the client connection */
+ if (self->signalling_client_conn != NULL)
+ {
+ close_ok = g_io_stream_close (G_IO_STREAM (self->signalling_client_conn), NULL, &error);
+ if (error != NULL)
+ g_warning ("NdWFDMiceSink: Error closing signalling client connection: %s", error->message);
+ if (!close_ok)
+ g_warning ("NdWFDMiceSink: Signalling client connection not closed");
+
+ g_clear_object (&self->signalling_client_conn);
+ g_debug ("NdWFDMiceSink: Client connection removed");
+ }
+
+ /* Destroy the server that is streaming. */
+ if (self->server_source_id)
+ {
+ g_source_remove (self->server_source_id);
+ self->server_source_id = 0;
+ }
+
+ /* Needs to protect against recursion. */
+ if (self->server)
+ {
+ g_autoptr(WfdServer) server = NULL;
+
+ server = g_steal_pointer (&self->server);
+ g_signal_handlers_disconnect_by_data (server, self);
+ wfd_server_purge (server);
+ }
+}
+
+static void
+nd_wfd_mice_sink_sink_stop_stream (NdSink *sink)
+{
+ NdWFDMiceSink *self = ND_WFD_MICE_SINK (sink);
+
+ nd_wfd_mice_sink_sink_stop_stream_int (self);
+
+ self->state = ND_SINK_STATE_DISCONNECTED;
+ g_object_notify (G_OBJECT (self), "state");
+}
+
+/******************************************************************
+* NdWFDMiceSink public functions
+******************************************************************/
+
+NdWFDMiceSink *
+nd_wfd_mice_sink_new (gchar *name,
+ gchar *remote_address)
+{
+ return g_object_new (ND_TYPE_WFD_MICE_SINK,
+ "name", name,
+ "address", remote_address,
+ NULL);
+}
+
+NdSinkState
+nd_wfd_mice_sink_get_state (NdWFDMiceSink *sink)
+{
+ return sink->state;
+}
+
+GSocketClient *
+nd_wfd_mice_sink_get_signalling_client (NdWFDMiceSink *sink)
+{
+ return sink->signalling_client;
+}
\ No newline at end of file
diff --git a/src/nd-wfd-mice-sink.h b/src/nd-wfd-mice-sink.h
new file mode 100644
index 0000000..4b4d45f
--- /dev/null
+++ b/src/nd-wfd-mice-sink.h
@@ -0,0 +1,37 @@
+/* nd-wfd-mice-sink.h
+ *
+ * Copyright 2022 Christian Glombek <lorbus fedoraproject org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include "nd-sink.h"
+
+G_BEGIN_DECLS
+
+#define ND_TYPE_WFD_MICE_SINK (nd_wfd_mice_sink_get_type ())
+G_DECLARE_FINAL_TYPE (NdWFDMiceSink, nd_wfd_mice_sink, ND, WFD_MICE_SINK, GObject)
+
+NdWFDMiceSink * nd_wfd_mice_sink_new (gchar * name,
+ gchar * remote_address);
+
+NdSinkState nd_wfd_mice_sink_get_state (NdWFDMiceSink *sink);
+
+GSocketClient * nd_wfd_mice_sink_get_signalling_client (NdWFDMiceSink *sink);
+
+G_END_DECLS
diff --git a/src/nd-window.c b/src/nd-window.c
index e42da88..7a155f5 100644
--- a/src/nd-window.c
+++ b/src/nd-window.c
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <avahi-gobject/ga-client.h>
+#include <avahi-gobject/ga-service-browser.h>
#include <glib/gi18n.h>
#include "gnome-network-displays-config.h"
#include "nd-window.h"
@@ -25,6 +27,7 @@
#include "nd-meta-provider.h"
#include "nd-nm-device-registry.h"
#include "nd-dummy-provider.h"
+#include "nd-wfd-mice-provider.h"
#include <gst/gst.h>
@@ -35,6 +38,7 @@ struct _NdWindow
{
GtkApplicationWindow parent_instance;
+ GaClient *avahi_client;
NdMetaProvider *meta_provider;
NdNMDeviceRegistry *nm_device_registry;
@@ -315,6 +319,39 @@ find_sink_list_row_activated_cb (NdWindow *self, NdSinkRow *row, NdSinkList *sin
GTK_WIDGET (nd_sink_row_new (self->stream_sink)));
}
+static void
+gnome_nd_window_constructed (GObject *obj)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(NdWFDMiceProvider) mice_provider = NULL;
+ NdWindow *self = ND_WINDOW (obj);
+
+ self->cancellable = g_cancellable_new ();
+ self->avahi_client = ga_client_new (GA_CLIENT_FLAG_NO_FLAGS);
+
+
+ if (!ga_client_start (self->avahi_client, &error))
+ {
+ g_warning ("NdWindow: Failed to start Avahi Client");
+ if (error != NULL)
+ g_warning ("NdWindow: Error: %s", error->message);
+ return;
+ }
+
+ g_debug ("NdWindow: Got avahi client");
+
+ mice_provider = nd_wfd_mice_provider_new (self->avahi_client);
+
+ if (!nd_wfd_mice_provider_browse (mice_provider, error))
+ {
+ g_warning ("NdWindow: Avahi client failed to browse: %s", error->message);
+ return;
+ }
+
+ g_debug ("NdWindow: Got avahi browser");
+ nd_meta_provider_add_provider (self->meta_provider, ND_PROVIDER (mice_provider));
+}
+
static void
gnome_nd_window_finalize (GObject *obj)
{
@@ -330,19 +367,32 @@ gnome_nd_window_finalize (GObject *obj)
g_clear_object (&self->meta_provider);
g_clear_object (&self->nm_device_registry);
+ g_clear_object (&self->avahi_client);
g_clear_pointer (&self->sink_property_bindings, g_ptr_array_unref);
G_OBJECT_CLASS (gnome_nd_window_parent_class)->finalize (obj);
}
+static void
+gnome_nd_window_dispose (GObject *obj)
+{
+ NdWindow *self = ND_WINDOW (obj);
+
+ g_object_run_dispose (G_OBJECT (self->avahi_client));
+
+ G_OBJECT_CLASS (gnome_nd_window_parent_class)->dispose (obj);
+}
+
static void
gnome_nd_window_class_init (NdWindowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ object_class->constructed = gnome_nd_window_constructed;
object_class->finalize = gnome_nd_window_finalize;
+ object_class->dispose = gnome_nd_window_dispose;
ND_TYPE_SINK_LIST;
ND_TYPE_CODEC_INSTALL;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]