rfbsrc: add uri interface
authorMarc Leeman <m.leeman@televic.com>
Tue, 15 Mar 2022 11:44:20 +0000 (12:44 +0100)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Fri, 3 Jun 2022 19:44:28 +0000 (19:44 +0000)
Adding a uri interface enables plugging in RFB/VNC sources to anything
that makes use of uridecodebin:

gst-play-1.0 rfb://:password@10.40.216.180:5903?shared=1

Use userinfo to pass user (ignored) and password, other key/value pairs
can be encoded in the query part of the URI (see shared)

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1963>

subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json
subprojects/gst-plugins-bad/gst/librfb/gstrfb-utils.c [new file with mode: 0644]
subprojects/gst-plugins-bad/gst/librfb/gstrfb-utils.h [new file with mode: 0644]
subprojects/gst-plugins-bad/gst/librfb/gstrfbsrc.c
subprojects/gst-plugins-bad/gst/librfb/gstrfbsrc.h
subprojects/gst-plugins-bad/gst/librfb/meson.build

index c16540860a936ec2bf790b593de9d89c2fd3ae8d..d0c983fe6e82dc51054239ffa605c0f764a443c2 100644 (file)
                     "GInitiallyUnowned",
                     "GObject"
                 ],
+                "interfaces": [
+                    "GstURIHandler"
+                ],
                 "klass": "Source/Video",
                 "long-name": "Rfb source",
                 "pad-templates": {
                         "type": "gboolean",
                         "writable": true
                     },
+                    "uri": {
+                        "blurb": "URI in the form of rfb://host:port?query",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "rfb://127.0.0.1:5900",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gchararray",
+                        "writable": true
+                    },
                     "use-copyrect": {
                         "blurb": "Use copyrect encoding",
                         "conditionally-available": false,
diff --git a/subprojects/gst-plugins-bad/gst/librfb/gstrfb-utils.c b/subprojects/gst-plugins-bad/gst/librfb/gstrfb-utils.c
new file mode 100644 (file)
index 0000000..1f77f64
--- /dev/null
@@ -0,0 +1,56 @@
+/* GStreamer
+ * Copyright (C) <2022> Marc Leeman <marc.leeman@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/226
+ */
+
+#include "gstrfb-utils.h"
+
+static void
+gst_rfb_utils_uri_query_foreach (const gchar * key, const gchar * value,
+    GObject * src)
+{
+  if (key == NULL) {
+    GST_WARNING_OBJECT (src, "Refusing to use empty key.");
+    return;
+  }
+
+  if (value == NULL) {
+    GST_WARNING_OBJECT (src, "Refusing to use NULL for key %s.", key);
+    return;
+  }
+
+  GST_DEBUG_OBJECT (src, "Setting property '%s' to '%s'", key, value);
+  gst_util_set_object_arg (src, key, value);
+}
+
+void
+gst_rfb_utils_set_properties_from_uri_query (GObject * obj, const GstUri * uri)
+{
+  GHashTable *hash_table;
+
+  g_return_if_fail (uri != NULL);
+  hash_table = gst_uri_get_query_table (uri);
+
+  if (hash_table) {
+    g_hash_table_foreach (hash_table,
+        (GHFunc) gst_rfb_utils_uri_query_foreach, obj);
+
+    g_hash_table_unref (hash_table);
+  }
+}
diff --git a/subprojects/gst-plugins-bad/gst/librfb/gstrfb-utils.h b/subprojects/gst-plugins-bad/gst/librfb/gstrfb-utils.h
new file mode 100644 (file)
index 0000000..b09c37c
--- /dev/null
@@ -0,0 +1,29 @@
+/* GStreamer
+ * Copyright (C) <2022> Marc Leeman <marc.leeman@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/226
+ */
+
+#ifndef __GST_RFB_UTILS_H__
+#define __GST_RFB_UTILS_H__
+
+#include <gst/gst.h>
+
+void gst_rfb_utils_set_properties_from_uri_query (GObject * obj, const GstUri * uri);
+
+#endif
index 59ffd6a2dc105a01939912efab44b6b4b9388fa9..ef4abc080dad001188500e754b91c1a93645e937 100644 (file)
 #endif
 
 #include "gstrfbsrc.h"
+#include "gstrfb-utils.h"
 
 #include <gst/video/video.h>
+#include <glib/gi18n-lib.h>
 
 #include <string.h>
 #include <stdlib.h>
 #include <X11/Xlib.h>
 #endif
 
+#define DEFAULT_PROP_HOST             "127.0.0.1"
+#define DEFAULT_PROP_PORT             5900
+#define DEFAULT_PROP_URI              "rfb://"DEFAULT_PROP_HOST":"G_STRINGIFY(DEFAULT_PROP_PORT)
+
 enum
 {
   PROP_0,
+  PROP_URI,
   PROP_HOST,
   PROP_PORT,
   PROP_VERSION,
@@ -80,8 +87,15 @@ static gboolean gst_rfb_src_decide_allocation (GstBaseSrc * bsrc,
     GstQuery * query);
 static GstFlowReturn gst_rfb_src_fill (GstPushSrc * psrc, GstBuffer * outbuf);
 
+static void gst_rfb_src_uri_handler_init (gpointer g_iface,
+    gpointer iface_data);
+static gboolean
+gst_rfb_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
+    GError ** error);
+
 #define gst_rfb_src_parent_class parent_class
-G_DEFINE_TYPE (GstRfbSrc, gst_rfb_src, GST_TYPE_PUSH_SRC);
+G_DEFINE_TYPE_WITH_CODE (GstRfbSrc, gst_rfb_src, GST_TYPE_PUSH_SRC,
+    G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_rfb_src_uri_handler_init));
 GST_ELEMENT_REGISTER_DEFINE (rfbsrc, "rfbsrc", GST_RANK_NONE, GST_TYPE_RFB_SRC);
 
 static void
@@ -104,12 +118,26 @@ gst_rfb_src_class_init (GstRfbSrcClass * klass)
   gobject_class->set_property = gst_rfb_src_set_property;
   gobject_class->get_property = gst_rfb_src_get_property;
 
+  /**
+   * GstRfbSrc:uri:
+   *
+   * uri to an RFB from. All GStreamer parameters can be
+   * encoded in the URI, this URI format is RFC compliant.
+   *
+   * Since: 1.22
+   */
+  g_object_class_install_property (gobject_class, PROP_URI,
+      g_param_spec_string ("uri", "URI",
+          "URI in the form of rfb://host:port?query", DEFAULT_PROP_URI,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_property (gobject_class, PROP_HOST,
       g_param_spec_string ("host", "Host to connect to", "Host to connect to",
-          "127.0.0.1", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+          DEFAULT_PROP_HOST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   g_object_class_install_property (gobject_class, PROP_PORT,
       g_param_spec_int ("port", "Port", "Port",
-          1, 65535, 5900, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+          1, 65535, DEFAULT_PROP_PORT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   g_object_class_install_property (gobject_class, PROP_VERSION,
       g_param_spec_string ("version", "RFB protocol version",
           "RFB protocol version", "3.3",
@@ -179,8 +207,9 @@ gst_rfb_src_init (GstRfbSrc * src)
   gst_base_src_set_live (bsrc, TRUE);
   gst_base_src_set_format (bsrc, GST_FORMAT_TIME);
 
-  src->host = g_strdup ("127.0.0.1");
-  src->port = 5900;
+  src->uri = gst_uri_from_string (DEFAULT_PROP_URI);
+  src->host = g_strdup (DEFAULT_PROP_HOST);
+  src->port = DEFAULT_PROP_PORT;
   src->version_major = 3;
   src->version_minor = 3;
 
@@ -196,6 +225,9 @@ gst_rfb_src_finalize (GObject * object)
 {
   GstRfbSrc *src = GST_RFB_SRC (object);
 
+  if (src->uri)
+    gst_uri_unref (src->uri);
+
   g_free (src->host);
 
   if (src->decoder) {
@@ -248,6 +280,12 @@ gst_rfb_src_set_property (GObject * object, guint prop_id,
   GstRfbSrc *src = GST_RFB_SRC (object);
 
   switch (prop_id) {
+    case PROP_URI:{
+      const gchar *str_uri = g_value_get_string (value);
+
+      gst_rfb_src_uri_set_uri ((GstURIHandler *) src, str_uri, NULL);
+      break;
+    }
     case PROP_HOST:
       src->host = g_value_dup_string (value);;
       break;
@@ -298,6 +336,14 @@ gst_rfb_src_get_property (GObject * object, guint prop_id,
   gchar *version;
 
   switch (prop_id) {
+    case PROP_URI:
+      GST_OBJECT_LOCK (object);
+      if (src->uri)
+        g_value_take_string (value, gst_uri_to_string (src->uri));
+      else
+        g_value_set_string (value, NULL);
+      GST_OBJECT_UNLOCK (object);
+      break;
     case PROP_HOST:
       g_value_set_string (value, src->host);
       break;
@@ -633,6 +679,124 @@ gst_rfb_src_unlock (GstBaseSrc * bsrc)
   return TRUE;
 }
 
+static GstURIType
+gst_rfb_src_uri_get_type (GType type)
+{
+  return GST_URI_SRC;
+}
+
+static const gchar *const *
+gst_rfb_src_uri_get_protocols (GType type)
+{
+  static const gchar *protocols[] = { (char *) "rfb", NULL };
+
+  return protocols;
+}
+
+static gchar *
+gst_rfb_src_uri_get_uri (GstURIHandler * handler)
+{
+  GstRfbSrc *src = (GstRfbSrc *) handler;
+  gchar *str_uri = NULL;
+
+  GST_OBJECT_LOCK (src);
+  str_uri = gst_uri_to_string (src->uri);
+  GST_OBJECT_UNLOCK (src);
+
+  return str_uri;
+}
+
+static gboolean
+gst_rfb_src_uri_set_uri (GstURIHandler * handler, const gchar * str_uri,
+    GError ** error)
+{
+  GstRfbSrc *src = (GstRfbSrc *) handler;
+  GstUri *uri = NULL;
+  const gchar *userinfo;
+
+  g_return_val_if_fail (str_uri != NULL, FALSE);
+
+  if (GST_STATE (src) >= GST_STATE_PAUSED) {
+    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
+        _("Changing the URI on rfbsrc when it is running is not supported"));
+    GST_ERROR_OBJECT (src,
+        "Changing the URI on rfbsrc when it is running is not supported");
+    return FALSE;
+  }
+
+  if (!(uri = gst_uri_from_string (str_uri))) {
+    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+        _("Invalid URI: %s"), str_uri);
+    GST_ERROR_OBJECT (src, "Invalid URI: %s", str_uri);
+    return FALSE;
+  }
+
+  if (g_strcmp0 (gst_uri_get_scheme (uri), "rfb") != 0) {
+    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+        _("Invalid scheme in uri (needs to be rfb): %s"), str_uri);
+    GST_ERROR_OBJECT (src, "Invalid scheme in uri (needs to be rfb): %s",
+        str_uri);
+    gst_uri_unref (uri);
+    return FALSE;
+  }
+
+  /* Recursive set to src, do not use the same lock in all property
+   * setters. */
+  g_object_set (src, "host", gst_uri_get_host (uri), NULL);
+  g_object_set (src, "port", gst_uri_get_port (uri), NULL);
+
+  userinfo = gst_uri_get_userinfo (uri);
+  if (userinfo) {
+    gchar *pass;
+    gchar **split = g_strsplit (userinfo, ":", 2);
+
+    if (!split || !split[0] || !split[1]) {
+      g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+          _("Failed to parse username:password data"));
+      GST_ERROR_OBJECT (src, "Failed to parse username:password data");
+      g_strfreev (split);
+      gst_uri_unref (uri);
+      return FALSE;
+    }
+
+    if (g_strrstr (split[1], ":") != NULL)
+      GST_WARNING_OBJECT (src,
+          "userinfo %s contains more than one ':', will "
+          "assume that the first ':' delineates user:pass. You should escape "
+          "the user and pass before adding to the URI.", userinfo);
+
+    pass = g_uri_unescape_string (split[1], NULL);
+    g_strfreev (split);
+
+    g_object_set (src, "password", pass, NULL);
+    g_free (pass);
+  }
+
+  /* Only save URI once it is accepted */
+  GST_OBJECT_LOCK (src);
+  if (src->uri)
+    gst_uri_unref (src->uri);
+  src->uri = gst_uri_ref (uri);
+  GST_OBJECT_UNLOCK (src);
+
+  gst_rfb_utils_set_properties_from_uri_query (G_OBJECT (src), uri);
+
+  gst_uri_unref (uri);
+
+  return TRUE;
+}
+
+static void
+gst_rfb_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
+{
+  GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
+
+  iface->get_type = gst_rfb_src_uri_get_type;
+  iface->get_protocols = gst_rfb_src_uri_get_protocols;
+  iface->get_uri = gst_rfb_src_uri_get_uri;
+  iface->set_uri = gst_rfb_src_uri_set_uri;
+}
+
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
index e106549251b253be8058ed5a6c1661e824e4d4f4..92ad78fdbcc92a0beb920b422f25a10ccf8e14b6 100644 (file)
@@ -51,6 +51,7 @@ struct _GstRfbSrc
 {
   GstPushSrc element;
 
+  GstUri *uri;
   gchar *host;
   gint port;
 
@@ -64,7 +65,6 @@ struct _GstRfbSrc
   /* protocol version */
   guint version_major;
   guint version_minor;
-
 };
 
 GType gst_rfb_src_get_type (void);
index 657ee056dfae4cee7a80eeeef2ac81929d33df89..ff66d484d046bc37c383efe39821edf1dc4f76de 100644 (file)
@@ -1,5 +1,6 @@
 rfbsrc_sources = [
   'gstrfbsrc.c',
+  'gstrfb-utils.c',
   'rfbdecoder.c',
   'd3des.c',
 ]