1
0
Fork 0

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: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1552>
This commit is contained in:
Jonas Ådahl 2020-11-04 16:09:42 +01:00 committed by Marge Bot
parent d7c8535ac6
commit 5104a9b2ce
5 changed files with 340 additions and 23 deletions

View file

@ -312,6 +312,12 @@ s2us (int64_t s)
return ms2us (s * 1000); return ms2us (s * 1000);
} }
static inline int64_t
s2ms (int64_t s)
{
return (int64_t) ms (s * 1000);
}
G_END_DECLS G_END_DECLS
#endif /* __CLUTTER_PRIVATE_H__ */ #endif /* __CLUTTER_PRIVATE_H__ */

View file

@ -40,12 +40,15 @@
#include "cogl/cogl.h" #include "cogl/cogl.h"
#include "core/display-private.h" #include "core/display-private.h"
#include "core/meta-selection-private.h" #include "core/meta-selection-private.h"
#include "core/meta-selection-source-remote.h"
#include "meta/meta-backend.h" #include "meta/meta-backend.h"
#include "meta-dbus-remote-desktop.h" #include "meta-dbus-remote-desktop.h"
#define META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/org/gnome/Mutter/RemoteDesktop/Session" #define META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/org/gnome/Mutter/RemoteDesktop/Session"
#define TRANSFER_REQUEST_CLEANUP_TIMEOUT_MS (s2ms (15))
typedef enum _MetaRemoteDesktopNotifyAxisFlags typedef enum _MetaRemoteDesktopNotifyAxisFlags
{ {
META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_NONE = 0, META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_NONE = 0,
@ -86,6 +89,9 @@ struct _MetaRemoteDesktopSession
gulong owner_changed_handler_id; gulong owner_changed_handler_id;
SelectionReadData *read_data; SelectionReadData *read_data;
unsigned int transfer_serial; unsigned int transfer_serial;
MetaSelectionSourceRemote *current_source;
GHashTable *transfer_requests;
guint transfer_request_timeout_id;
}; };
static void static void
@ -810,6 +816,31 @@ handle_notify_touch_up (MetaDBusRemoteDesktopSession *skeleton,
return TRUE; 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 * static const char *
mime_types_to_string (char **formats, mime_types_to_string (char **formats,
char *buf, char *buf,
@ -831,8 +862,16 @@ mime_types_to_string (char **formats,
return buf; return buf;
} }
static gboolean
is_own_source (MetaRemoteDesktopSession *session,
MetaSelectionSource *source)
{
return source && source == META_SELECTION_SOURCE (session->current_source);
}
static GVariant * static GVariant *
generate_owner_changed_variant (char **mime_types_array) generate_owner_changed_variant (char **mime_types_array,
gboolean is_own_source)
{ {
GVariantBuilder builder; GVariantBuilder builder;
@ -841,6 +880,8 @@ generate_owner_changed_variant (char **mime_types_array)
{ {
g_variant_builder_add (&builder, "{sv}", "mime-types", g_variant_builder_add (&builder, "{sv}", "mime-types",
g_variant_new ("(^as)", mime_types_array)); 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); return g_variant_builder_end (&builder);
@ -875,16 +916,19 @@ on_selection_owner_changed (MetaSelection *selection,
} }
meta_topic (META_DEBUG_REMOTE_DESKTOP, 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", "notifying %s",
owner, owner,
owner ? g_type_name_from_instance ((GTypeInstance *) owner) owner ? g_type_name_from_instance ((GTypeInstance *) owner)
: "NULL", : "NULL",
is_own_source (session, owner) ? "yes" : "no",
mime_types_to_string (mime_types_array, log_buf, mime_types_to_string (mime_types_array, log_buf,
G_N_ELEMENTS (log_buf)), G_N_ELEMENTS (log_buf)),
session->peer_name); 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 ( object_path = g_dbus_interface_skeleton_get_object_path (
G_DBUS_INTERFACE_SKELETON (session)); G_DBUS_INTERFACE_SKELETON (session));
@ -895,8 +939,6 @@ on_selection_owner_changed (MetaSelection *selection,
"SelectionOwnerChanged", "SelectionOwnerChanged",
g_variant_new ("(@a{sv})", options_variant), g_variant_new ("(@a{sv})", options_variant),
NULL); NULL);
session->transfer_serial++;
} }
static gboolean static gboolean
@ -905,6 +947,8 @@ handle_enable_clipboard (MetaDBusRemoteDesktopSession *skeleton,
GVariant *arg_options) GVariant *arg_options)
{ {
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
GVariant *mime_types_variant;
g_autoptr (GError) error = NULL;
MetaDisplay *display = meta_get_display (); MetaDisplay *display = meta_get_display ();
MetaSelection *selection = meta_display_get_selection (display); MetaSelection *selection = meta_display_get_selection (display);
@ -920,6 +964,35 @@ handle_enable_clipboard (MetaDBusRemoteDesktopSession *skeleton,
return TRUE; 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->is_clipboard_enabled = TRUE;
session->owner_changed_handler_id = session->owner_changed_handler_id =
g_signal_connect (selection, "owner-changed", g_signal_connect (selection, "owner-changed",
@ -932,6 +1005,64 @@ handle_enable_clipboard (MetaDBusRemoteDesktopSession *skeleton,
return TRUE; 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 static void
cancel_selection_read (MetaRemoteDesktopSession *session) cancel_selection_read (MetaRemoteDesktopSession *session)
{ {
@ -964,6 +1095,7 @@ handle_disable_clipboard (MetaDBusRemoteDesktopSession *skeleton,
} }
g_clear_signal_handler (&session->owner_changed_handler_id, selection); g_clear_signal_handler (&session->owner_changed_handler_id, selection);
reset_current_selection_source (session);
cancel_selection_read (session); cancel_selection_read (session);
meta_dbus_remote_desktop_session_complete_disable_clipboard (skeleton, meta_dbus_remote_desktop_session_complete_disable_clipboard (skeleton,
@ -978,10 +1110,8 @@ handle_set_selection (MetaDBusRemoteDesktopSession *skeleton,
GVariant *arg_options) GVariant *arg_options)
{ {
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
g_autoptr (GVariant) mime_types_variant = NULL;
meta_topic (META_DEBUG_REMOTE_DESKTOP, g_autoptr (GError) error = NULL;
"Set selection for %s",
g_dbus_method_invocation_get_sender (invocation));
if (!session->is_clipboard_enabled) if (!session->is_clipboard_enabled)
{ {
@ -991,19 +1121,114 @@ handle_set_selection (MetaDBusRemoteDesktopSession *skeleton,
return TRUE; 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, meta_dbus_remote_desktop_session_complete_set_selection (skeleton,
invocation); invocation);
return TRUE; 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 static gboolean
handle_selection_write (MetaDBusRemoteDesktopSession *skeleton, handle_selection_write (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation, GDBusMethodInvocation *invocation,
GUnixFDList *fd_list, GUnixFDList *fd_list_in,
unsigned int serial) unsigned int serial)
{ {
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); 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, meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Write selection for %s", "Write selection for %s",
@ -1017,22 +1242,62 @@ handle_selection_write (MetaDBusRemoteDesktopSession *skeleton,
return TRUE; return TRUE;
} }
if (session->transfer_serial != serial) if (!session->current_source)
{ {
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED, G_DBUS_ERROR_FAILED,
"Provided transfer serial %u " "No current selection owned");
"doesn't match current current "
"transfer serial %u",
serial,
session->transfer_serial);
return TRUE; 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, meta_dbus_remote_desktop_session_complete_selection_write (skeleton,
invocation, invocation,
NULL, fd_list,
NULL); fd_variant);
return TRUE; return TRUE;
} }
@ -1132,6 +1397,14 @@ handle_selection_read (MetaDBusRemoteDesktopSession *skeleton,
return TRUE; 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) if (session->read_data)
{ {
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, 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_assert (!meta_remote_desktop_session_is_running (session));
g_clear_signal_handler (&session->owner_changed_handler_id, selection); g_clear_signal_handler (&session->owner_changed_handler_id, selection);
reset_current_selection_source (session);
cancel_selection_read (session); cancel_selection_read (session);
g_hash_table_unref (session->transfer_requests);
g_clear_object (&session->handle); g_clear_object (&session->handle);
g_free (session->peer_name); g_free (session->peer_name);
@ -1260,6 +1535,8 @@ meta_remote_desktop_session_init (MetaRemoteDesktopSession *session)
session->object_path = session->object_path =
g_strdup_printf (META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/u%u", g_strdup_printf (META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/u%u",
++global_session_number); ++global_session_number);
session->transfer_requests = g_hash_table_new (NULL, NULL);
} }
static void static void

View file

@ -47,6 +47,10 @@ gboolean meta_remote_desktop_session_register_screen_cast (MetaRemoteDesktopSess
MetaScreenCastSession *screen_cast_session, MetaScreenCastSession *screen_cast_session,
GError **error); 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); void meta_remote_desktop_session_close (MetaRemoteDesktopSession *session);
MetaRemoteDesktopSession * meta_remote_desktop_session_new (MetaRemoteDesktop *remote_desktop, MetaRemoteDesktopSession * meta_remote_desktop_session_new (MetaRemoteDesktop *remote_desktop,

View file

@ -22,6 +22,8 @@
#include "core/meta-selection-source-remote.h" #include "core/meta-selection-source-remote.h"
#include <gio/gunixinputstream.h>
#include "backends/meta-remote-desktop-session.h" #include "backends/meta-remote-desktop-session.h"
struct _MetaSelectionSourceRemote struct _MetaSelectionSourceRemote
@ -56,16 +58,16 @@ meta_selection_source_remote_read_async (MetaSelectionSource *source,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data) gpointer user_data)
{ {
MetaSelectionSourceRemote *source_remote =
META_SELECTION_SOURCE_REMOTE (source);
GTask *task; GTask *task;
GInputStream *stream;
task = g_task_new (source, cancellable, callback, user_data); task = g_task_new (source, cancellable, callback, user_data);
g_task_set_source_tag (task, meta_selection_source_remote_read_async); 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); meta_remote_desktop_session_request_transfer (source_remote->session,
g_task_return_pointer (task, stream, g_object_unref); mimetype,
task);
g_object_unref (task);
} }
static GInputStream * static GInputStream *
@ -80,6 +82,27 @@ meta_selection_source_remote_read_finish (MetaSelectionSource *source,
return g_task_propagate_pointer (G_TASK (result), error); 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 * static GList *
meta_selection_source_remote_get_mimetypes (MetaSelectionSource *source) meta_selection_source_remote_get_mimetypes (MetaSelectionSource *source)
{ {

View file

@ -30,6 +30,13 @@ G_DECLARE_FINAL_TYPE (MetaSelectionSourceRemote,
META, SELECTION_SOURCE_REMOTE, META, SELECTION_SOURCE_REMOTE,
MetaSelectionSource) 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, MetaSelectionSourceRemote * meta_selection_source_remote_new (MetaRemoteDesktopSession *session,
GList *mime_types); GList *mime_types);