[gnome-network-displays] Add Miracast over Infrastructure (MICE) support



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]