[gnome-network-displays/cc-tmp: 1/80] cc: Add Chromecast support
- From: Benjamin Berg <bberg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-network-displays/cc-tmp: 1/80] cc: Add Chromecast support
- Date: Fri, 9 Sep 2022 12:03:48 +0000 (UTC)
commit 3f242d359569683506a8ab166ffdab320d52204a
Author: Anupam Kumar <kyteinsky gmail com>
Date: Fri Jul 1 19:13:14 2022 +0530
cc: Add Chromecast support
Initial Commit
src/cc/cc-client.c | 645 +++++++++++++++++++++++++++++++++++++++++++++++++++
src/cc/cc-client.h | 21 ++
src/meson.build | 2 +
src/nd-cc-provider.c | 412 ++++++++++++++++++++++++++++++++
src/nd-cc-provider.h | 39 ++++
src/nd-cc-sink.c | 575 +++++++++++++++++++++++++++++++++++++++++++++
src/nd-cc-sink.h | 36 +++
src/nd-window.c | 6 +-
8 files changed, 1735 insertions(+), 1 deletion(-)
---
diff --git a/src/cc/cc-client.c b/src/cc/cc-client.c
new file mode 100644
index 0000000..e990cdd
--- /dev/null
+++ b/src/cc/cc-client.c
@@ -0,0 +1,645 @@
+#include <glib-object.h>
+#include <gst/rtsp/gstrtspmessage.h>
+#include <gst/video/video.h>
+#include "cc-client.h"
+#include "wfd-media-factory.h"
+#include "wfd-media.h"
+#include "wfd-params.h"
+
+typedef enum {
+ INIT_STATE_M0_INVALID = 0,
+ INIT_STATE_M1_SOURCE_QUERY_OPTIONS = 1,
+ INIT_STATE_M2_SINK_QUERY_OPTIONS = 2,
+ INIT_STATE_M3_SOURCE_GET_PARAMS = 3,
+ INIT_STATE_M4_SOURCE_SET_PARAMS = 4,
+ INIT_STATE_M5_SOURCE_TRIGGER_SETUP = 5,
+
+ INIT_STATE_DONE = 9999,
+} CCClientInitState;
+
+typedef enum {
+ CONNECTION_TYPE_WEBRTC = 0,
+ CONNECTION_TYPE_RTSP = 1
+} CCConnectionType;
+
+struct _CCClient
+{
+ GstRTSPClient parent_instance;
+
+ CCConnectionType connection_type;
+ guint keep_alive_source_id;
+
+ CCClientInitState init_state;
+ WfdMedia *media;
+ WfdParams *params;
+
+ WfdMediaQuirks media_quirks;
+};
+
+G_DEFINE_TYPE (CCClient, cc_client, GST_TYPE_RTSP_CLIENT)
+
+// XXX
+static const gchar * supported_rtsp_features[] = {
+ "org.wfa.wfd1.0",
+ "OPTIONS",
+ "DESCRIBE",
+ "GET_PARAMETER",
+ "PAUSE",
+ "PLAY",
+ "SETUP",
+ "SET_PARAMETER",
+ "TEARDOWN",
+ NULL
+};
+
+CCClient *
+cc_client_new (void)
+{
+ return g_object_new (CC_TYPE_CLIENT, NULL);
+}
+
+static void
+cc_client_finalize (GObject *object)
+{
+ CCClient *self = (CCClient *) object;
+
+ g_debug ("CCClient: Finalize");
+
+ g_clear_pointer (&self->params, wfd_params_free);
+
+ if (self->keep_alive_source_id)
+ g_source_remove (self->keep_alive_source_id);
+ self->keep_alive_source_id = 0;
+
+ G_OBJECT_CLASS (cc_client_parent_class)->finalize (object);
+}
+
+gchar *
+cc_client_check_requirements (GstRTSPClient *client, GstRTSPContext *ctx, gchar ** arr)
+{
+ g_autoptr(GPtrArray) unsupported = NULL;
+ gchar **req;
+ char *res = NULL;
+
+ for (req = arr; *req; req++)
+ {
+ if (!g_strv_contains (supported_rtsp_features, *req))
+ {
+ if (unsupported == NULL)
+ unsupported = g_ptr_array_new ();
+ g_ptr_array_add (unsupported, *req);
+ }
+ }
+
+ if (!unsupported)
+ return g_strdup ("");
+
+ res = g_strjoinv (", ", (GStrv) unsupported->pdata);
+ g_warning ("CCClient: Cannot support the following requested features: %s", res);
+
+ return res;
+}
+
+gint
+compare_resolutions (gconstpointer a, gconstpointer b)
+{
+ WfdResolution *res_a = (WfdResolution *) a;
+ WfdResolution *res_b = (WfdResolution *) b;
+ gint a_weight;
+ gint b_weight;
+
+ a_weight = res_a->width * res_a->height * 100 + res_a->refresh_rate / (res_a->interlaced ? 2 : 1) -
res_a->interlaced;
+ b_weight = res_b->width * res_b->height * 100 + res_b->refresh_rate / (res_b->interlaced ? 2 : 1) -
res_b->interlaced;
+ return a_weight - b_weight;
+}
+
+void
+cc_client_select_codec_and_resolution (CCClient *self, WfdH264ProfileFlags profile)
+{
+ gint i;
+ WfdVideoCodec *codec = NULL;
+
+ for (i = 0; i < self->params->video_codecs->len; i++)
+ {
+ WfdVideoCodec *item = g_ptr_array_index (self->params->video_codecs, i);
+
+ /* Use the first codec we can find. */
+ if (!codec)
+ codec = item;
+
+ if (codec->profile != item->profile && item->profile == profile)
+ codec = item;
+
+ if (codec->profile == item->profile && item->level > codec->level)
+ codec = item;
+ }
+
+ if (codec)
+ self->params->selected_codec = wfd_video_codec_ref (codec);
+ else
+ g_warning ("No codec/resolution could be found, falling back to defaults!");
+
+#if 0
+ /* The native resolution reported by some devices is just useless */
+ if (codec->native)
+ {
+ self->params->selected_resolution = wfd_resolution_copy (codec->native);
+ }
+ else
+ {
+ /* Find a good resolution. */
+ g_autoptr(GList) resolutions = NULL;
+ GList *last;
+
+ resolutions = wfd_video_codec_get_resolutions (codec);
+ resolutions = g_list_sort (resolutions, compare_resolutions);
+ last = g_list_last (resolutions);
+ if (last)
+ {
+ self->params->selected_resolution = wfd_resolution_copy ((WfdResolution *) last->data);
+ }
+ else
+ {
+#endif
+ /* Create a standard full HD resolution if everything fails. */
+ g_warning ("CCClient: No resolution found, falling back to standard FullHD resolution.");
+ self->params->selected_resolution = wfd_resolution_new ();
+ self->params->selected_resolution->width = 1920;
+ self->params->selected_resolution->height = 1080;
+ self->params->selected_resolution->refresh_rate = 30;
+ self->params->selected_resolution->interlaced = FALSE;
+#if 0
+}
+}
+#endif
+ g_debug ("selected resolution %i, %i @%i", self->params->selected_resolution->width,
self->params->selected_resolution->height, self->params->selected_resolution->refresh_rate);
+
+ /* We currently only support AAC with two channels */
+ for (i = 0; i < self->params->audio_codecs->len; i++)
+ {
+ WfdAudioCodec *codec = g_ptr_array_index (self->params->audio_codecs, i);
+
+ /* Accept AAC with 48KHz and 2 channels; this is currently hardcoded in the media factory */
+ if (codec->type == WFD_AUDIO_AAC && codec->modes & 0x1)
+ {
+ self->params->selected_audio_codec = wfd_audio_codec_new ();
+ self->params->selected_audio_codec->type = WFD_AUDIO_AAC;
+ self->params->selected_audio_codec->modes = G_GUINT64_CONSTANT (0x00000001);
+ }
+ }
+}
+
+gboolean
+cc_client_configure_client_media (GstRTSPClient * client,
+ GstRTSPMedia * media, GstRTSPStream * stream,
+ GstRTSPContext * ctx)
+{
+ CCClient *self = CC_CLIENT (client);
+
+ g_autoptr(GstElement) element = NULL;
+ gboolean res;
+
+ g_return_val_if_fail (self->params->selected_codec, FALSE);
+ g_return_val_if_fail (self->params->selected_resolution, FALSE);
+
+ self->media = WFD_MEDIA (media);
+
+ element = gst_rtsp_media_get_element (media);
+ self->media_quirks = wfd_configure_media_element (GST_BIN (element), self->params);
+
+ res = GST_RTSP_CLIENT_CLASS (cc_client_parent_class)->configure_client_media (client, media, stream, ctx);
+
+ return res;
+}
+
+static gboolean
+cc_client_idle_trigger_setup (gpointer user_data)
+{
+ cc_client_trigger_method (CC_CLIENT (user_data), "SETUP");
+ g_object_unref (user_data);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gchar *
+cc_client_get_presentation_uri (CCClient *self)
+{
+ g_autoptr(GSocketAddress) sock_addr = NULL;
+ GstRTSPClient *client = GST_RTSP_CLIENT (self);
+ GstRTSPConnection *connection;
+ GSocket *socket;
+ GInetAddress *inet_addr;
+ g_autofree gchar *addr = NULL;
+ gint port;
+
+ connection = gst_rtsp_client_get_connection (client);
+ socket = gst_rtsp_connection_get_read_socket (connection);
+ sock_addr = g_socket_get_local_address (socket, NULL);
+ inet_addr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sock_addr));
+ addr = g_inet_address_to_string (inet_addr);
+ port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sock_addr));
+
+ // XXX
+ return g_strdup_printf ("rtsp://%s:%d/wfd1.0/streamid=0", addr, port);
+}
+
+static void
+cc_client_set_params (CCClient *self)
+{
+ GstRTSPMessage msg = { 0 };
+ g_autofree gchar * body = NULL;
+ g_autofree gchar * presentation_uri = NULL;
+ g_autofree gchar * resolution_descr = NULL;
+ g_autofree gchar * audio_descr = NULL;
+
+ self->init_state = INIT_STATE_M4_SOURCE_SET_PARAMS;
+
+ gst_rtsp_message_init_request (&msg, GST_RTSP_SET_PARAMETER, "rtsp://localhost/wfd1.0");
+
+ presentation_uri = cc_client_get_presentation_uri (self);
+ resolution_descr = wfd_video_codec_get_descriptor_for_resolution (self->params->selected_codec,
self->params->selected_resolution);
+ audio_descr = wfd_audio_get_descriptor (self->params->selected_audio_codec);
+
+ body = g_strdup_printf (
+ "wfd_video_formats: %s\r\n"
+ "wfd_audio_codecs: %s\r\n"
+ "wfd_presentation_URL: %s none\r\n"
+ "wfd_client_rtp_ports: RTP/AVP/UDP;unicast %u %u mode=play\r\n",
+ resolution_descr,
+ audio_descr,
+ presentation_uri,
+ self->params->primary_rtp_port, self->params->secondary_rtp_port);
+
+ gst_rtsp_message_add_header_by_name (&msg, "Content-Type", "text/parameters");
+ gst_rtsp_message_set_body (&msg, (guint8 *) body, strlen (body));
+
+ gst_rtsp_client_send_message (GST_RTSP_CLIENT (self), NULL, &msg);
+
+ gst_rtsp_message_unset (&msg);
+}
+
+static gboolean
+cc_client_idle_set_params (gpointer user_data)
+{
+ cc_client_set_params (CC_CLIENT (user_data));
+ g_object_unref (user_data);
+
+ return G_SOURCE_REMOVE;
+}
+
+GstRTSPFilterResult
+cc_client_touch_session_filter_func (GstRTSPClient *client,
+ GstRTSPSession *sess,
+ gpointer user_data)
+{
+ gst_rtsp_session_touch (sess);
+
+ return GST_RTSP_FILTER_KEEP;
+}
+
+void
+cc_client_handle_response (GstRTSPClient * client, GstRTSPContext *ctx)
+{
+ CCClient *self = CC_CLIENT (client);
+
+ /* Some sinks do not reply with the correct session-id. Which causes
+ * gst-rtsp-server to not touch the session, triggering a timeout
+ * even though the sink actually replied.
+ *
+ * Work around this by explicitly touching the session (again). And
+ * to do that, just touch all of them, which is acceptable as we will
+ * only have one.
+ */
+ gst_rtsp_client_session_filter (client, cc_client_touch_session_filter_func, NULL);
+
+ /* Track the initialization process and possibly trigger the
+ * next state of the connection establishment. */
+ switch (self->init_state)
+ {
+ case INIT_STATE_M1_SOURCE_QUERY_OPTIONS:
+ g_debug ("CCClient: OPTIONS querying done");
+ self->init_state = INIT_STATE_M2_SINK_QUERY_OPTIONS;
+ break;
+
+ case INIT_STATE_M3_SOURCE_GET_PARAMS:
+ g_debug ("CCClient: GET_PARAMS done");
+ wfd_params_from_sink (self->params, ctx->response->body, ctx->response->body_size);
+
+ /* XXX: Pick the better profile if we have an encoder that supports it! */
+ cc_client_select_codec_and_resolution (self, WFD_H264_PROFILE_BASE);
+
+ g_idle_add (cc_client_idle_set_params, g_object_ref (self));
+ break;
+
+ case INIT_STATE_M4_SOURCE_SET_PARAMS:
+ g_debug ("CCClient: SET_PARAMS done");
+ g_idle_add (cc_client_idle_trigger_setup, g_object_ref (self));
+ break;
+
+ case INIT_STATE_M5_SOURCE_TRIGGER_SETUP:
+ self->init_state = INIT_STATE_DONE;
+ g_debug ("CCClient: Initialization done!");
+ break;
+
+ default:
+ /* Nothing to be done in the other states. */
+ break;
+ }
+}
+
+static gchar *
+cc_client_make_path_from_uri (GstRTSPClient * client, const GstRTSPUrl * uri)
+{
+ GstRTSPContext *ctx = gst_rtsp_context_get_current ();
+
+ /* Strip /streamid=0.
+ * This is a bad hack, because gstreamer does not support playing/pausing
+ * a specific stream. We can do so safely because we only have one stream.
+ */
+ if (ctx->request &&
+ (ctx->request->type_data.request.method == GST_RTSP_PLAY ||
+ ctx->request->type_data.request.method == GST_RTSP_PAUSE))
+ {
+ if (g_str_has_suffix (uri->abspath, "/streamid=0"))
+ return g_strndup (uri->abspath, strlen (uri->abspath) - 11);
+ else
+ return g_strdup (uri->abspath);
+ }
+ else
+ {
+ return GST_RTSP_CLIENT_CLASS (cc_client_parent_class)->make_path_from_uri (client, uri);
+ }
+}
+
+GstRTSPFilterResult
+cc_client_timeout_session_filter_func (GstRTSPClient *client,
+ GstRTSPSession *sess,
+ gpointer user_data)
+{
+ GstRTSPMessage msg = { 0 };
+
+ g_debug ("CCClient: Doing keep-alive");
+
+ gst_rtsp_message_init_request (&msg, GST_RTSP_GET_PARAMETER, "rtsp://localhost/wfd1.0/streamid=0");
+
+ gst_rtsp_client_send_message (client, sess, &msg);
+
+ gst_rtsp_message_unset (&msg);
+
+ return GST_RTSP_FILTER_KEEP;
+}
+
+static gboolean
+cc_client_keep_alive_timeout (gpointer user_data)
+{
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+
+ gst_rtsp_client_session_filter (client, cc_client_timeout_session_filter_func, NULL);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+cc_client_new_session (GstRTSPClient *client, GstRTSPSession *session)
+{
+ CCClient *self = CC_CLIENT (client);
+
+ // XXX: look into this
+ /* The WFD standard suggests a timeout of 30 seconds */
+ gst_rtsp_session_set_timeout (session, 30);
+ g_object_set (session, "timeout-always-visible", FALSE, NULL);
+
+ // XXX: this too
+ if (self->connection_type == CONNECTION_TYPE_WEBRTC && self->keep_alive_source_id == 0)
+ self->keep_alive_source_id = g_timeout_add_seconds (25, cc_client_keep_alive_timeout, client);
+}
+
+static GstRTSPResult
+cc_client_params_set (GstRTSPClient *client, GstRTSPContext *ctx)
+{
+ CCClient *self = CC_CLIENT (client);
+ g_autofree gchar *body_str = NULL;
+
+ g_auto(GStrv) lines = NULL;
+ gchar **line = NULL;
+
+ gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
+ gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
+
+ if (ctx->request->body == NULL || ctx->request->body_size == 0)
+ return GST_RTSP_OK;
+
+ body_str = g_strndup ((gchar *) ctx->request->body, ctx->request->body_size);
+ lines = g_strsplit (body_str, "\n", 0);
+
+ for (line = lines; *line; line++)
+ {
+ g_auto(GStrv) split_line = NULL;
+ gchar *option;
+ G_GNUC_UNUSED gchar *value;
+
+ g_strstrip (*line);
+
+ /* Ignore empty lines */
+ if (**line == '\0')
+ continue;
+
+ split_line = g_strsplit (*line, ":", 2);
+
+ option = g_strstrip (split_line[0]);
+ if (split_line[1])
+ value = g_strstrip (split_line[1]);
+ else
+ value = NULL;
+
+ if (g_str_equal (option, "wfd_idr_request"))
+ {
+ /* Force a key unit event. */
+ if (self->media_quirks & WFD_QUIRK_NO_IDR)
+ {
+ g_debug ("Cannot force key frame as the pipeline doesn't support it!");
+ }
+ else if (self->media)
+ {
+ GstRTSPStream *stream;
+ g_autoptr(GstPad) srcpad = NULL;
+ g_autoptr(GstEvent) event = NULL;
+
+ stream = gst_rtsp_media_get_stream (GST_RTSP_MEDIA (self->media), 0);
+ if (!stream)
+ return GST_RTSP_OK;
+
+ srcpad = gst_rtsp_stream_get_srcpad (stream);
+
+ g_debug ("Forcing a keyframe!");
+ event = gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE, TRUE, 0);
+ gst_pad_send_event (srcpad, g_steal_pointer (&event));
+ }
+ else
+ {
+ g_debug ("Cannot force key frame currently, no media!");
+ }
+ }
+ else
+ {
+ g_debug ("Ignoring unknown parameter %s", option);
+ }
+ }
+
+ return GST_RTSP_OK;
+}
+
+static gboolean
+cc_client_idle_cc_query_params (gpointer user_data)
+{
+ CCClient *self = CC_CLIENT (user_data);
+ GstRTSPMessage msg = { 0 };
+ g_autofree gchar * query_params = NULL;
+
+ g_debug ("CC query params");
+
+ self->init_state = INIT_STATE_M3_SOURCE_GET_PARAMS;
+
+ gst_rtsp_message_init_request (&msg, GST_RTSP_GET_PARAMETER, "rtsp://localhost/wfd1.0");
+ gst_rtsp_message_add_header_by_name (&msg, "Content-Type", "text/parameters");
+ query_params = wfd_params_m3_query_params (self->params);
+ gst_rtsp_message_set_body (&msg, (guint8 *) query_params, strlen (query_params));
+
+ gst_rtsp_client_send_message (GST_RTSP_CLIENT (self), NULL, &msg);
+
+ gst_rtsp_message_unset (&msg);
+ g_object_unref (user_data);
+
+ return G_SOURCE_REMOVE;
+}
+
+GstRTSPStatusCode
+cc_client_pre_options_request (GstRTSPClient *client, GstRTSPContext *ctx)
+{
+ CCClient *self = CC_CLIENT (client);
+
+ if (self->init_state <= INIT_STATE_M2_SINK_QUERY_OPTIONS)
+ {
+ if (self->init_state != INIT_STATE_M2_SINK_QUERY_OPTIONS)
+ {
+ g_message ("CCClient: Got OPTIONS before getting reply querying WFD support; assuming normal RTSP
connection.");
+ /* The standard says to disconnect. However, if we do this,
+ * then it is possible to connect a normal RTSP client for testing.
+ * e.g. VLC will play back the stream correctly.
+ *
+ * Also flag the connection as "normal" RTSP and set a selected audio codec.
+ */
+ self->connection_type = CONNECTION_TYPE_RTSP;
+
+ /* Enable audio with AAC and 2 channels (48kHz), currently hardcoded in the media factory*/
+ self->params->selected_audio_codec = wfd_audio_codec_new ();
+ self->params->selected_audio_codec->type = WFD_AUDIO_AAC;
+ self->params->selected_audio_codec->modes = G_GUINT64_CONSTANT (0x00000001);
+
+ self->init_state = INIT_STATE_DONE;
+ }
+ else
+ {
+ g_idle_add (cc_client_idle_cc_query_params, g_object_ref (client));
+ }
+ }
+
+ return GST_RTSP_STS_OK;
+}
+
+
+static void
+cc_client_send_message (GstRTSPClient *client, GstRTSPContext *ctx, GstRTSPMessage *msg)
+{
+ gchar *hdr = NULL;
+
+ /* Hook for sending a message. */
+
+ /* Modify the "Public" header to advertise support for WFD 1.0 */
+ gst_rtsp_message_get_header (msg, GST_RTSP_HDR_PUBLIC, &hdr, 0);
+ if (hdr)
+ {
+ g_autofree gchar * new_hdr = NULL;
+
+ new_hdr = g_strconcat ("org.wfa.wfd1.0, ", hdr, NULL);
+
+ gst_rtsp_message_remove_header (msg, GST_RTSP_HDR_PUBLIC, -1);
+ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_PUBLIC, new_hdr);
+ }
+
+ /* Strip away any ;timeout=30 from outgoing messages. This seems to be
+ * confuse some clients. */
+ gst_rtsp_message_get_header (msg, GST_RTSP_HDR_SESSION, &hdr, 0);
+ if (msg->type == GST_RTSP_MESSAGE_REQUEST && hdr)
+ {
+ if (g_str_has_suffix (hdr, ";timeout=30"))
+ {
+ g_autofree gchar * new_hdr = NULL;
+
+ new_hdr = g_strndup (hdr, strlen (hdr) - 11);
+ gst_rtsp_message_remove_header (msg, GST_RTSP_HDR_SESSION, -1);
+ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_SESSION, new_hdr);
+ }
+ }
+}
+
+static void
+cc_client_class_init (CCClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GstRTSPClientClass *client_class = GST_RTSP_CLIENT_CLASS (klass);
+
+ object_class->finalize = cc_client_finalize;
+
+ client_class->check_requirements = cc_client_check_requirements;
+ client_class->configure_client_media = cc_client_configure_client_media;
+ client_class->handle_response = cc_client_handle_response;
+ client_class->make_path_from_uri = cc_client_make_path_from_uri;
+ client_class->new_session = cc_client_new_session;
+ client_class->params_set = cc_client_params_set;
+ client_class->pre_options_request = cc_client_pre_options_request;
+ client_class->send_message = cc_client_send_message;
+}
+
+static void
+cc_client_init (CCClient *self)
+{
+ self->init_state = INIT_STATE_M0_INVALID;
+ self->params = wfd_params_new ();
+}
+
+void
+cc_client_query_support (CCClient *self)
+{
+ GstRTSPMessage msg = { 0 };
+
+ if (self->init_state != INIT_STATE_M0_INVALID)
+ return;
+
+ self->init_state = INIT_STATE_M1_SOURCE_QUERY_OPTIONS;
+ gst_rtsp_message_init_request (&msg, GST_RTSP_OPTIONS, "*");
+ gst_rtsp_message_add_header_by_name (&msg, "Require", "org.wfa.wfd1.0");
+
+ gst_rtsp_client_send_message (GST_RTSP_CLIENT (self), NULL, &msg);
+
+ gst_rtsp_message_unset (&msg);
+}
+
+void
+cc_client_trigger_method (CCClient *self, const gchar *method)
+{
+ GstRTSPMessage msg = { 0 };
+ g_autofree gchar *body = NULL;
+
+ if (g_str_equal (method, "SETUP") && self->init_state == INIT_STATE_M4_SOURCE_SET_PARAMS)
+ self->init_state = INIT_STATE_M5_SOURCE_TRIGGER_SETUP;
+
+ gst_rtsp_message_init_request (&msg, GST_RTSP_SET_PARAMETER, "rtsp://localhost/wfd1.0");
+ body = g_strdup_printf ("wfd_trigger_method: %s\r\n", method);
+ gst_rtsp_message_set_body (&msg, (guint8 *) body, strlen (body));
+ gst_rtsp_message_add_header_by_name (&msg, "Content-Type", "text/parameters");
+
+ gst_rtsp_client_send_message (GST_RTSP_CLIENT (self), NULL, &msg);
+
+ gst_rtsp_message_unset (&msg);
+}
diff --git a/src/cc/cc-client.h b/src/cc/cc-client.h
new file mode 100644
index 0000000..5a4c291
--- /dev/null
+++ b/src/cc/cc-client.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#include <gst/rtsp-server/rtsp-client.h>
+#pragma GCC diagnostic pop
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_CLIENT (cc_client_get_type ())
+#define CC_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_CLIENT, CCClientClass))
+
+G_DECLARE_FINAL_TYPE (CCClient, cc_client, CC, CLIENT, GstRTSPClient)
+
+CCClient * cc_client_new (void);
+void cc_client_query_support (CCClient *self);
+void cc_client_trigger_method (CCClient *self,
+ const gchar *method);
+
+
+G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index c50da56..13ddba5 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -14,6 +14,8 @@ gnome_nd_sources = [
'nd-meta-provider.c',
'nd-wfd-mice-sink.c',
'nd-wfd-mice-provider.c',
+ 'nd-cc-sink.c',
+ 'nd-cc-provider.c',
'nd-wfd-p2p-sink.c',
'nd-wfd-p2p-provider.c',
'nd-nm-device-registry.c',
diff --git a/src/nd-cc-provider.c b/src/nd-cc-provider.c
new file mode 100644
index 0000000..3e4de08
--- /dev/null
+++ b/src/nd-cc-provider.c
@@ -0,0 +1,412 @@
+/* nd-cc-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-cc-provider.h"
+#include "nd-sink.h"
+#include "nd-cc-sink.h"
+
+struct _NdCCProvider
+{
+ GObject parent_instance;
+
+ GPtrArray *sinks;
+ GaClient *avahi_client;
+
+ GSocketClient *signalling_client;
+ // GSocketService *signalling_server;
+
+ gboolean discover;
+};
+
+enum
+{
+ PROP_CLIENT = 1,
+
+ PROP_DISCOVER,
+
+ PROP_LAST = PROP_DISCOVER,
+};
+
+static void nd_cc_provider_provider_iface_init(NdProviderIface *iface);
+static GList *nd_cc_provider_provider_get_sinks(NdProvider *provider);
+
+G_DEFINE_TYPE_EXTENDED(NdCCProvider, nd_cc_provider, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE(ND_TYPE_PROVIDER,
+ nd_cc_provider_provider_iface_init);)
+
+static GParamSpec *props[PROP_LAST] = {
+ NULL,
+};
+
+static void
+nd_cc_provider_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NdCCProvider *provider = ND_CC_PROVIDER(object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ g_assert(provider->avahi_client == NULL);
+ 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_cc_provider_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NdCCProvider *provider = ND_CC_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_cc_provider_finalize(GObject *object)
+{
+ NdCCProvider *provider = ND_CC_PROVIDER(object);
+ GError *error;
+
+ g_clear_pointer(&provider->sinks, g_ptr_array_unref);
+
+ if (provider->signalling_client)
+ {
+ if (!g_socket_close((GSocket *)provider->signalling_client, &error))
+ g_warning("NdCCProvider: Error closing signalling client socket: %s", error->message);
+ g_object_unref(provider->signalling_client);
+ }
+
+ // if (provider->signalling_server)
+ // {
+ // if (!g_socket_close((GSocket *)provider->signalling_server, &error))
+ // g_warning("NdCCProvider: Error closing signalling server socket: %s", error->message);
+ // g_object_unref(provider->signalling_server);
+ // }
+
+ G_OBJECT_CLASS(nd_cc_provider_parent_class)->finalize(object);
+}
+
+static void
+nd_cc_provider_class_init(NdCCProviderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = nd_cc_provider_get_property;
+ object_class->set_property = nd_cc_provider_set_property;
+ object_class->finalize = nd_cc_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,
+ NdCCProvider *provider)
+{
+ NdCCSink *sink = NULL;
+ gchar address[AVAHI_ADDRESS_STR_MAX];
+
+ g_debug("NdCCProvider: Found sink %s at %s:%d on interface %i", name, hostname, port, iface);
+
+ if (avahi_address_snprint(address, sizeof(address), addr) == NULL)
+ g_warning("NdCCProvider: Failed to convert AvahiAddress to string");
+
+ g_debug("NdCCProvider: Resolved %s to %s", hostname, address);
+
+ sink = nd_cc_sink_new(provider->signalling_client, 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,
+ NdCCProvider *provider)
+{
+ g_warning("NdCCProvider: 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,
+ NdCCProvider *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("NdCCProvider: 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,
+ NdCCProvider *provider)
+{
+ g_debug("NdCCProvider: mDNS service \"%s\" removed from interface %i", name, iface);
+
+ for (gint i = 0; i < provider->sinks->len; i++)
+ {
+ g_autoptr(NdCCSink) sink = g_object_ref(g_ptr_array_index(provider->sinks, i));
+
+ NdSinkState state = nd_cc_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("NdCCProvider: Removing sink");
+ g_ptr_array_remove_index(provider->sinks, i);
+ g_signal_emit_by_name(provider, "sink-removed", sink);
+ break;
+ }
+ }
+}
+
+// // TODO: msg receiving callback
+// static void
+// signalling_incoming_cb(GSocketService *service,
+// GSocketConnection *connection,
+// GObject *source,
+// gpointer user_data)
+// {
+// /* TODO */
+// /* read full message, find sink, and respond with client */
+
+// /* NdCCProvider * self = ND_CC_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("NdCCProvider: Failed to connect to signalling host: %s", error->message);
+
+// g_debug("NdCCProvider: Received Message: %s", buffer);
+
+// return;
+// }
+
+// TODO: is GThreadedSocketService required?
+static void
+nd_cc_provider_init(NdCCProvider *provider)
+{
+ // g_autoptr(GError) error = NULL;
+ // GSocketService *server;
+
+ provider->discover = TRUE;
+ provider->sinks = g_ptr_array_new_with_free_func(g_object_unref);
+ provider->signalling_client = g_socket_client_new();
+ // server = g_socket_service_new();
+
+ // g_socket_listener_add_inet_port((GSocketListener *)server,
+ // 8009,
+ // NULL,
+ // &error);
+ // if (error != NULL)
+ // {
+ // g_warning("NdCCProvider: 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_cc_provider_provider_iface_init(NdProviderIface *iface)
+{
+ iface->get_sinks = nd_cc_provider_provider_get_sinks;
+}
+
+static GList *
+nd_cc_provider_provider_get_sinks(NdProvider *provider)
+{
+ NdCCProvider *cc_provider = ND_CC_PROVIDER(provider);
+ GList *res = NULL;
+
+ for (gint i = 0; i < cc_provider->sinks->len; i++)
+ res = g_list_prepend(res, g_ptr_array_index(cc_provider->sinks, i));
+
+ return res;
+}
+
+/******************************************************************
+ * NdCCProvider public functions
+ ******************************************************************/
+
+GaClient *
+nd_cc_provider_get_client(NdCCProvider *provider)
+{
+ return provider->avahi_client;
+}
+
+GSocketClient *
+nd_cc_provider_get_signalling_client(NdCCProvider *provider)
+{
+ return provider->signalling_client;
+}
+
+// GSocketService *
+// nd_cc_provider_get_signalling_server(NdCCProvider *provider)
+// {
+// return provider->signalling_server;
+// }
+
+NdCCProvider *
+nd_cc_provider_new(GaClient *client)
+{
+ return g_object_new(ND_TYPE_CC_PROVIDER,
+ "client", client,
+ NULL);
+}
+
+gboolean
+nd_cc_provider_browse(NdCCProvider *provider, GError *error)
+{
+ GaServiceBrowser *avahi_browser;
+
+ avahi_browser = ga_service_browser_new("_googlecast._tcp");
+
+ if (provider->avahi_client == NULL)
+ {
+ g_warning("NdCCProvider: 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("NdCCProvider: Failed to attach Avahi Service Browser: %s", error->message);
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/src/nd-cc-provider.h b/src/nd-cc-provider.h
new file mode 100644
index 0000000..924f5d6
--- /dev/null
+++ b/src/nd-cc-provider.h
@@ -0,0 +1,39 @@
+/* nd-cc-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_CC_PROVIDER (nd_cc_provider_get_type ())
+G_DECLARE_FINAL_TYPE (NdCCProvider, nd_cc_provider, ND, CC_PROVIDER, GObject)
+
+NdCCProvider * nd_cc_provider_new (GaClient * client);
+
+GaClient * nd_cc_provider_get_client (NdCCProvider *provider);
+
+GSocketClient * nd_cc_provider_get_signalling_client (NdCCProvider *provider);
+
+gboolean nd_cc_provider_browse (NdCCProvider * provider,
+ GError * error);
+
+G_END_DECLS
diff --git a/src/nd-cc-sink.c b/src/nd-cc-sink.c
new file mode 100644
index 0000000..f768ca1
--- /dev/null
+++ b/src/nd-cc-sink.c
@@ -0,0 +1,575 @@
+/* nd-cc-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-cc-sink.h"
+#include "cc/cc-client.h"
+#include "wfd/wfd-media-factory.h"
+#include "wfd/wfd-server.h"
+
+struct _NdCCSink
+{
+ 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 *comm_client;
+ GSocketConnection *comm_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,
+};
+
+// interface related functions
+static void nd_cc_sink_sink_iface_init (NdSinkIface *iface);
+static NdSink * nd_cc_sink_sink_start_stream (NdSink *sink);
+static void nd_cc_sink_sink_stop_stream (NdSink *sink);
+
+static void nd_cc_sink_sink_stop_stream_int (NdCCSink *self);
+
+G_DEFINE_TYPE_EXTENDED (NdCCSink, nd_cc_sink, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (ND_TYPE_SINK,
+ nd_cc_sink_sink_iface_init);
+ )
+
+static GParamSpec * props[PROP_LAST] = { NULL, };
+
+// TODO: this should be the protobuf msg
+// msg sent after connection
+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 bits) */
+ /* Source ID GnomeMICEDisplay (ascii) */
+ 0x47, 0x6E, 0x6F, 0x6D, 0x65, 0x4D, 0x49, 0x43, 0x45, 0x44, 0x69, 0x73, 0x70, 0x6C, 0x61, 0x79
+};
+
+static gchar msg_stop_projection[] = {
+ 0x00, 0x24, /* Length (36 bytes) */
+ 0x01, /* MICE Protocol Version */
+ 0x02, /* Command STOP_PROJECTION */
+
+ 0x00, /* Friendly Name TLV */
+ 0x00, 0x0A, /* Length (10 bytes) */
+ /* GNOME (UTF-16-encoded) */
+ 0x47, 0x00, 0x4E, 0x00, 0x4F, 0x00, 0x4D, 0x00, 0x45, 0x00,
+
+ 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_cc_sink_get_property (GObject * object,
+ guint prop_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ NdCCSink *sink = ND_CC_SINK (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ g_value_set_object (value, sink->comm_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, 100);
+ 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_cc_sink_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NdCCSink *sink = ND_CC_SINK (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLIENT:
+ /* Construct only */
+ sink->comm_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_cc_sink_finalize (GObject *object)
+{
+ NdCCSink *sink = ND_CC_SINK (object);
+
+ g_debug ("NdCCSink: Finalizing");
+
+ nd_cc_sink_sink_stop_stream_int (sink);
+
+ g_cancellable_cancel (sink->cancellable);
+ g_clear_object (&sink->cancellable);
+
+ 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_cc_sink_parent_class)->finalize (object);
+}
+
+static void
+nd_cc_sink_class_init (NdCCSinkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = nd_cc_sink_get_property;
+ object_class->set_property = nd_cc_sink_set_property;
+ object_class->finalize = nd_cc_sink_finalize;
+
+ props[PROP_CLIENT] =
+ g_param_spec_object ("client", "Communication Client",
+ "The GSocketClient used for Chromecast communication.",
+ 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_cc_sink_init (NdCCSink *sink)
+{
+ sink->state = ND_SINK_STATE_DISCONNECTED;
+ sink->cancellable = g_cancellable_new ();
+}
+
+/******************************************************************
+* NdSink interface implementation
+******************************************************************/
+
+static void
+nd_cc_sink_sink_iface_init (NdSinkIface *iface)
+{
+ iface->start_stream = nd_cc_sink_sink_start_stream;
+ iface->stop_stream = nd_cc_sink_sink_stop_stream;
+}
+
+static void
+play_request_cb (NdCCSink *sink, GstRTSPContext *ctx, CCClient *client)
+{
+ g_debug ("NdCCSink: Got play request from client");
+
+ sink->state = ND_SINK_STATE_STREAMING;
+ g_object_notify (G_OBJECT (sink), "state");
+}
+
+gboolean
+comm_client_send (NdCCSink * self,
+ GSocketClient * client,
+ gchar * remote_address,
+ const void * message,
+ gssize size,
+ GCancellable * cancellable,
+ GError * error)
+{
+ GOutputStream * ostream;
+
+ if (self->comm_client_conn == NULL)
+ self->comm_client_conn = g_socket_client_connect_to_host (client,
+ (gchar *) remote_address,
+ 8009,
+ // 8010,
+ NULL,
+ &error);
+
+ if (!self->comm_client_conn || error != NULL)
+ {
+ if (error != NULL)
+ g_warning ("NdCCSink: Failed to write to communication stream: %s", error->message);
+
+ return FALSE;
+
+ }
+
+ g_assert (G_IO_STREAM (self->comm_client_conn));
+
+ g_debug ("NdCCSink: Client connection established");
+
+ ostream = g_io_stream_get_output_stream (G_IO_STREAM (self->comm_client_conn));
+ if (!ostream)
+ {
+ g_warning ("NdCCSink: Could not signal to sink");
+
+ 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 ("NdCCSink: Communication client socket send would block");
+ return FALSE;
+ }
+ else
+ {
+ g_warning ("NdCCSink: Error writing to client socket output: %s", error->message);
+ return FALSE;
+ }
+ }
+
+ g_debug ("NdCCSink: Sent %" G_GSSIZE_FORMAT " bytes of data", size);
+
+ return TRUE;
+}
+
+static void
+closed_cb (NdCCSink *sink, CCClient *client)
+{
+ g_autoptr(GError) error = NULL;
+
+ /* Connection was closed, do a clean shutdown*/
+ gboolean comm_client_ok = comm_client_send (sink,
+ sink->comm_client,
+ sink->remote_address,
+ msg_stop_projection,
+ sizeof (msg_stop_projection),
+ NULL,
+ error);
+
+ if (!comm_client_ok || error != NULL)
+ {
+ if (error != NULL)
+ g_warning ("NdCCSink: Failed to send stop projection cmd to client: %s", error->message);
+ else
+ g_warning ("NdCCSink: Failed to send stop projection cmd to client");
+
+ sink->state = ND_SINK_STATE_ERROR;
+ g_object_notify (G_OBJECT (sink), "state");
+ g_clear_object (&sink->server);
+ }
+ nd_cc_sink_sink_stop_stream (ND_SINK (sink));
+}
+
+static void
+client_connected_cb (NdCCSink *sink, CCClient *client, WfdServer *server)
+{
+ g_debug ("NdCCSink: 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 (NdCCSink *sink, WfdServer *server)
+{
+ GstElement *res;
+
+ g_signal_emit_by_name (sink, "create-source", &res);
+ g_debug ("NdCCSink: Create source signal emitted");
+ return res;
+}
+
+static GstElement *
+server_create_audio_source_cb (NdCCSink *sink, WfdServer *server)
+{
+ GstElement *res;
+
+ g_signal_emit_by_name (sink, "create-audio-source", &res);
+ g_debug ("NdCCSink: Create audio source signal emitted");
+
+ return res;
+}
+
+static NdSink *
+nd_cc_sink_sink_start_stream (NdSink *sink)
+{
+ NdCCSink *self = ND_CC_SINK (sink);
+ g_autoptr(GError) error = NULL;
+ gboolean send_ok;
+
+ g_return_val_if_fail (self->state == ND_SINK_STATE_DISCONNECTED, NULL);
+
+ g_assert (self->server == NULL);
+
+ // self->state = ND_SINK_STATE_ENSURE_FIREWALL;
+ // g_object_notify (G_OBJECT (self), "state");
+
+ g_debug ("NdCCSink: Attempting connection to Chromecast: %s", self->remote_name);
+
+ // not using server for now
+ 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_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 = comm_client_send (self,
+ self->comm_client,
+ self->remote_address,
+ msg_source_ready,
+ sizeof (msg_source_ready),
+ NULL,
+ error);
+ if (!send_ok || error != NULL)
+ {
+ if (error != NULL)
+ g_warning ("NdCCSink: Failed to connect to Chromecast: %s", error->message);
+ else
+ g_warning ("NdCCSink: Failed to connect to Chromecast");
+
+ 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_cc_sink_sink_stop_stream_int (NdCCSink *self)
+{
+ g_autoptr(GError) error;
+ gboolean close_ok;
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+
+ self->cancellable = g_cancellable_new ();
+
+ /* Close the client connection */
+ if (self->comm_client_conn != NULL)
+ {
+ close_ok = g_io_stream_close (G_IO_STREAM (self->comm_client_conn), NULL, &error);
+ if (error != NULL)
+ {
+ g_warning ("NdCCSink: Error closing communication client connection: %s", error->message);
+ }
+ if (!close_ok)
+ {
+ g_warning ("NdCCSink: Communication client connection not closed");
+ }
+
+ g_clear_object (&self->comm_client_conn);
+ g_debug ("NdCCSink: 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_cc_sink_sink_stop_stream (NdSink *sink)
+{
+ NdCCSink *self = ND_CC_SINK (sink);
+
+ nd_cc_sink_sink_stop_stream_int (self);
+
+ self->state = ND_SINK_STATE_DISCONNECTED;
+ g_object_notify (G_OBJECT (self), "state");
+}
+
+/******************************************************************
+* NdCCSink public functions
+******************************************************************/
+
+NdCCSink *
+nd_cc_sink_new (GSocketClient *client,
+ gchar *name,
+ gchar *remote_address)
+{
+ return g_object_new (ND_TYPE_CC_SINK,
+ "client", client,
+ "name", name,
+ "address", remote_address,
+ NULL);
+}
+
+NdSinkState
+nd_cc_sink_get_state (NdCCSink *sink)
+{
+ return sink->state;
+}
diff --git a/src/nd-cc-sink.h b/src/nd-cc-sink.h
new file mode 100644
index 0000000..5f3a0fd
--- /dev/null
+++ b/src/nd-cc-sink.h
@@ -0,0 +1,36 @@
+/* nd-cc-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_CC_SINK (nd_cc_sink_get_type ())
+G_DECLARE_FINAL_TYPE (NdCCSink, nd_cc_sink, ND, CC_SINK, GObject)
+
+NdCCSink * nd_cc_sink_new (GSocketClient *client,
+ gchar *name,
+ gchar *remote_address);
+
+NdSinkState nd_cc_sink_get_state (NdCCSink *sink);
+
+G_END_DECLS
diff --git a/src/nd-window.c b/src/nd-window.c
index 7a155f5..37adf0e 100644
--- a/src/nd-window.c
+++ b/src/nd-window.c
@@ -28,6 +28,7 @@
#include "nd-nm-device-registry.h"
#include "nd-dummy-provider.h"
#include "nd-wfd-mice-provider.h"
+#include "nd-cc-provider.h"
#include <gst/gst.h>
@@ -324,6 +325,7 @@ gnome_nd_window_constructed (GObject *obj)
{
g_autoptr(GError) error = NULL;
g_autoptr(NdWFDMiceProvider) mice_provider = NULL;
+ g_autoptr(NdCCProvider) cc_provider = NULL;
NdWindow *self = ND_WINDOW (obj);
self->cancellable = g_cancellable_new ();
@@ -341,8 +343,9 @@ gnome_nd_window_constructed (GObject *obj)
g_debug ("NdWindow: Got avahi client");
mice_provider = nd_wfd_mice_provider_new (self->avahi_client);
+ cc_provider = nd_cc_provider_new (self->avahi_client);
- if (!nd_wfd_mice_provider_browse (mice_provider, error))
+ if (!nd_wfd_mice_provider_browse (mice_provider, error) || !nd_cc_provider_browse (cc_provider, error))
{
g_warning ("NdWindow: Avahi client failed to browse: %s", error->message);
return;
@@ -350,6 +353,7 @@ gnome_nd_window_constructed (GObject *obj)
g_debug ("NdWindow: Got avahi browser");
nd_meta_provider_add_provider (self->meta_provider, ND_PROVIDER (mice_provider));
+ nd_meta_provider_add_provider (self->meta_provider, ND_PROVIDER (cc_provider));
}
static void
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]