725 lines
24 KiB
Diff
725 lines
24 KiB
Diff
From af25561e0f4e5aa2c3460ce7448b0c580f28532a Mon Sep 17 00:00:00 2001
|
|
From: Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
|
Date: Tue, 28 Sep 2021 21:04:28 -0300
|
|
Subject: [PATCH] Implement screencast stream restoration
|
|
|
|
Handle receiving 'restore_data' and 'persist_mode'.
|
|
|
|
For monitors, use the match string introduced by the previous
|
|
commit to identify individual monitors. For windows, do a strict
|
|
check on the app id, and a best-match approach to window titles.
|
|
Virtual monitors don't have any particular data attached to them,
|
|
so they are merely stored as "virtual" and restored.
|
|
---
|
|
src/screencast.c | 327 +++++++++++++++++++++++++++++++++++++++-
|
|
src/screencast.h | 7 +
|
|
src/screencastdialog.c | 14 +-
|
|
src/screencastdialog.h | 5 +-
|
|
src/screencastwidget.c | 23 +++
|
|
src/screencastwidget.h | 6 +
|
|
src/screencastwidget.ui | 8 +
|
|
src/utils.c | 77 ++++++++++
|
|
src/utils.h | 3 +
|
|
9 files changed, 456 insertions(+), 14 deletions(-)
|
|
|
|
diff --git a/src/screencast.c b/src/screencast.c
|
|
index 2713d26..55a3e5c 100644
|
|
--- a/src/screencast.c
|
|
+++ b/src/screencast.c
|
|
@@ -33,8 +33,15 @@
|
|
#include "externalwindow.h"
|
|
#include "request.h"
|
|
#include "session.h"
|
|
+#include "shellintrospect.h"
|
|
#include "utils.h"
|
|
|
|
+#define RESTORE_FORMAT_VERSION 1
|
|
+#define RESTORE_VARIANT_TYPE "(xxa(uuv))"
|
|
+#define MONITOR_TYPE "s"
|
|
+#define WINDOW_TYPE "(ss)"
|
|
+#define VIRTUAL_TYPE "b"
|
|
+
|
|
typedef struct _ScreenCastDialogHandle ScreenCastDialogHandle;
|
|
|
|
typedef struct _ScreenCastSession
|
|
@@ -49,6 +56,13 @@ typedef struct _ScreenCastSession
|
|
|
|
ScreenCastSelection select;
|
|
|
|
+ ScreenCastPersistMode persist_mode;
|
|
+ GPtrArray *streams_to_restore;
|
|
+ struct {
|
|
+ GVariant *data;
|
|
+ int64_t creation_time;
|
|
+ } restored;
|
|
+
|
|
GDBusMethodInvocation *start_invocation;
|
|
ScreenCastDialogHandle *dialog_handle;
|
|
} ScreenCastSession;
|
|
@@ -99,6 +113,68 @@ screen_cast_dialog_handle_close (ScreenCastDialogHandle *dialog_handle)
|
|
screen_cast_dialog_handle_free (dialog_handle);
|
|
}
|
|
|
|
+static GVariant *
|
|
+serialize_streams_as_restore_data (ScreenCastSession *screen_cast_session,
|
|
+ GPtrArray *streams)
|
|
+{
|
|
+ GVariantBuilder restore_data_builder;
|
|
+ GVariantBuilder impl_builder;
|
|
+ int64_t creation_time;
|
|
+ int64_t last_used_time;
|
|
+ guint i;
|
|
+
|
|
+ if (!streams || streams->len == 0)
|
|
+ return NULL;
|
|
+
|
|
+ last_used_time = g_get_real_time ();
|
|
+ if (screen_cast_session->restored.creation_time != -1)
|
|
+ creation_time = screen_cast_session->restored.creation_time;
|
|
+ else
|
|
+ creation_time = g_get_real_time ();
|
|
+
|
|
+ g_variant_builder_init (&impl_builder, G_VARIANT_TYPE (RESTORE_VARIANT_TYPE));
|
|
+ g_variant_builder_add (&impl_builder, "x", creation_time);
|
|
+ g_variant_builder_add (&impl_builder, "x", last_used_time);
|
|
+
|
|
+ g_variant_builder_open (&impl_builder, G_VARIANT_TYPE ("a(uuv)"));
|
|
+ for (i = 0; i < streams->len; i++)
|
|
+ {
|
|
+ ScreenCastStreamInfo *info = g_ptr_array_index (streams, i);
|
|
+ GVariant *stream_variant;
|
|
+ Monitor *monitor;
|
|
+ Window *window;
|
|
+
|
|
+ switch (info->type)
|
|
+ {
|
|
+ case SCREEN_CAST_SOURCE_TYPE_MONITOR:
|
|
+ monitor = info->data.monitor;
|
|
+ stream_variant = g_variant_new (MONITOR_TYPE,
|
|
+ monitor_get_match_string (monitor));
|
|
+ break;
|
|
+
|
|
+ case SCREEN_CAST_SOURCE_TYPE_WINDOW:
|
|
+ window = info->data.window;
|
|
+ stream_variant = g_variant_new (WINDOW_TYPE,
|
|
+ window_get_app_id (window),
|
|
+ window_get_title (window));
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ g_variant_builder_add (&impl_builder,
|
|
+ "(uuv)",
|
|
+ i,
|
|
+ info->type,
|
|
+ stream_variant);
|
|
+ }
|
|
+ g_variant_builder_close (&impl_builder);
|
|
+
|
|
+ g_variant_builder_init (&restore_data_builder, G_VARIANT_TYPE ("(suv)"));
|
|
+ g_variant_builder_add (&restore_data_builder, "s", "GNOME");
|
|
+ g_variant_builder_add (&restore_data_builder, "u", RESTORE_FORMAT_VERSION);
|
|
+ g_variant_builder_add (&restore_data_builder, "v", g_variant_builder_end (&impl_builder));
|
|
+ return g_variant_builder_end (&restore_data_builder);
|
|
+}
|
|
+
|
|
static void
|
|
cancel_start_session (ScreenCastSession *screen_cast_session,
|
|
int response)
|
|
@@ -142,6 +218,22 @@ on_gnome_screen_cast_session_ready (GnomeScreenCastSession *gnome_screen_cast_se
|
|
"streams",
|
|
g_variant_builder_end (&streams_builder));
|
|
|
|
+ if (screen_cast_session->persist_mode != SCREEN_CAST_PERSIST_MODE_NONE)
|
|
+ {
|
|
+ g_autoptr(GPtrArray) streams = g_steal_pointer (&screen_cast_session->streams_to_restore);
|
|
+ GVariant *restore_data;
|
|
+
|
|
+ restore_data = serialize_streams_as_restore_data (screen_cast_session, streams);
|
|
+
|
|
+ if (restore_data)
|
|
+ {
|
|
+ g_variant_builder_add (&results_builder, "{sv}", "persist_mode",
|
|
+ g_variant_new_uint32 (screen_cast_session->persist_mode));
|
|
+ g_variant_builder_add (&results_builder, "{sv}", "restore_data",
|
|
+ g_variant_new_variant (restore_data));
|
|
+ }
|
|
+ }
|
|
+
|
|
xdp_impl_screen_cast_complete_start (XDP_IMPL_SCREEN_CAST (impl),
|
|
screen_cast_session->start_invocation, 0,
|
|
g_variant_builder_end (&results_builder));
|
|
@@ -178,6 +270,9 @@ start_session (ScreenCastSession *screen_cast_session,
|
|
G_CALLBACK (on_gnome_screen_cast_session_closed),
|
|
screen_cast_session);
|
|
|
|
+ if (screen_cast_session->persist_mode != SCREEN_CAST_PERSIST_MODE_NONE)
|
|
+ screen_cast_session->streams_to_restore = g_ptr_array_ref (streams);
|
|
+
|
|
if (!gnome_screen_cast_session_record_selections (gnome_screen_cast_session,
|
|
streams,
|
|
&screen_cast_session->select,
|
|
@@ -193,6 +288,7 @@ start_session (ScreenCastSession *screen_cast_session,
|
|
static void
|
|
on_screen_cast_dialog_done_cb (GtkWidget *widget,
|
|
int dialog_response,
|
|
+ ScreenCastPersistMode persist_mode,
|
|
GPtrArray *streams,
|
|
ScreenCastDialogHandle *dialog_handle)
|
|
{
|
|
@@ -218,8 +314,12 @@ on_screen_cast_dialog_done_cb (GtkWidget *widget,
|
|
|
|
if (response == 0)
|
|
{
|
|
+ ScreenCastSession *screen_cast_session = dialog_handle->session;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
+ screen_cast_session->persist_mode = MIN (screen_cast_session->persist_mode,
|
|
+ persist_mode);
|
|
+
|
|
if (!start_session (dialog_handle->session, streams, &error))
|
|
{
|
|
g_warning ("Failed to start session: %s", error->message);
|
|
@@ -269,7 +369,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);
|
|
|
|
@@ -295,6 +396,161 @@ create_screen_cast_dialog (ScreenCastSession *session,
|
|
return dialog_handle;
|
|
}
|
|
|
|
+static Monitor *
|
|
+find_monitor_by_string (const char *monitor_string)
|
|
+{
|
|
+ DisplayStateTracker *display_state_tracker = display_state_tracker_get ();
|
|
+ GList *l;
|
|
+
|
|
+ for (l = display_state_tracker_get_logical_monitors (display_state_tracker);
|
|
+ l;
|
|
+ l = l->next)
|
|
+ {
|
|
+ LogicalMonitor *logical_monitor = l->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 *l;
|
|
+
|
|
+ best_match = NULL;
|
|
+ best_match_distance = G_MAXLONG;
|
|
+
|
|
+ for (l = shell_introspect_get_windows (shell_introspect); l; l = l->next)
|
|
+ {
|
|
+ Window *window = l->data;
|
|
+ glong distance;
|
|
+
|
|
+ if (g_strcmp0 (window_get_app_id (window), app_id) != 0)
|
|
+ continue;
|
|
+
|
|
+ distance = str_distance (window_get_title (window), title);
|
|
+
|
|
+ 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)
|
|
+
|
|
+{
|
|
+ ScreenCastStreamInfo *info;
|
|
+ g_autoptr(GVariantIter) array_iter = NULL;
|
|
+ g_autoptr(GPtrArray) streams = NULL;
|
|
+ g_autoptr(GError) error = NULL;
|
|
+ ScreenCastSourceType source_type;
|
|
+ GVariant *data;
|
|
+ uint32_t id;
|
|
+ int64_t creation_time;
|
|
+ int64_t last_used_time;
|
|
+
|
|
+ if (!screen_cast_session->restored.data)
|
|
+ return FALSE;
|
|
+
|
|
+ streams = g_ptr_array_new_with_free_func (g_free);
|
|
+
|
|
+ g_variant_get (screen_cast_session->restored.data,
|
|
+ RESTORE_VARIANT_TYPE,
|
|
+ &creation_time,
|
|
+ &last_used_time,
|
|
+ &array_iter);
|
|
+
|
|
+ while (g_variant_iter_next (array_iter, "(uuv)", &id, &source_type, &data))
|
|
+ {
|
|
+ switch (source_type)
|
|
+ {
|
|
+ case SCREEN_CAST_SOURCE_TYPE_MONITOR:
|
|
+ {
|
|
+ if (!(screen_cast_session->select.source_types & SCREEN_CAST_SOURCE_TYPE_MONITOR) ||
|
|
+ !g_variant_check_format_string (data, MONITOR_TYPE, FALSE))
|
|
+ goto fail;
|
|
+
|
|
+ const char *match_string = g_variant_get_string (data, NULL);
|
|
+ Monitor *monitor = find_monitor_by_string (match_string);
|
|
+
|
|
+ if (!monitor)
|
|
+ goto fail;
|
|
+
|
|
+ info = g_new0 (ScreenCastStreamInfo, 1);
|
|
+ info->type = SCREEN_CAST_SOURCE_TYPE_MONITOR;
|
|
+ info->data.monitor = monitor;
|
|
+ g_ptr_array_add (streams, info);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case SCREEN_CAST_SOURCE_TYPE_WINDOW:
|
|
+ {
|
|
+ if (!(screen_cast_session->select.source_types & SCREEN_CAST_SOURCE_TYPE_WINDOW) ||
|
|
+ !g_variant_check_format_string (data, WINDOW_TYPE, FALSE))
|
|
+ goto fail;
|
|
+
|
|
+ const char *app_id = NULL;
|
|
+ const char *title = NULL;
|
|
+ Window *window;
|
|
+
|
|
+ g_variant_get (data, "(&s&s)", &app_id, &title);
|
|
+
|
|
+ window = find_best_window_by_app_id_and_title (app_id, title);
|
|
+
|
|
+ if (!window)
|
|
+ goto fail;
|
|
+
|
|
+ info = g_new0 (ScreenCastStreamInfo, 1);
|
|
+ info->type = SCREEN_CAST_SOURCE_TYPE_WINDOW;
|
|
+ info->data.window = window;
|
|
+ g_ptr_array_add (streams, info);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ goto fail;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ screen_cast_session->restored.creation_time = creation_time;
|
|
+
|
|
+ start_session (screen_cast_session, streams, &error);
|
|
+
|
|
+ if (error)
|
|
+ {
|
|
+ g_warning ("Error restoring stream from session: %s", error->message);
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ return TRUE;
|
|
+
|
|
+fail:
|
|
+ return FALSE;
|
|
+}
|
|
+
|
|
static gboolean
|
|
handle_start (XdpImplScreenCast *object,
|
|
GDBusMethodInvocation *invocation,
|
|
@@ -307,7 +563,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);
|
|
@@ -329,14 +584,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;
|
|
|
|
@@ -356,6 +616,9 @@ handle_select_sources (XdpImplScreenCast *object,
|
|
const char *arg_app_id,
|
|
GVariant *arg_options)
|
|
{
|
|
+ g_autofree gchar *provider = NULL;
|
|
+ g_autoptr(GVariant) restore_data = NULL;
|
|
+ ScreenCastSession *screen_cast_session;
|
|
Session *session;
|
|
int response;
|
|
uint32_t types;
|
|
@@ -364,6 +627,7 @@ handle_select_sources (XdpImplScreenCast *object,
|
|
ScreenCastSelection select;
|
|
GVariantBuilder results_builder;
|
|
GVariant *results;
|
|
+ uint32_t version;
|
|
|
|
session = lookup_session (arg_session_handle);
|
|
if (!session)
|
|
@@ -427,6 +691,21 @@ 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);
|
|
+
|
|
+ if (g_variant_lookup (arg_options, "restore_data", "(suv)", &provider, &version, &restore_data))
|
|
+ {
|
|
+ if (!g_variant_check_format_string (restore_data, "(suv)", FALSE))
|
|
+ {
|
|
+ g_warning ("Cannot parse restore data, ignoring");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (g_strcmp0 (provider, "GNOME") == 0 && version == RESTORE_FORMAT_VERSION)
|
|
+ screen_cast_session->restored.data = g_variant_ref (restore_data);
|
|
+ }
|
|
+
|
|
out:
|
|
g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT);
|
|
results = g_variant_builder_end (&results_builder);
|
|
@@ -567,6 +846,8 @@ screen_cast_session_finalize (GObject *object)
|
|
{
|
|
ScreenCastSession *screen_cast_session = (ScreenCastSession *)object;
|
|
|
|
+ g_clear_pointer (&screen_cast_session->streams_to_restore, g_ptr_array_unref);
|
|
+ g_clear_pointer (&screen_cast_session->restored.data, g_variant_unref);
|
|
g_clear_object (&screen_cast_session->gnome_screen_cast_session);
|
|
|
|
G_OBJECT_CLASS (screen_cast_session_parent_class)->finalize (object);
|
|
@@ -575,6 +856,8 @@ screen_cast_session_finalize (GObject *object)
|
|
static void
|
|
screen_cast_session_init (ScreenCastSession *screen_cast_session)
|
|
{
|
|
+ screen_cast_session->persist_mode = SCREEN_CAST_PERSIST_MODE_NONE;
|
|
+ screen_cast_session->restored.creation_time = -1;
|
|
}
|
|
|
|
static void
|
|
@@ -594,6 +877,14 @@ gboolean
|
|
screen_cast_init (GDBusConnection *connection,
|
|
GError **error)
|
|
{
|
|
+ /*
|
|
+ * Ensure ShellIntrospect and DisplayStateTracker are initialized before
|
|
+ * any screencast session is created to avoid race conditions when restoring
|
|
+ * previous streams.
|
|
+ */
|
|
+ display_state_tracker_get ();
|
|
+ shell_introspect_get ();
|
|
+
|
|
impl_connection = connection;
|
|
gnome_screen_cast = gnome_screen_cast_new (connection);
|
|
|
|
diff --git a/src/screencast.h b/src/screencast.h
|
|
index a9be16b..d78066e 100644
|
|
--- a/src/screencast.h
|
|
+++ b/src/screencast.h
|
|
@@ -38,6 +38,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 3e3b064..d80329e 100644
|
|
--- a/src/screencastdialog.c
|
|
+++ b/src/screencastdialog.c
|
|
@@ -59,6 +59,7 @@ static void
|
|
button_clicked (GtkWidget *button,
|
|
ScreenCastDialog *dialog)
|
|
{
|
|
+ ScreenCastPersistMode persist_mode;
|
|
g_autoptr(GPtrArray) streams = NULL;
|
|
int response;
|
|
|
|
@@ -71,14 +72,16 @@ button_clicked (GtkWidget *button,
|
|
|
|
response = GTK_RESPONSE_OK;
|
|
streams = screen_cast_widget_get_selected_streams (screen_cast_widget);
|
|
+ persist_mode = screen_cast_widget_get_persist_mode (screen_cast_widget);
|
|
}
|
|
else
|
|
{
|
|
response = GTK_RESPONSE_CANCEL;
|
|
+ persist_mode = SCREEN_CAST_PERSIST_MODE_NONE;
|
|
streams = NULL;
|
|
}
|
|
|
|
- g_signal_emit (dialog, signals[DONE], 0, response, streams);
|
|
+ g_signal_emit (dialog, signals[DONE], 0, response, persist_mode, streams);
|
|
}
|
|
|
|
static void
|
|
@@ -120,7 +123,8 @@ screen_cast_dialog_class_init (ScreenCastDialogClass *klass)
|
|
0,
|
|
NULL, NULL,
|
|
NULL,
|
|
- G_TYPE_NONE, 2,
|
|
+ G_TYPE_NONE, 3,
|
|
+ G_TYPE_INT,
|
|
G_TYPE_INT,
|
|
G_TYPE_PTR_ARRAY);
|
|
|
|
@@ -143,8 +147,9 @@ screen_cast_dialog_init (ScreenCastDialog *dialog)
|
|
}
|
|
|
|
ScreenCastDialog *
|
|
-screen_cast_dialog_new (const char *app_id,
|
|
- ScreenCastSelection *select)
|
|
+screen_cast_dialog_new (const char *app_id,
|
|
+ ScreenCastSelection *select,
|
|
+ ScreenCastPersistMode persist_mode)
|
|
{
|
|
ScreenCastDialog *dialog;
|
|
ScreenCastWidget *screen_cast_widget;
|
|
@@ -155,6 +160,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;
|
|
}
|
|
diff --git a/src/screencastdialog.h b/src/screencastdialog.h
|
|
index 1fca470..c132ecf 100644
|
|
--- a/src/screencastdialog.h
|
|
+++ b/src/screencastdialog.h
|
|
@@ -26,5 +26,6 @@
|
|
G_DECLARE_FINAL_TYPE (ScreenCastDialog, screen_cast_dialog,
|
|
SCREEN_CAST, DIALOG, GtkWindow)
|
|
|
|
-ScreenCastDialog * screen_cast_dialog_new (const char *app_id,
|
|
- ScreenCastSelection *select);
|
|
+ScreenCastDialog * screen_cast_dialog_new (const char *app_id,
|
|
+ ScreenCastSelection *select,
|
|
+ ScreenCastPersistMode persist_mode);
|
|
diff --git a/src/screencastwidget.c b/src/screencastwidget.c
|
|
index 3119245..c100ad9 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;
|
|
|
|
@@ -454,6 +457,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);
|
|
@@ -635,3 +639,22 @@ screen_cast_widget_get_selected_streams (ScreenCastWidget *self)
|
|
|
|
return g_steal_pointer (&streams);
|
|
}
|
|
+
|
|
+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_NONE);
|
|
+}
|
|
+
|
|
+ScreenCastPersistMode
|
|
+screen_cast_widget_get_persist_mode (ScreenCastWidget *screen_cast_widget)
|
|
+{
|
|
+ if (!gtk_check_button_get_active (screen_cast_widget->persist_check))
|
|
+ return SCREEN_CAST_PERSIST_MODE_NONE;
|
|
+
|
|
+ return screen_cast_widget->persist_mode;
|
|
+}
|
|
diff --git a/src/screencastwidget.h b/src/screencastwidget.h
|
|
index ad95903..a963f99 100644
|
|
--- a/src/screencastwidget.h
|
|
+++ b/src/screencastwidget.h
|
|
@@ -40,3 +40,9 @@ void screen_cast_widget_set_source_types (ScreenCastWidget *screen_cast_widg
|
|
ScreenCastSourceType source_types);
|
|
|
|
GPtrArray *screen_cast_widget_get_selected_streams (ScreenCastWidget *self);
|
|
+
|
|
+void screen_cast_widget_set_persist_mode (ScreenCastWidget *screen_cast_widget,
|
|
+ ScreenCastPersistMode persist_mode);
|
|
+
|
|
+ScreenCastPersistMode
|
|
+screen_cast_widget_get_persist_mode (ScreenCastWidget *screen_cast_widget);
|
|
diff --git a/src/screencastwidget.ui b/src/screencastwidget.ui
|
|
index fb83b94..0a9028e 100644
|
|
--- a/src/screencastwidget.ui
|
|
+++ b/src/screencastwidget.ui
|
|
@@ -152,5 +152,13 @@
|
|
</child>
|
|
</object>
|
|
</child>
|
|
+
|
|
+ <!-- Persist permission -->
|
|
+ <child>
|
|
+ <object class="GtkCheckButton" id="persist_check">
|
|
+ <property name="active">True</property>
|
|
+ <property name="label" translatable="yes">Remember this selection</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);
|