From 5104a9b2ceea8635178639e719d5c9e0243c25ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 4 Nov 2020 16:09:42 +0100 Subject: [PATCH] remote-desktop/session: Add support for SelectionTransfer/Write When a transfer request is done to the MetaSelectionSourceRemote source, it's translated to a SelectionTransfer signal, which the remote desktop server is supposed to respond to with SelectionWrite. A timeout (set to 15 seconds) is added to handle too long timeouts, which cancels the transfer request. Part-of: --- clutter/clutter/clutter-private.h | 6 + src/backends/meta-remote-desktop-session.c | 313 +++++++++++++++++++-- src/backends/meta-remote-desktop-session.h | 4 + src/core/meta-selection-source-remote.c | 33 ++- src/core/meta-selection-source-remote.h | 7 + 5 files changed, 340 insertions(+), 23 deletions(-) diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h index e1c0d8388..f596ded9c 100644 --- a/clutter/clutter/clutter-private.h +++ b/clutter/clutter/clutter-private.h @@ -312,6 +312,12 @@ s2us (int64_t s) return ms2us (s * 1000); } +static inline int64_t +s2ms (int64_t s) +{ + return (int64_t) ms (s * 1000); +} + G_END_DECLS #endif /* __CLUTTER_PRIVATE_H__ */ diff --git a/src/backends/meta-remote-desktop-session.c b/src/backends/meta-remote-desktop-session.c index fcdf8d9fa..ba6e84ae3 100644 --- a/src/backends/meta-remote-desktop-session.c +++ b/src/backends/meta-remote-desktop-session.c @@ -40,12 +40,15 @@ #include "cogl/cogl.h" #include "core/display-private.h" #include "core/meta-selection-private.h" +#include "core/meta-selection-source-remote.h" #include "meta/meta-backend.h" #include "meta-dbus-remote-desktop.h" #define META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/org/gnome/Mutter/RemoteDesktop/Session" +#define TRANSFER_REQUEST_CLEANUP_TIMEOUT_MS (s2ms (15)) + typedef enum _MetaRemoteDesktopNotifyAxisFlags { META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_NONE = 0, @@ -86,6 +89,9 @@ struct _MetaRemoteDesktopSession gulong owner_changed_handler_id; SelectionReadData *read_data; unsigned int transfer_serial; + MetaSelectionSourceRemote *current_source; + GHashTable *transfer_requests; + guint transfer_request_timeout_id; }; static void @@ -810,6 +816,31 @@ handle_notify_touch_up (MetaDBusRemoteDesktopSession *skeleton, return TRUE; } +static MetaSelectionSourceRemote * +create_remote_desktop_source (MetaRemoteDesktopSession *session, + GVariant *mime_types_variant, + GError **error) +{ + GVariantIter iter; + char *mime_type; + GList *mime_types = NULL; + + g_variant_iter_init (&iter, mime_types_variant); + if (g_variant_iter_n_children (&iter) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "No mime types in mime types list"); + return NULL; + } + + while (g_variant_iter_next (&iter, "s", &mime_type)) + mime_types = g_list_prepend (mime_types, mime_type); + + mime_types = g_list_reverse (mime_types); + + return meta_selection_source_remote_new (session, mime_types); +} + static const char * mime_types_to_string (char **formats, char *buf, @@ -831,8 +862,16 @@ mime_types_to_string (char **formats, return buf; } +static gboolean +is_own_source (MetaRemoteDesktopSession *session, + MetaSelectionSource *source) +{ + return source && source == META_SELECTION_SOURCE (session->current_source); +} + static GVariant * -generate_owner_changed_variant (char **mime_types_array) +generate_owner_changed_variant (char **mime_types_array, + gboolean is_own_source) { GVariantBuilder builder; @@ -841,6 +880,8 @@ generate_owner_changed_variant (char **mime_types_array) { g_variant_builder_add (&builder, "{sv}", "mime-types", g_variant_new ("(^as)", mime_types_array)); + g_variant_builder_add (&builder, "{sv}", "session-is-owner", + g_variant_new_boolean (is_own_source)); } return g_variant_builder_end (&builder); @@ -875,16 +916,19 @@ on_selection_owner_changed (MetaSelection *selection, } meta_topic (META_DEBUG_REMOTE_DESKTOP, - "Clipboard owner changed, owner: %p (%s), mime types: [%s], " + "Clipboard owner changed, owner: %p (%s, is own? %s), mime types: [%s], " "notifying %s", owner, owner ? g_type_name_from_instance ((GTypeInstance *) owner) : "NULL", + is_own_source (session, owner) ? "yes" : "no", mime_types_to_string (mime_types_array, log_buf, G_N_ELEMENTS (log_buf)), session->peer_name); - options_variant = generate_owner_changed_variant (mime_types_array); + options_variant = + generate_owner_changed_variant (mime_types_array, + is_own_source (session, owner)); object_path = g_dbus_interface_skeleton_get_object_path ( G_DBUS_INTERFACE_SKELETON (session)); @@ -895,8 +939,6 @@ on_selection_owner_changed (MetaSelection *selection, "SelectionOwnerChanged", g_variant_new ("(@a{sv})", options_variant), NULL); - - session->transfer_serial++; } static gboolean @@ -905,6 +947,8 @@ handle_enable_clipboard (MetaDBusRemoteDesktopSession *skeleton, GVariant *arg_options) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); + GVariant *mime_types_variant; + g_autoptr (GError) error = NULL; MetaDisplay *display = meta_get_display (); MetaSelection *selection = meta_display_get_selection (display); @@ -920,6 +964,35 @@ handle_enable_clipboard (MetaDBusRemoteDesktopSession *skeleton, return TRUE; } + mime_types_variant = g_variant_lookup_value (arg_options, + "mime-types", + G_VARIANT_TYPE_STRING_ARRAY); + if (mime_types_variant) + { + g_autoptr (MetaSelectionSourceRemote) source_remote = NULL; + + source_remote = create_remote_desktop_source (session, + mime_types_variant, + &error); + if (!source_remote) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Invalid mime type list: %s", + error->message); + return TRUE; + } + + meta_topic (META_DEBUG_REMOTE_DESKTOP, + "Setting remote desktop clipboard source: %p from %s", + source_remote, session->peer_name); + + g_set_object (&session->current_source, source_remote); + meta_selection_set_owner (selection, + META_SELECTION_CLIPBOARD, + META_SELECTION_SOURCE (source_remote)); + } + session->is_clipboard_enabled = TRUE; session->owner_changed_handler_id = g_signal_connect (selection, "owner-changed", @@ -932,6 +1005,64 @@ handle_enable_clipboard (MetaDBusRemoteDesktopSession *skeleton, return TRUE; } +static gboolean +cancel_transfer_request (gpointer key, + gpointer value, + gpointer user_data) +{ + GTask *task = G_TASK (value); + MetaRemoteDesktopSession *session = user_data; + + meta_selection_source_remote_cancel_transfer (session->current_source, + task); + + return TRUE; +} + +static void +meta_remote_desktop_session_cancel_transfer_requests (MetaRemoteDesktopSession *session) +{ + g_return_if_fail (session->current_source); + + g_hash_table_foreach_remove (session->transfer_requests, + cancel_transfer_request, + session); +} + +static gboolean +transfer_request_cleanup_timout (gpointer user_data) +{ + MetaRemoteDesktopSession *session = user_data; + + meta_topic (META_DEBUG_REMOTE_DESKTOP, + "Cancel unanswered SelectionTransfer requests for %s, " + "waited for %.02f seconds already", + session->peer_name, + TRANSFER_REQUEST_CLEANUP_TIMEOUT_MS / 1000.0); + + meta_remote_desktop_session_cancel_transfer_requests (session); + + session->transfer_request_timeout_id = 0; + return G_SOURCE_REMOVE; +} + +static void +reset_current_selection_source (MetaRemoteDesktopSession *session) +{ + MetaDisplay *display = meta_get_display (); + MetaSelection *selection = meta_display_get_selection (display); + + if (!session->current_source) + return; + + meta_selection_unset_owner (selection, + META_SELECTION_CLIPBOARD, + META_SELECTION_SOURCE (session->current_source)); + meta_remote_desktop_session_cancel_transfer_requests (session); + g_clear_handle_id (&session->transfer_request_timeout_id, g_source_remove); + g_clear_object (&session->current_source); +} + static void cancel_selection_read (MetaRemoteDesktopSession *session) { @@ -964,6 +1095,7 @@ handle_disable_clipboard (MetaDBusRemoteDesktopSession *skeleton, } g_clear_signal_handler (&session->owner_changed_handler_id, selection); + reset_current_selection_source (session); cancel_selection_read (session); meta_dbus_remote_desktop_session_complete_disable_clipboard (skeleton, @@ -978,10 +1110,8 @@ handle_set_selection (MetaDBusRemoteDesktopSession *skeleton, GVariant *arg_options) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); - - meta_topic (META_DEBUG_REMOTE_DESKTOP, - "Set selection for %s", - g_dbus_method_invocation_get_sender (invocation)); + g_autoptr (GVariant) mime_types_variant = NULL; + g_autoptr (GError) error = NULL; if (!session->is_clipboard_enabled) { @@ -991,19 +1121,114 @@ handle_set_selection (MetaDBusRemoteDesktopSession *skeleton, return TRUE; } + if (session->current_source) + { + meta_remote_desktop_session_cancel_transfer_requests (session); + g_clear_handle_id (&session->transfer_request_timeout_id, + g_source_remove); + } + + mime_types_variant = g_variant_lookup_value (arg_options, + "mime-types", + G_VARIANT_TYPE_STRING_ARRAY); + if (mime_types_variant) + { + g_autoptr (MetaSelectionSourceRemote) source_remote = NULL; + MetaDisplay *display = meta_get_display (); + + source_remote = create_remote_desktop_source (session, + mime_types_variant, + &error); + if (!source_remote) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Invalid format list: %s", + error->message); + return TRUE; + } + + meta_topic (META_DEBUG_REMOTE_DESKTOP, + "Set selection for %s to %p", + g_dbus_method_invocation_get_sender (invocation), + source_remote); + + g_set_object (&session->current_source, source_remote); + meta_selection_set_owner (meta_display_get_selection (display), + META_SELECTION_CLIPBOARD, + META_SELECTION_SOURCE (source_remote)); + } + else + { + meta_topic (META_DEBUG_REMOTE_DESKTOP, + "Unset selection for %s", + g_dbus_method_invocation_get_sender (invocation)); + + reset_current_selection_source (session); + } + meta_dbus_remote_desktop_session_complete_set_selection (skeleton, invocation); return TRUE; } +static void +reset_transfer_cleanup_timeout (MetaRemoteDesktopSession *session) +{ + g_clear_handle_id (&session->transfer_request_timeout_id, g_source_remove); + session->transfer_request_timeout_id = + g_timeout_add (TRANSFER_REQUEST_CLEANUP_TIMEOUT_MS, + transfer_request_cleanup_timout, + session); +} + +void +meta_remote_desktop_session_request_transfer (MetaRemoteDesktopSession *session, + const char *mime_type, + GTask *task) +{ + const char *object_path; + + session->transfer_serial++; + + meta_topic (META_DEBUG_REMOTE_DESKTOP, + "Emit SelectionTransfer ('%s', %u) for %s", + mime_type, + session->transfer_serial, + session->peer_name); + + g_hash_table_insert (session->transfer_requests, + GUINT_TO_POINTER (session->transfer_serial), + task); + reset_transfer_cleanup_timeout (session); + + object_path = g_dbus_interface_skeleton_get_object_path ( + G_DBUS_INTERFACE_SKELETON (session)); + g_dbus_connection_emit_signal (session->connection, + NULL, + object_path, + "org.gnome.Mutter.RemoteDesktop.Session", + "SelectionTransfer", + g_variant_new ("(su)", + mime_type, + session->transfer_serial), + NULL); +} + static gboolean handle_selection_write (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, - GUnixFDList *fd_list, + GUnixFDList *fd_list_in, unsigned int serial) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); + g_autoptr (GError) error = NULL; + int pipe_fds[2]; + g_autoptr (GUnixFDList) fd_list = NULL; + int fd_idx; + GVariant *fd_variant; + GTask *task; meta_topic (META_DEBUG_REMOTE_DESKTOP, "Write selection for %s", @@ -1017,22 +1242,62 @@ handle_selection_write (MetaDBusRemoteDesktopSession *skeleton, return TRUE; } - if (session->transfer_serial != serial) + if (!session->current_source) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, - "Provided transfer serial %u " - "doesn't match current current " - "transfer serial %u", - serial, - session->transfer_serial); + "No current selection owned"); return TRUE; } + if (!g_hash_table_steal_extended (session->transfer_requests, + GUINT_TO_POINTER (serial), + NULL, + (gpointer *) &task)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Transfer serial %u doesn't match " + "any transfer request", + serial); + return TRUE; + } + + if (!g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Failed open pipe: %s", + error->message); + return TRUE; + } + + if (!g_unix_set_fd_nonblocking (pipe_fds[0], TRUE, &error)) + { + close (pipe_fds[0]); + close (pipe_fds[1]); + + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Failed to make pipe non-blocking: %s", + error->message); + return TRUE; + } + + fd_list = g_unix_fd_list_new (); + + fd_idx = g_unix_fd_list_append (fd_list, pipe_fds[1], NULL); + close (pipe_fds[1]); + fd_variant = g_variant_new_handle (fd_idx); + + meta_selection_source_remote_complete_transfer (session->current_source, + pipe_fds[0], + task); + meta_dbus_remote_desktop_session_complete_selection_write (skeleton, invocation, - NULL, - NULL); + fd_list, + fd_variant); return TRUE; } @@ -1132,6 +1397,14 @@ handle_selection_read (MetaDBusRemoteDesktopSession *skeleton, return TRUE; } + if (is_own_source (session, source)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Tried to read own selection"); + return TRUE; + } + if (session->read_data) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, @@ -1233,7 +1506,9 @@ meta_remote_desktop_session_finalize (GObject *object) g_assert (!meta_remote_desktop_session_is_running (session)); g_clear_signal_handler (&session->owner_changed_handler_id, selection); + reset_current_selection_source (session); cancel_selection_read (session); + g_hash_table_unref (session->transfer_requests); g_clear_object (&session->handle); g_free (session->peer_name); @@ -1260,6 +1535,8 @@ meta_remote_desktop_session_init (MetaRemoteDesktopSession *session) session->object_path = g_strdup_printf (META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/u%u", ++global_session_number); + + session->transfer_requests = g_hash_table_new (NULL, NULL); } static void diff --git a/src/backends/meta-remote-desktop-session.h b/src/backends/meta-remote-desktop-session.h index 1edb3739b..7af9c4897 100644 --- a/src/backends/meta-remote-desktop-session.h +++ b/src/backends/meta-remote-desktop-session.h @@ -47,6 +47,10 @@ gboolean meta_remote_desktop_session_register_screen_cast (MetaRemoteDesktopSess MetaScreenCastSession *screen_cast_session, GError **error); +void meta_remote_desktop_session_request_transfer (MetaRemoteDesktopSession *session, + const char *mime_type, + GTask *task); + void meta_remote_desktop_session_close (MetaRemoteDesktopSession *session); MetaRemoteDesktopSession * meta_remote_desktop_session_new (MetaRemoteDesktop *remote_desktop, diff --git a/src/core/meta-selection-source-remote.c b/src/core/meta-selection-source-remote.c index 828c8c8c8..aa9f9e60a 100644 --- a/src/core/meta-selection-source-remote.c +++ b/src/core/meta-selection-source-remote.c @@ -22,6 +22,8 @@ #include "core/meta-selection-source-remote.h" +#include + #include "backends/meta-remote-desktop-session.h" struct _MetaSelectionSourceRemote @@ -56,16 +58,16 @@ meta_selection_source_remote_read_async (MetaSelectionSource *source, GAsyncReadyCallback callback, gpointer user_data) { + MetaSelectionSourceRemote *source_remote = + META_SELECTION_SOURCE_REMOTE (source); GTask *task; - GInputStream *stream; task = g_task_new (source, cancellable, callback, user_data); g_task_set_source_tag (task, meta_selection_source_remote_read_async); - stream = g_memory_input_stream_new_from_data ("place holder text", -1, NULL); - g_task_return_pointer (task, stream, g_object_unref); - - g_object_unref (task); + meta_remote_desktop_session_request_transfer (source_remote->session, + mimetype, + task); } static GInputStream * @@ -80,6 +82,27 @@ meta_selection_source_remote_read_finish (MetaSelectionSource *source, return g_task_propagate_pointer (G_TASK (result), error); } +void +meta_selection_source_remote_complete_transfer (MetaSelectionSourceRemote *source_remote, + int fd, + GTask *task) +{ + GInputStream *stream; + + stream = g_unix_input_stream_new (fd, TRUE); + g_task_return_pointer (task, stream, g_object_unref); + g_object_unref (task); +} + +void +meta_selection_source_remote_cancel_transfer (MetaSelectionSourceRemote *source_remote, + GTask *task) +{ + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Remote selection transfer was cancelled"); + g_object_unref (task); +} + static GList * meta_selection_source_remote_get_mimetypes (MetaSelectionSource *source) { diff --git a/src/core/meta-selection-source-remote.h b/src/core/meta-selection-source-remote.h index 28a112758..51e66957b 100644 --- a/src/core/meta-selection-source-remote.h +++ b/src/core/meta-selection-source-remote.h @@ -30,6 +30,13 @@ G_DECLARE_FINAL_TYPE (MetaSelectionSourceRemote, META, SELECTION_SOURCE_REMOTE, MetaSelectionSource) +void meta_selection_source_remote_complete_transfer (MetaSelectionSourceRemote *source_remote, + int fd, + GTask *task); + +void meta_selection_source_remote_cancel_transfer (MetaSelectionSourceRemote *source_remote, + GTask *task); + MetaSelectionSourceRemote * meta_selection_source_remote_new (MetaRemoteDesktopSession *session, GList *mime_types);