[xdg-desktop-portal-gnome/gbsneto/screencast-session-restore] WIP: Implement stream restoration




commit fc0a7d4c5d85cd8c392adf274d754fd4e1b3b065
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Tue Sep 28 21:04:28 2021 -0300

    WIP: Implement stream restoration
    
    Still not fully ready, but this is enough to
    get going

 src/screencast.c        | 221 ++++++++++++++++++++++++++++++++++++++++++++++--
 src/screencast.h        |   7 ++
 src/screencastdialog.c  |  25 +++++-
 src/screencastdialog.h  |   3 +-
 src/screencastwidget.c  |  75 ++++++++++++++++
 src/screencastwidget.h  |   6 ++
 src/screencastwidget.ui |   7 ++
 src/utils.c             |  77 +++++++++++++++++
 src/utils.h             |   3 +
 9 files changed, 411 insertions(+), 13 deletions(-)
---
diff --git a/src/screencast.c b/src/screencast.c
index 27d8946..8ee44ab 100644
--- a/src/screencast.c
+++ b/src/screencast.c
@@ -33,6 +33,7 @@
 #include "externalwindow.h"
 #include "request.h"
 #include "session.h"
+#include "shellintrospect.h"
 #include "utils.h"
 
 typedef struct _ScreenCastDialogHandle ScreenCastDialogHandle;
@@ -49,6 +50,9 @@ typedef struct _ScreenCastSession
 
   ScreenCastSelection select;
 
+  ScreenCastPersistMode persist_mode;
+  char **restore_data;
+
   GDBusMethodInvocation *start_invocation;
   ScreenCastDialogHandle *dialog_handle;
 } ScreenCastSession;
@@ -127,6 +131,8 @@ static void
 screen_cast_dialog_done (GtkWidget *widget,
                          int dialog_response,
                          GVariant *selections,
+                         GStrv restore_data,
+                         ScreenCastPersistMode persist_mode,
                          ScreenCastDialogHandle *dialog_handle)
 {
   int response;
@@ -151,8 +157,16 @@ screen_cast_dialog_done (GtkWidget *widget,
 
   if (response == 0)
     {
+      ScreenCastSession *session = dialog_handle->session;
       g_autoptr(GError) error = NULL;
 
+      if (session->persist_mode != SCREEN_CAST_PERSIST_MODE_NONE && restore_data)
+        {
+          g_clear_pointer (&session->restore_data, g_strfreev);
+          session->restore_data = g_strdupv (restore_data);
+          session->persist_mode = MIN (session->persist_mode, persist_mode);
+        }
+
       if (!start_session (dialog_handle->session, selections, &error))
         {
           g_warning ("Failed to start session: %s", error->message);
@@ -202,7 +216,8 @@ create_screen_cast_dialog (ScreenCastSession *session,
   g_object_ref_sink (fake_parent);
 
   dialog = GTK_WIDGET (screen_cast_dialog_new (request->app_id,
-                                               &session->select));
+                                               &session->select,
+                                               session->persist_mode));
   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (fake_parent));
   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
 
@@ -288,6 +303,8 @@ handle_select_sources (XdpImplScreenCast *object,
                        const char *arg_app_id,
                        GVariant *arg_options)
 {
+  g_autoptr(GVariant) restore_data = NULL;
+  ScreenCastSession *screen_cast_session;
   Session *session;
   int response;
   uint32_t types;
@@ -359,6 +376,25 @@ handle_select_sources (XdpImplScreenCast *object,
       response = 2;
     }
 
+  screen_cast_session = (ScreenCastSession *)session;
+  g_variant_lookup (arg_options, "persist_mode", "u", &screen_cast_session->persist_mode);
+  g_variant_lookup (arg_options, "restore_data", "v", &restore_data);
+
+  if (restore_data)
+    {
+      if (g_variant_check_format_string (restore_data, "^a&s", FALSE))
+        {
+          g_autofree gchar **restore_data_strv = NULL;
+
+          g_variant_get (restore_data,  "^a&s", &restore_data_strv);
+          screen_cast_session->restore_data = g_strdupv (restore_data_strv);
+        }
+      else
+        {
+          g_warning ("Cannot parse restore data, ignoring");
+        }
+    }
+
 out:
   g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT);
   results = g_variant_builder_end (&results_builder);
@@ -386,6 +422,18 @@ start_done (ScreenCastSession *screen_cast_session)
                          "streams",
                          g_variant_builder_end (&streams_builder));
 
+  if (screen_cast_session->persist_mode != SCREEN_CAST_PERSIST_MODE_NONE &&
+      screen_cast_session->restore_data)
+    {
+      GVariant *restore_data =
+        g_variant_new_strv ((const char * const*)screen_cast_session->restore_data, -1);
+
+      g_variant_builder_add (&results_builder, "{sv}", "restore_data",
+                             g_variant_new_variant (restore_data));
+      g_variant_builder_add (&results_builder, "{sv}", "persist_mode",
+                             g_variant_new_uint32 (screen_cast_session->persist_mode));
+    }
+
   xdp_impl_screen_cast_complete_start (XDP_IMPL_SCREEN_CAST (impl),
                                        screen_cast_session->start_invocation, 0,
                                        g_variant_builder_end (&results_builder));
@@ -457,6 +505,156 @@ cancel_start_session (ScreenCastSession *screen_cast_session,
                                        g_variant_builder_end (&results_builder));
 }
 
+static Monitor *
+find_monitor_by_string (const char *monitor_string)
+{
+  DisplayStateTracker *display_state_tracker = display_state_tracker_get ();
+  GList *logical_monitors;
+
+  for (logical_monitors = display_state_tracker_get_logical_monitors (display_state_tracker);
+       logical_monitors;
+       logical_monitors = logical_monitors->next)
+    {
+      LogicalMonitor *logical_monitor = logical_monitors->data;
+      GList *monitors;
+
+      for (monitors = logical_monitor_get_monitors (logical_monitor);
+           monitors;
+           monitors = monitors->next)
+        {
+          Monitor *monitor = monitors->data;
+
+          if (g_strcmp0 (monitor_get_match_string (monitor), monitor_string) == 0)
+            return monitor;
+        }
+    }
+
+  return NULL;
+}
+
+static Window *
+find_best_window_by_app_id_and_title (const char *app_id,
+                                      const char *title)
+{
+  ShellIntrospect *shell_introspect = shell_introspect_get ();
+  Window *best_match;
+  glong best_match_distance;
+  GList *windows;
+
+  best_match = NULL;
+  best_match_distance = G_MAXLONG;
+
+  for (windows = shell_introspect_get_windows (shell_introspect);
+       windows;
+       windows = windows->next)
+    {
+      Window *window = windows->data;
+      glong distance;
+
+      g_message ("   Comparing: %s - %s", window_get_app_id (window), app_id);
+
+      if (g_strcmp0 (window_get_app_id (window), app_id) != 0)
+        continue;
+
+      distance = str_distance (window_get_title (window), title);
+
+
+      g_message ("   Distance between %s and %s: %ld",
+                 window_get_title (window), title, distance);
+
+      if (distance == 0)
+        return window;
+
+      if (distance < best_match_distance)
+        {
+          best_match = window;
+          best_match_distance = distance;
+        }
+    }
+
+  return best_match;
+}
+
+static gboolean
+restore_stream_from_data (ScreenCastSession *screen_cast_session)
+
+{
+  g_autoptr(GError) error = NULL;
+  GVariantBuilder selections_builder;
+  GVariantBuilder sources_builder;
+  gsize i;
+
+  if (!screen_cast_session->restore_data)
+    return FALSE;
+
+  g_variant_builder_init (&sources_builder, G_VARIANT_TYPE ("a(u?)"));
+  for (i = 0; screen_cast_session->restore_data[i] != NULL; i++)
+    {
+      const char *to_restore = screen_cast_session->restore_data[i];
+
+      if (g_str_has_prefix (to_restore, "monitor::"))
+        {
+          Monitor *monitor = find_monitor_by_string (to_restore + strlen ("monitor::"));
+
+          if (!monitor)
+            goto fail;
+
+          g_variant_builder_add (&sources_builder, "(us)",
+                                 SCREEN_CAST_SOURCE_TYPE_MONITOR,
+                                 monitor_get_connector (monitor));
+        }
+      else if (g_str_has_prefix (to_restore, "window::"))
+        {
+          const char *window_string = to_restore + strlen ("window::");
+
+          g_message ("Parsing %s", window_string);
+
+          g_auto(GStrv) strv = g_strsplit (window_string, "::", 2);
+          Window *window;
+
+          if (!strv || g_strv_length (strv) == 3)
+            goto fail;
+
+          g_message ("   OK: %s - %s", strv[0], strv[1]);
+
+          window = find_best_window_by_app_id_and_title (strv[0], strv[1]);
+
+          if (!window)
+            goto fail;
+
+          g_variant_builder_add (&sources_builder, "(ut)",
+                                 SCREEN_CAST_SOURCE_TYPE_WINDOW,
+                                 window_get_id (window));
+        }
+      else
+        {
+          goto fail;
+        }
+    }
+
+  g_variant_builder_init (&selections_builder, G_VARIANT_TYPE ("a{sv}"));
+  g_variant_builder_add (&selections_builder, "{sv}",
+                         "selected_screen_cast_sources",
+                         g_variant_builder_end (&sources_builder));
+
+  start_session (screen_cast_session,
+                 g_variant_builder_end (&selections_builder),
+                 &error);
+
+  if (error)
+    {
+      g_warning ("Error restoring stream from session: %s", error->message);
+      return FALSE;
+    }
+
+  return TRUE;
+
+fail:
+  g_variant_builder_clear (&selections_builder);
+  g_variant_builder_clear (&sources_builder);
+  return FALSE;
+}
+
 static gboolean
 handle_start (XdpImplScreenCast *object,
               GDBusMethodInvocation *invocation,
@@ -469,7 +667,6 @@ handle_start (XdpImplScreenCast *object,
   const char *sender;
   g_autoptr(Request) request = NULL;
   ScreenCastSession *screen_cast_session;
-  ScreenCastDialogHandle *dialog_handle;
   GVariantBuilder results_builder;
 
   sender = g_dbus_method_invocation_get_sender (invocation);
@@ -491,14 +688,19 @@ handle_start (XdpImplScreenCast *object,
       goto err;
     }
 
-  dialog_handle = create_screen_cast_dialog (screen_cast_session,
-                                             invocation,
-                                             request,
-                                             arg_parent_window);
+  screen_cast_session->start_invocation = invocation;
 
+  if (!restore_stream_from_data (screen_cast_session))
+    {
+      ScreenCastDialogHandle *dialog_handle;
 
-  screen_cast_session->start_invocation = invocation;
-  screen_cast_session->dialog_handle = dialog_handle;
+      dialog_handle = create_screen_cast_dialog (screen_cast_session,
+                                                 invocation,
+                                                 request,
+                                                 arg_parent_window);
+
+      screen_cast_session->dialog_handle = dialog_handle;
+    }
 
   return TRUE;
 
@@ -622,6 +824,9 @@ screen_cast_session_finalize (GObject *object)
 static void
 screen_cast_session_init (ScreenCastSession *screen_cast_session)
 {
+  display_state_tracker_get ();
+  shell_introspect_get ();
+  screen_cast_session->persist_mode = SCREEN_CAST_PERSIST_MODE_NONE;
 }
 
 static void
diff --git a/src/screencast.h b/src/screencast.h
index f5033b2..42b5a57 100644
--- a/src/screencast.h
+++ b/src/screencast.h
@@ -35,6 +35,13 @@ typedef enum _ScreenCastCursorMode
   SCREEN_CAST_CURSOR_MODE_METADATA = 4,
 } ScreenCastCursorMode;
 
+typedef enum _ScreenCastPersistMode
+{
+  SCREEN_CAST_PERSIST_MODE_NONE = 0,
+  SCREEN_CAST_PERSIST_MODE_TRANSIENT = 1,
+  SCREEN_CAST_PERSIST_MODE_PERSISTENT = 2,
+} ScreenCastPersistMode;
+
 typedef struct _ScreenCastSelection
 {
   gboolean multiple;
diff --git a/src/screencastdialog.c b/src/screencastdialog.c
index 306992d..1bfd841 100644
--- a/src/screencastdialog.c
+++ b/src/screencastdialog.c
@@ -55,8 +55,10 @@ static void
 button_clicked (GtkWidget *button,
                 ScreenCastDialog *dialog)
 {
+  ScreenCastPersistMode persist_mode;
   int response;
   GVariant *selections;
+  GStrv restore_data;
 
   gtk_widget_hide (GTK_WIDGET (dialog));
 
@@ -72,14 +74,25 @@ button_clicked (GtkWidget *button,
       screen_cast_widget_add_selections (screen_cast_widget,
                                          &selections_builder);
       selections = g_variant_builder_end (&selections_builder);
+
+      restore_data = screen_cast_widget_get_restore_data (screen_cast_widget,
+                                                          &persist_mode);
     }
   else
     {
       response = GTK_RESPONSE_CANCEL;
       selections = NULL;
+      restore_data = NULL;
+      persist_mode = SCREEN_CAST_PERSIST_MODE_NONE;
     }
 
-  g_signal_emit (dialog, signals[DONE], 0, response, selections);
+  g_signal_emit (dialog,
+                 signals[DONE],
+                 0,
+                 response,
+                 selections,
+                 restore_data,
+                 persist_mode);
 }
 
 static void
@@ -95,7 +108,8 @@ on_has_selection_changed (ScreenCastWidget *screen_cast_widget,
 
 ScreenCastDialog *
 screen_cast_dialog_new (const char *app_id,
-                        ScreenCastSelection *select)
+                        ScreenCastSelection *select,
+                        ScreenCastPersistMode persist_mode)
 {
   ScreenCastDialog *dialog;
   ScreenCastWidget *screen_cast_widget;
@@ -106,6 +120,7 @@ screen_cast_dialog_new (const char *app_id,
   screen_cast_widget_set_allow_multiple (screen_cast_widget, select->multiple);
   screen_cast_widget_set_source_types (screen_cast_widget,
                                        select->source_types);
+  screen_cast_widget_set_persist_mode (screen_cast_widget, persist_mode);
 
   return dialog;
 }
@@ -144,9 +159,11 @@ screen_cast_dialog_class_init (ScreenCastDialogClass *klass)
                                 0,
                                 NULL, NULL,
                                 NULL,
-                                G_TYPE_NONE, 2,
+                                G_TYPE_NONE, 4,
                                 G_TYPE_INT,
-                                G_TYPE_VARIANT);
+                                G_TYPE_VARIANT,
+                                G_TYPE_STRV,
+                                G_TYPE_INT);
 
   init_screen_cast_widget ();
 
diff --git a/src/screencastdialog.h b/src/screencastdialog.h
index 247d93b..f116dd3 100644
--- a/src/screencastdialog.h
+++ b/src/screencastdialog.h
@@ -27,4 +27,5 @@ G_DECLARE_FINAL_TYPE (ScreenCastDialog, screen_cast_dialog,
                       SCREEN_CAST, DIALOG, GtkWindow)
 
 ScreenCastDialog * screen_cast_dialog_new (const char *app_id,
-                                           ScreenCastSelection *select);
+                                           ScreenCastSelection *select,
+                                           ScreenCastPersistMode persist_mode);
diff --git a/src/screencastwidget.c b/src/screencastwidget.c
index cdd57dc..5ff4572 100644
--- a/src/screencastwidget.c
+++ b/src/screencastwidget.c
@@ -51,6 +51,9 @@ struct _ScreenCastWidget
   GtkWidget *window_list;
   GtkWidget *window_list_scrolled;
 
+  GtkCheckButton *persist_check;
+  ScreenCastPersistMode persist_mode;
+
   DisplayStateTracker *display_state_tracker;
   gulong monitors_changed_handler_id;
 
@@ -472,6 +475,68 @@ screen_cast_widget_add_selections (ScreenCastWidget *widget,
     }
 }
 
+GStrv
+screen_cast_widget_get_restore_data (ScreenCastWidget      *widget,
+                                     ScreenCastPersistMode *out_persist_mode)
+{
+  GPtrArray *array;
+  GList *selected_monitor_rows;
+  GList *selected_window_rows;
+  GList *l;
+
+  if (widget->persist_mode == SCREEN_CAST_PERSIST_MODE_NONE ||
+      (widget->persist_mode == SCREEN_CAST_PERSIST_MODE_PERSISTENT &&
+       !gtk_check_button_get_active (widget->persist_check)))
+    {
+      *out_persist_mode = SCREEN_CAST_PERSIST_MODE_NONE;
+      return NULL;
+    }
+
+  selected_monitor_rows =
+    gtk_list_box_get_selected_rows (GTK_LIST_BOX (widget->monitor_list));
+  selected_window_rows =
+    gtk_list_box_get_selected_rows (GTK_LIST_BOX (widget->window_list));
+  if (!selected_monitor_rows && !selected_window_rows)
+    {
+      *out_persist_mode = SCREEN_CAST_PERSIST_MODE_NONE;
+      return NULL;
+    }
+
+  array = g_ptr_array_new ();
+
+  for (l = selected_monitor_rows; l; l = l->next)
+    {
+      GtkWidget *monitor_widget = gtk_list_box_row_get_child (l->data);
+      Monitor *monitor;
+
+      monitor = g_object_get_qdata (G_OBJECT (monitor_widget),
+                                    quark_monitor_widget_data);
+
+      g_ptr_array_add (array, g_strdup_printf ("monitor::%s", monitor_get_match_string (monitor)));
+    }
+  g_list_free (selected_monitor_rows);
+
+  for (l = selected_window_rows; l; l = l->next)
+    {
+      GtkWidget *window_widget = gtk_list_box_row_get_child (l->data);
+      Window *window;
+
+      window = g_object_get_qdata (G_OBJECT (window_widget),
+                                    quark_window_widget_data);
+
+      g_ptr_array_add (array, g_strdup_printf ("window::%s::%s",
+                                               window_get_app_id (window),
+                                               window_get_title (window)));
+    }
+  g_list_free (selected_window_rows);
+
+  g_ptr_array_add (array, NULL);
+
+  *out_persist_mode = widget->persist_mode;
+
+  return (GStrv) g_ptr_array_free (array, FALSE);
+}
+
 void
 screen_cast_widget_set_app_id (ScreenCastWidget *widget,
                                const char *app_id)
@@ -532,6 +597,15 @@ screen_cast_widget_set_source_types (ScreenCastWidget *screen_cast_widget,
     gtk_widget_show (screen_cast_widget->source_type_switcher);
 }
 
+void
+screen_cast_widget_set_persist_mode (ScreenCastWidget      *screen_cast_widget,
+                                     ScreenCastPersistMode  persist_mode)
+{
+  screen_cast_widget->persist_mode = persist_mode;
+  gtk_widget_set_visible (GTK_WIDGET (screen_cast_widget->persist_check),
+                                      persist_mode == SCREEN_CAST_PERSIST_MODE_PERSISTENT);
+}
+
 static void
 screen_cast_widget_finalize (GObject *object)
 {
@@ -622,6 +696,7 @@ screen_cast_widget_class_init (ScreenCastWidgetClass *klass)
                                                  G_TYPE_BOOLEAN);
 
   gtk_widget_class_set_template_from_resource (widget_class, 
"/org/freedesktop/portal/desktop/gnome/screencastwidget.ui");
+  gtk_widget_class_bind_template_child (widget_class, ScreenCastWidget, persist_check);
   gtk_widget_class_bind_template_child (widget_class, ScreenCastWidget, source_type_switcher);
   gtk_widget_class_bind_template_child (widget_class, ScreenCastWidget, source_type);
   gtk_widget_class_bind_template_child (widget_class, ScreenCastWidget, monitor_selection);
diff --git a/src/screencastwidget.h b/src/screencastwidget.h
index 3a1ebd2..0b80274 100644
--- a/src/screencastwidget.h
+++ b/src/screencastwidget.h
@@ -39,5 +39,11 @@ void screen_cast_widget_set_allow_multiple (ScreenCastWidget *widget,
 void screen_cast_widget_set_source_types (ScreenCastWidget *screen_cast_widget,
                                           ScreenCastSourceType source_types);
 
+void screen_cast_widget_set_persist_mode (ScreenCastWidget *screen_cast_widget,
+                                          ScreenCastPersistMode persist_mode);
+
 void screen_cast_widget_add_selections (ScreenCastWidget *widget,
                                         GVariantBuilder *selections_builder);
+
+GStrv screen_cast_widget_get_restore_data (ScreenCastWidget      *widget,
+                                           ScreenCastPersistMode *out_persist_mode);
diff --git a/src/screencastwidget.ui b/src/screencastwidget.ui
index 2417ca5..d727c72 100644
--- a/src/screencastwidget.ui
+++ b/src/screencastwidget.ui
@@ -149,5 +149,12 @@
         </child>
       </object>
     </child>
+
+    <!-- Persist permission -->
+    <child>
+      <object class="GtkCheckButton" id="persist_check">
+        <property name="label" translatable="yes">Remember this decision</property>
+      </object>
+    </child>
   </template>
 </interface>
diff --git a/src/utils.c b/src/utils.c
index b7dd472..5e0485c 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -45,3 +45,80 @@ xdg_desktop_portal_error_quark (void)
                                       G_N_ELEMENTS (xdg_desktop_portal_error_entries));
   return (GQuark) quark_volatile;
 }
+
+glong
+str_distance (const char *a,
+              const char *b)
+{
+  g_autofree gint *v0 = NULL;
+  g_autofree gint *v1 = NULL;
+  const gchar *s;
+  const gchar *t;
+  gunichar sc;
+  gunichar tc;
+  glong b_char_len;
+  glong cost;
+  glong i;
+  glong j;
+
+  /*
+  * Handle degenerate cases.
+  */
+  if (g_strcmp0 (a, b) == 0)
+    return 0;
+  else if (!*a)
+    return g_utf8_strlen (a, -1);
+  else if (!*b)
+    return g_utf8_strlen (a, -1);
+
+  b_char_len = g_utf8_strlen (b, -1);
+
+  /*
+   * Create two vectors to hold our states.
+   */
+
+  v0 = g_new0 (gint, b_char_len + 1);
+  v1 = g_new0 (gint, b_char_len + 1);
+
+  /*
+   * initialize v0 (the previous row of distances).
+   * this row is A[0][i]: edit distance for an empty a.
+   * the distance is just the number of characters to delete from b.
+   */
+  for (i = 0; i < b_char_len + 1; i++)
+    v0[i] = i;
+
+  for (i = 0, s = a; s && *s; i++, s = g_utf8_next_char(s))
+    {
+      /*
+       * Calculate v1 (current row distances) from the previous row v0.
+       */
+
+      sc = g_utf8_get_char(s);
+
+      /*
+       * first element of v1 is A[i+1][0]
+       *
+       * edit distance is delete (i+1) chars from a to match empty
+       * b.
+       */
+      v1[0] = i + 1;
+
+      /*
+       * use formula to fill in the rest of the row.
+       */
+      for (j = 0, t = b; t && *t; j++, t = g_utf8_next_char(t))
+        {
+          tc = g_utf8_get_char(t);
+          cost = (sc == tc) ? 0 : 1;
+          v1[j+1] = MIN (v1[j] + 1, MIN (v0[j+1] + 1, v0[j] + cost));
+        }
+
+      /*
+       * copy v1 (current row) to v0 (previous row) for next iteration.
+       */
+      memcpy (v0, v1, sizeof(gint) * b_char_len);
+    }
+
+  return v1[b_char_len];
+}
diff --git a/src/utils.h b/src/utils.h
index 5fdfda9..fa3f1b0 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -37,3 +37,6 @@ typedef enum {
 #define XDG_DESKTOP_PORTAL_ERROR xdg_desktop_portal_error_quark ()
 
 GQuark  xdg_desktop_portal_error_quark (void);
+
+glong str_distance (const char *a,
+                    const char *b);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]