/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2015-2017 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * */ #include "config.h" #include "backends/meta-remote-desktop-session.h" #include #include #include #include #include #include #include #include #include "backends/meta-dbus-session-watcher.h" #include "backends/meta-dbus-session-manager.h" #include "backends/meta-eis.h" #include "backends/meta-logical-monitor.h" #include "backends/meta-screen-cast-session.h" #include "backends/meta-remote-access-controller-private.h" #include "backends/x11/meta-backend-x11.h" #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)) enum { PROP_0, N_PROPS }; typedef enum _MetaRemoteDesktopNotifyAxisFlags { META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_NONE = 0, META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_FINISH = 1 << 0, META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_WHEEL = 1 << 1, META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_FINGER = 1 << 2, META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_CONTINUOUS = 1 << 3, } MetaRemoteDesktopNotifyAxisFlags; typedef struct _SelectionReadData { MetaRemoteDesktopSession *session; GOutputStream *stream; GCancellable *cancellable; } SelectionReadData; struct _MetaRemoteDesktopSession { MetaDBusRemoteDesktopSessionSkeleton parent; MetaDbusSessionManager *session_manager; GDBusConnection *connection; char *peer_name; char *session_id; char *object_path; MetaScreenCastSession *screen_cast_session; gulong screen_cast_session_closed_handler_id; guint started : 1; MetaEis *eis; ClutterVirtualInputDevice *virtual_pointer; ClutterVirtualInputDevice *virtual_keyboard; ClutterVirtualInputDevice *virtual_touchscreen; MetaRemoteDesktopSessionHandle *handle; gboolean is_clipboard_enabled; gulong owner_changed_handler_id; SelectionReadData *read_data; unsigned int transfer_serial; MetaSelectionSourceRemote *current_source; GHashTable *transfer_requests; guint transfer_request_timeout_id; GHashTable *mapping_ids; gulong monitors_changed_handler_id; }; static void initable_init_iface (GInitableIface *iface); static void meta_remote_desktop_session_init_iface (MetaDBusRemoteDesktopSessionIface *iface); static void meta_dbus_session_init_iface (MetaDbusSessionInterface *iface); G_DEFINE_TYPE_WITH_CODE (MetaRemoteDesktopSession, meta_remote_desktop_session, META_DBUS_TYPE_REMOTE_DESKTOP_SESSION_SKELETON, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_init_iface) G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_REMOTE_DESKTOP_SESSION, meta_remote_desktop_session_init_iface) G_IMPLEMENT_INTERFACE (META_TYPE_DBUS_SESSION, meta_dbus_session_init_iface)) struct _MetaRemoteDesktopSessionHandle { MetaRemoteAccessHandle parent; MetaRemoteDesktopSession *session; }; G_DEFINE_TYPE (MetaRemoteDesktopSessionHandle, meta_remote_desktop_session_handle, META_TYPE_REMOTE_ACCESS_HANDLE) struct _MetaLogicalMonitorViewport { GObject parent; MetaLogicalMonitor *logical_monitor; }; #define META_TYPE_LOGICAL_MONITOR_VIEWPORT (meta_logical_monitor_viewport_get_type ()) G_DECLARE_FINAL_TYPE (MetaLogicalMonitorViewport, meta_logical_monitor_viewport, META, LOGICAL_MONITOR_VIEWPORT, GObject) static void meta_eis_viewport_init_iface (MetaEisViewportInterface *iface); G_DEFINE_FINAL_TYPE_WITH_CODE (MetaLogicalMonitorViewport, meta_logical_monitor_viewport, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (META_TYPE_EIS_VIEWPORT, meta_eis_viewport_init_iface)) static MetaRemoteDesktopSessionHandle * meta_remote_desktop_session_handle_new (MetaRemoteDesktopSession *session); static gboolean meta_logical_monitor_viewport_is_standalone (MetaEisViewport *viewport) { return FALSE; } static const char * meta_logical_monitor_viewport_get_mapping_id (MetaEisViewport *viewport) { return NULL; } static gboolean meta_logical_monitor_viewport_get_position (MetaEisViewport *viewport, int *out_x, int *out_y) { MetaLogicalMonitorViewport *logical_monitor_viewport = META_LOGICAL_MONITOR_VIEWPORT (viewport); MtkRectangle layout; layout = meta_logical_monitor_get_layout (logical_monitor_viewport->logical_monitor); *out_x = layout.x; *out_y = layout.y; return TRUE; } static void meta_logical_monitor_viewport_get_size (MetaEisViewport *viewport, int *out_width, int *out_height) { MetaLogicalMonitorViewport *logical_monitor_viewport = META_LOGICAL_MONITOR_VIEWPORT (viewport); MtkRectangle layout; layout = meta_logical_monitor_get_layout (logical_monitor_viewport->logical_monitor); *out_width = layout.width; *out_height = layout.height; } static double meta_logical_monitor_viewport_get_physical_scale (MetaEisViewport *viewport) { MetaLogicalMonitorViewport *logical_monitor_viewport = META_LOGICAL_MONITOR_VIEWPORT (viewport); return meta_logical_monitor_get_scale (logical_monitor_viewport->logical_monitor); } static gboolean meta_logical_monitor_viewport_transform_coordinate (MetaEisViewport *viewport, double x, double y, double *out_x, double *out_y) { *out_x = x; *out_y = y; return TRUE; } static void meta_eis_viewport_init_iface (MetaEisViewportInterface *eis_viewport_iface) { eis_viewport_iface->is_standalone = meta_logical_monitor_viewport_is_standalone; eis_viewport_iface->get_mapping_id = meta_logical_monitor_viewport_get_mapping_id; eis_viewport_iface->get_position = meta_logical_monitor_viewport_get_position; eis_viewport_iface->get_size = meta_logical_monitor_viewport_get_size; eis_viewport_iface->get_physical_scale = meta_logical_monitor_viewport_get_physical_scale; eis_viewport_iface->transform_coordinate = meta_logical_monitor_viewport_transform_coordinate; } static void meta_logical_monitor_viewport_class_init (MetaLogicalMonitorViewportClass *klass) { } static void meta_logical_monitor_viewport_init (MetaLogicalMonitorViewport *logical_monitor_viewport) { } static MetaLogicalMonitorViewport * meta_logical_monitor_viewport_new (MetaLogicalMonitor *logical_monitor) { MetaLogicalMonitorViewport *logical_monitor_viewport; logical_monitor_viewport = g_object_new (META_TYPE_LOGICAL_MONITOR_VIEWPORT, NULL); logical_monitor_viewport->logical_monitor = logical_monitor; return logical_monitor_viewport; } static MetaDisplay * display_from_session (MetaRemoteDesktopSession *session) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaContext *context = meta_backend_get_context (backend); return meta_context_get_display (context); } static gboolean meta_remote_desktop_session_is_running (MetaRemoteDesktopSession *session) { return !!session->started; } static void init_remote_access_handle (MetaRemoteDesktopSession *session) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaRemoteAccessController *remote_access_controller; MetaRemoteAccessHandle *remote_access_handle; session->handle = meta_remote_desktop_session_handle_new (session); remote_access_controller = meta_backend_get_remote_access_controller (backend); remote_access_handle = META_REMOTE_ACCESS_HANDLE (session->handle); meta_remote_access_controller_notify_new_handle (remote_access_controller, remote_access_handle); } static void ensure_virtual_device (MetaRemoteDesktopSession *session, ClutterInputDeviceType device_type) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); ClutterSeat *seat = clutter_backend_get_default_seat (clutter_backend); ClutterVirtualInputDevice **virtual_device_ptr = NULL; switch (device_type) { case CLUTTER_POINTER_DEVICE: virtual_device_ptr = &session->virtual_pointer; break; case CLUTTER_KEYBOARD_DEVICE: virtual_device_ptr = &session->virtual_keyboard; break; case CLUTTER_TOUCHSCREEN_DEVICE: virtual_device_ptr = &session->virtual_touchscreen; break; default: g_assert_not_reached (); } g_assert (virtual_device_ptr); if (*virtual_device_ptr) return; *virtual_device_ptr = clutter_seat_create_virtual_device (seat, device_type); } static void on_stream_is_configured (MetaScreenCastStream *stream, GParamSpec *pspec, MetaRemoteDesktopSession *session) { g_signal_handlers_disconnect_by_func (stream, on_stream_is_configured, session); g_return_if_fail (meta_screen_cast_stream_is_configured (stream)); meta_eis_add_viewport (session->eis, META_EIS_VIEWPORT (stream)); } static void on_stream_added (MetaScreenCastSession *screen_cast_session, MetaScreenCastStream *stream, MetaRemoteDesktopSession *session) { if (meta_screen_cast_stream_is_configured (stream)) { meta_eis_add_viewport (session->eis, META_EIS_VIEWPORT (stream)); } else { g_signal_connect (stream, "notify::is-configured", G_CALLBACK (on_stream_is_configured), session); } } static void on_stream_removed (MetaScreenCastSession *screen_cast_session, MetaScreenCastStream *stream, MetaRemoteDesktopSession *session) { if (g_signal_handlers_disconnect_by_func (stream, on_stream_is_configured, session) == 0) meta_eis_remove_viewport (session->eis, META_EIS_VIEWPORT (stream)); } static void add_logical_monitor_viewports (MetaRemoteDesktopSession *session) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); GList *logical_monitors; GList *l; GList *viewports = NULL; logical_monitors = meta_monitor_manager_get_logical_monitors (monitor_manager); for (l = logical_monitors; l; l = l->next) { MetaLogicalMonitor *logical_monitor = l->data; MetaLogicalMonitorViewport *logical_monitor_viewport; logical_monitor_viewport = meta_logical_monitor_viewport_new (logical_monitor); viewports = g_list_append (viewports, logical_monitor_viewport); } meta_eis_remove_all_viewports (session->eis); meta_eis_take_viewports (session->eis, viewports); } static void on_monitors_changed (MetaMonitorManager *monitor_manager, MetaRemoteDesktopSession *session) { add_logical_monitor_viewports (session); } static void initialize_viewports (MetaRemoteDesktopSession *session) { if (session->screen_cast_session) { GList *streams; GList *l; streams = meta_screen_cast_session_peek_streams (session->screen_cast_session); for (l = streams; l; l = l->next) { MetaScreenCastStream *stream = META_SCREEN_CAST_STREAM (l->data); meta_eis_add_viewport (session->eis, META_EIS_VIEWPORT (stream)); } g_signal_connect (session->screen_cast_session, "stream-added", G_CALLBACK (on_stream_added), session); g_signal_connect (session->screen_cast_session, "stream-removed", G_CALLBACK (on_stream_removed), session); } else { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); add_logical_monitor_viewports (session); session->monitors_changed_handler_id = g_signal_connect (monitor_manager, "monitors-changed", G_CALLBACK (on_monitors_changed), session); } } static gboolean meta_remote_desktop_session_start (MetaRemoteDesktopSession *session, GError **error) { g_assert (!session->started); if (session->screen_cast_session) { if (!meta_screen_cast_session_start (session->screen_cast_session, error)) return FALSE; } if (session->eis) initialize_viewports (session); init_remote_access_handle (session); session->started = TRUE; return TRUE; } static void meta_remote_desktop_session_close (MetaDbusSession *dbus_session) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (dbus_session); MetaDBusRemoteDesktopSession *skeleton = META_DBUS_REMOTE_DESKTOP_SESSION (session); MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); session->started = FALSE; if (session->screen_cast_session) { MetaDbusSession *screen_cast_session = META_DBUS_SESSION (session->screen_cast_session); g_clear_signal_handler (&session->screen_cast_session_closed_handler_id, session->screen_cast_session); meta_dbus_session_close (screen_cast_session); session->screen_cast_session = NULL; } g_clear_signal_handler (&session->monitors_changed_handler_id, monitor_manager); g_clear_object (&session->virtual_pointer); g_clear_object (&session->virtual_keyboard); g_clear_object (&session->virtual_touchscreen); g_clear_object (&session->eis); meta_dbus_session_notify_closed (META_DBUS_SESSION (session)); meta_dbus_remote_desktop_session_emit_closed (skeleton); g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (session)); if (session->handle) { MetaRemoteAccessHandle *remote_access_handle = META_REMOTE_ACCESS_HANDLE (session->handle); meta_remote_access_handle_notify_stopped (remote_access_handle); } g_object_unref (session); } char * meta_remote_desktop_session_get_object_path (MetaRemoteDesktopSession *session) { return session->object_path; } static void on_screen_cast_session_closed (MetaScreenCastSession *screen_cast_session, MetaRemoteDesktopSession *session) { session->screen_cast_session = NULL; meta_dbus_session_close (META_DBUS_SESSION (session)); } gboolean meta_remote_desktop_session_register_screen_cast (MetaRemoteDesktopSession *session, MetaScreenCastSession *screen_cast_session, GError **error) { if (session->started) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Remote desktop session already started"); return FALSE; } if (session->screen_cast_session) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Remote desktop session already have an associated " "screen cast session"); return FALSE; } session->screen_cast_session = screen_cast_session; session->screen_cast_session_closed_handler_id = g_signal_connect (screen_cast_session, "session-closed", G_CALLBACK (on_screen_cast_session_closed), session); return TRUE; } const char * meta_remote_desktop_session_acquire_mapping_id (MetaRemoteDesktopSession *session) { while (TRUE) { char *mapping_id; mapping_id = g_uuid_string_random (); if (g_hash_table_contains (session->mapping_ids, mapping_id)) { g_free (mapping_id); continue; } g_hash_table_add (session->mapping_ids, mapping_id); return mapping_id; } } void meta_remote_desktop_session_release_mapping_id (MetaRemoteDesktopSession *session, const char *mapping_id) { g_hash_table_remove (session->mapping_ids, mapping_id); } static gboolean check_permission (MetaRemoteDesktopSession *session, GDBusMethodInvocation *invocation) { return g_strcmp0 (session->peer_name, g_dbus_method_invocation_get_sender (invocation)) == 0; } static gboolean meta_remote_desktop_session_check_can_notify (MetaRemoteDesktopSession *session, GDBusMethodInvocation *invocation) { if (!session->started) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Session not started"); return FALSE; } if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return FALSE; } return TRUE; } static gboolean handle_start (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); GError *error = NULL; if (session->started) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Already started"); return TRUE; } if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return TRUE; } if (!meta_remote_desktop_session_start (session, &error)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to start remote desktop: %s", error->message); g_error_free (error); meta_dbus_session_close (META_DBUS_SESSION (session)); return TRUE; } meta_dbus_remote_desktop_session_complete_start (skeleton, invocation); return TRUE; } static gboolean handle_stop (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); if (!session->started) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Session not started"); return TRUE; } if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return TRUE; } meta_dbus_session_close (META_DBUS_SESSION (session)); meta_dbus_remote_desktop_session_complete_stop (skeleton, invocation); return TRUE; } static gboolean handle_notify_keyboard_keycode (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, unsigned int keycode, gboolean pressed) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); ClutterKeyState state; if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; if (pressed) { ensure_virtual_device (session, CLUTTER_KEYBOARD_DEVICE); state = CLUTTER_KEY_STATE_PRESSED; } else { if (!session->virtual_keyboard) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid key event"); return TRUE; } state = CLUTTER_KEY_STATE_RELEASED; } clutter_virtual_input_device_notify_key (session->virtual_keyboard, CLUTTER_CURRENT_TIME, keycode, state); meta_dbus_remote_desktop_session_complete_notify_keyboard_keycode (skeleton, invocation); return TRUE; } static gboolean handle_notify_keyboard_keysym (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, unsigned int keysym, gboolean pressed) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); ClutterKeyState state; if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; if (pressed) { ensure_virtual_device (session, CLUTTER_KEYBOARD_DEVICE); state = CLUTTER_KEY_STATE_PRESSED; } else { if (!session->virtual_keyboard) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid key event"); return TRUE; } state = CLUTTER_KEY_STATE_RELEASED; } clutter_virtual_input_device_notify_keyval (session->virtual_keyboard, CLUTTER_CURRENT_TIME, keysym, state); meta_dbus_remote_desktop_session_complete_notify_keyboard_keysym (skeleton, invocation); return TRUE; } static gboolean handle_notify_pointer_button (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, int button_code, gboolean pressed) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); uint32_t button; ClutterButtonState state; if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; button = meta_evdev_button_to_clutter (button_code); if (pressed) { ensure_virtual_device (session, CLUTTER_POINTER_DEVICE); state = CLUTTER_BUTTON_STATE_PRESSED; } else { if (!session->virtual_pointer) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid button event"); return TRUE; } state = CLUTTER_BUTTON_STATE_RELEASED; } clutter_virtual_input_device_notify_button (session->virtual_pointer, CLUTTER_CURRENT_TIME, button, state); meta_dbus_remote_desktop_session_complete_notify_pointer_button (skeleton, invocation); return TRUE; } static gboolean clutter_scroll_source_from_axis_flags (MetaRemoteDesktopNotifyAxisFlags axis_flags, ClutterScrollSource *scroll_source) { MetaRemoteDesktopNotifyAxisFlags scroll_mask; scroll_mask = META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_WHEEL | META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_FINGER | META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_CONTINUOUS; switch (axis_flags & scroll_mask) { case META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_WHEEL: *scroll_source = CLUTTER_SCROLL_SOURCE_WHEEL; return TRUE; case META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_NONE: case META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_FINGER: *scroll_source = CLUTTER_SCROLL_SOURCE_FINGER; return TRUE; case META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_CONTINUOUS: *scroll_source = CLUTTER_SCROLL_SOURCE_CONTINUOUS; return TRUE; } return FALSE; } static gboolean handle_notify_pointer_axis (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, double dx, double dy, uint32_t flags) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); ClutterScrollFinishFlags finish_flags = CLUTTER_SCROLL_FINISHED_NONE; ClutterScrollSource scroll_source; if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; if (!clutter_scroll_source_from_axis_flags (flags, &scroll_source)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid scroll source"); return TRUE; } if (flags & META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_FINISH) { finish_flags |= (CLUTTER_SCROLL_FINISHED_HORIZONTAL | CLUTTER_SCROLL_FINISHED_VERTICAL); } ensure_virtual_device (session, CLUTTER_POINTER_DEVICE); clutter_virtual_input_device_notify_scroll_continuous (session->virtual_pointer, CLUTTER_CURRENT_TIME, dx, dy, scroll_source, finish_flags); meta_dbus_remote_desktop_session_complete_notify_pointer_axis (skeleton, invocation); return TRUE; } static ClutterScrollDirection discrete_steps_to_scroll_direction (unsigned int axis, int steps) { if (axis == 0 && steps < 0) return CLUTTER_SCROLL_UP; if (axis == 0 && steps > 0) return CLUTTER_SCROLL_DOWN; if (axis == 1 && steps < 0) return CLUTTER_SCROLL_LEFT; if (axis == 1 && steps > 0) return CLUTTER_SCROLL_RIGHT; g_assert_not_reached (); return 0; } static gboolean handle_notify_pointer_axis_discrete (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, unsigned int axis, int steps) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); ClutterScrollDirection direction; int step_count; if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; if (axis > 1) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid axis value"); return TRUE; } if (steps == 0) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid axis steps value"); return TRUE; } ensure_virtual_device (session, CLUTTER_POINTER_DEVICE); /* * We don't have the actual scroll source, but only know they should be * considered as discrete steps. The device that produces such scroll events * is the scroll wheel, so pretend that is the scroll source. */ direction = discrete_steps_to_scroll_direction (axis, steps); for (step_count = 0; step_count < abs (steps); step_count++) clutter_virtual_input_device_notify_discrete_scroll (session->virtual_pointer, CLUTTER_CURRENT_TIME, direction, CLUTTER_SCROLL_SOURCE_WHEEL); meta_dbus_remote_desktop_session_complete_notify_pointer_axis_discrete (skeleton, invocation); return TRUE; } static gboolean handle_notify_pointer_motion_relative (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, double dx, double dy) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; ensure_virtual_device (session, CLUTTER_POINTER_DEVICE); clutter_virtual_input_device_notify_relative_motion (session->virtual_pointer, CLUTTER_CURRENT_TIME, dx, dy); meta_dbus_remote_desktop_session_complete_notify_pointer_motion_relative (skeleton, invocation); return TRUE; } static gboolean handle_notify_pointer_motion_absolute (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, const char *stream_path, double x, double y) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); MetaScreenCastStream *stream; double abs_x, abs_y; if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; if (!session->screen_cast_session) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "No screen cast active"); return TRUE; } stream = meta_screen_cast_session_get_stream (session->screen_cast_session, stream_path); if (!stream) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Unknown stream"); return TRUE; } ensure_virtual_device (session, CLUTTER_POINTER_DEVICE); if (meta_screen_cast_stream_transform_position (stream, x, y, &abs_x, &abs_y)) { clutter_virtual_input_device_notify_absolute_motion (session->virtual_pointer, CLUTTER_CURRENT_TIME, abs_x, abs_y); } else { meta_topic (META_DEBUG_REMOTE_DESKTOP, "Dropping early absolute pointer motion (%f, %f)", x, y); } meta_dbus_remote_desktop_session_complete_notify_pointer_motion_absolute (skeleton, invocation); return TRUE; } static gboolean handle_notify_touch_down (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, const char *stream_path, unsigned int slot, double x, double y) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); MetaScreenCastStream *stream; double abs_x, abs_y; if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; if (slot > CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Touch slot out of range"); return TRUE; } if (!session->screen_cast_session) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "No screen cast active"); return TRUE; } stream = meta_screen_cast_session_get_stream (session->screen_cast_session, stream_path); if (!stream) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Unknown stream"); return TRUE; } ensure_virtual_device (session, CLUTTER_TOUCHSCREEN_DEVICE); if (meta_screen_cast_stream_transform_position (stream, x, y, &abs_x, &abs_y)) { clutter_virtual_input_device_notify_touch_down (session->virtual_touchscreen, CLUTTER_CURRENT_TIME, slot, abs_x, abs_y); } else { meta_topic (META_DEBUG_REMOTE_DESKTOP, "Dropping early touch down (%f, %f)", x, y); } meta_dbus_remote_desktop_session_complete_notify_touch_down (skeleton, invocation); return TRUE; } static gboolean handle_notify_touch_motion (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, const char *stream_path, unsigned int slot, double x, double y) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); MetaScreenCastStream *stream; double abs_x, abs_y; if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; if (slot > CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Touch slot out of range"); return TRUE; } if (!session->screen_cast_session) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "No screen cast active"); return TRUE; } stream = meta_screen_cast_session_get_stream (session->screen_cast_session, stream_path); if (!stream) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Unknown stream"); return TRUE; } if (!session->virtual_touchscreen) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid touch point"); return TRUE; } if (meta_screen_cast_stream_transform_position (stream, x, y, &abs_x, &abs_y)) { clutter_virtual_input_device_notify_touch_motion (session->virtual_touchscreen, CLUTTER_CURRENT_TIME, slot, abs_x, abs_y); } else { meta_topic (META_DEBUG_REMOTE_DESKTOP, "Dropping early touch motion (%f, %f)", x, y); } meta_dbus_remote_desktop_session_complete_notify_touch_motion (skeleton, invocation); return TRUE; } static gboolean handle_notify_touch_up (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, unsigned int slot) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); if (!meta_remote_desktop_session_check_can_notify (session, invocation)) return TRUE; if (slot > CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Touch slot out of range"); return TRUE; } if (!session->virtual_touchscreen) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid touch point"); return TRUE; } clutter_virtual_input_device_notify_touch_up (session->virtual_touchscreen, CLUTTER_CURRENT_TIME, slot); meta_dbus_remote_desktop_session_complete_notify_touch_up (skeleton, invocation); 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, int buf_len) { g_autofree char *mime_types_string = NULL; int len; if (!formats) return "N\\A"; mime_types_string = g_strjoinv (",", formats); len = strlen (mime_types_string); strncpy (buf, mime_types_string, buf_len - 1); if (len >= buf_len - 1) buf[buf_len - 2] = '*'; buf[buf_len - 1] = '\0'; 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, gboolean is_own_source) { GVariantBuilder builder; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); if (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); } static void emit_owner_changed (MetaRemoteDesktopSession *session, MetaSelectionSource *owner) { char log_buf[255]; g_autofree char **mime_types_array = NULL; GList *l; int i; GVariant *options_variant; const char *object_path; if (owner) { GList *mime_types; mime_types = meta_selection_source_get_mimetypes (owner); mime_types_array = g_new0 (char *, g_list_length (mime_types) + 1); for (l = meta_selection_source_get_mimetypes (owner), i = 0; l; l = l->next, i++) mime_types_array[i] = l->data; } meta_topic (META_DEBUG_REMOTE_DESKTOP, "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, is_own_source (session, owner)); 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", "SelectionOwnerChanged", g_variant_new ("(@a{sv})", options_variant), NULL); } static void on_selection_owner_changed (MetaSelection *selection, MetaSelectionType selection_type, MetaSelectionSource *owner, MetaRemoteDesktopSession *session) { if (selection_type != META_SELECTION_CLIPBOARD) return; emit_owner_changed (session, owner); } static gboolean handle_enable_clipboard (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, GVariant *arg_options) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); GVariant *mime_types_variant; g_autoptr (GError) error = NULL; MetaDisplay *display = display_from_session (session); MetaSelection *selection = meta_display_get_selection (display); g_autoptr (MetaSelectionSourceRemote) source_remote = NULL; meta_topic (META_DEBUG_REMOTE_DESKTOP, "Enable clipboard for %s", g_dbus_method_invocation_get_sender (invocation)); if (session->is_clipboard_enabled) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Already enabled"); return TRUE; } mime_types_variant = g_variant_lookup_value (arg_options, "mime-types", G_VARIANT_TYPE_STRING_ARRAY); if (mime_types_variant) { 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; } } if (source_remote) { 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)); } else { MetaSelectionSource *owner; owner = meta_selection_get_current_owner (selection, META_SELECTION_CLIPBOARD); if (owner) emit_owner_changed (session, owner); } session->is_clipboard_enabled = TRUE; session->owner_changed_handler_id = g_signal_connect (selection, "owner-changed", G_CALLBACK (on_selection_owner_changed), session); meta_dbus_remote_desktop_session_complete_enable_clipboard (skeleton, invocation); 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_timeout (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 = display_from_session (session); 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) { if (!session->read_data) return; g_cancellable_cancel (session->read_data->cancellable); session->read_data->session = NULL; session->read_data = NULL; } static gboolean handle_disable_clipboard (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); MetaDisplay *display = display_from_session (session); MetaSelection *selection = meta_display_get_selection (display); meta_topic (META_DEBUG_REMOTE_DESKTOP, "Disable clipboard for %s", g_dbus_method_invocation_get_sender (invocation)); if (!session->is_clipboard_enabled) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Was not enabled"); return TRUE; } 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, invocation); return TRUE; } static gboolean handle_set_selection (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, GVariant *arg_options) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); g_autoptr (GVariant) mime_types_variant = NULL; g_autoptr (GError) error = NULL; if (!session->is_clipboard_enabled) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Clipboard not enabled"); 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 = display_from_session (session); 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_timeout, 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_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", g_dbus_method_invocation_get_sender (invocation)); if (!session->is_clipboard_enabled) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Clipboard not enabled"); return TRUE; } if (!session->current_source) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "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, fd_list, fd_variant); return TRUE; } static gboolean handle_selection_write_done (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, unsigned int arg_serial, gboolean arg_success) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); meta_topic (META_DEBUG_REMOTE_DESKTOP, "Write selection done for %s", g_dbus_method_invocation_get_sender (invocation)); if (!session->is_clipboard_enabled) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Clipboard not enabled"); return TRUE; } meta_dbus_remote_desktop_session_complete_selection_write_done (skeleton, invocation); return TRUE; } static gboolean is_pipe_broken (int fd) { GPollFD poll_fd = {0}; int poll_ret; int errsv; poll_fd.fd = fd; poll_fd.events = G_IO_OUT; do { poll_ret = g_poll (&poll_fd, 1, 0); errsv = errno; } while (poll_ret == -1 && errsv == EINTR); if (poll_ret < 0) return FALSE; return !!(poll_fd.revents & G_IO_ERR); } static gboolean has_pending_read_operation (SelectionReadData *read_data) { int fd; if (!read_data) return FALSE; fd = g_unix_output_stream_get_fd (G_UNIX_OUTPUT_STREAM (read_data->stream)); if (is_pipe_broken (fd)) { cancel_selection_read (read_data->session); return FALSE; } return TRUE; } static void transfer_cb (MetaSelection *selection, GAsyncResult *res, SelectionReadData *read_data) { g_autoptr (GError) error = NULL; if (!meta_selection_transfer_finish (selection, res, &error)) { g_warning ("Could not fetch selection data " "for remote desktop session: %s", error->message); } if (read_data->session) { meta_topic (META_DEBUG_REMOTE_DESKTOP, "Finished selection transfer for %s", read_data->session->peer_name); } g_output_stream_close (read_data->stream, NULL, NULL); g_clear_object (&read_data->stream); g_clear_object (&read_data->cancellable); if (read_data->session) read_data->session->read_data = NULL; g_free (read_data); } static gboolean handle_selection_read (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, GUnixFDList *fd_list_in, const char *mime_type) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); MetaDisplay *display = display_from_session (session); MetaSelection *selection = meta_display_get_selection (display); MetaSelectionSource *source; g_autoptr (GError) error = NULL; int pipe_fds[2]; g_autoptr (GUnixFDList) fd_list = NULL; int fd_idx; GVariant *fd_variant; SelectionReadData *read_data; meta_topic (META_DEBUG_REMOTE_DESKTOP, "Read selection for %s", g_dbus_method_invocation_get_sender (invocation)); if (!session->is_clipboard_enabled) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Clipboard not enabled"); return TRUE; } source = meta_selection_get_current_owner (selection, META_SELECTION_CLIPBOARD); if (!source) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FILE_NOT_FOUND, "No selection owner available"); 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 (has_pending_read_operation (session->read_data)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_LIMITS_EXCEEDED, "Tried to read in parallel"); 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[0], NULL); close (pipe_fds[0]); fd_variant = g_variant_new_handle (fd_idx); session->read_data = read_data = g_new0 (SelectionReadData, 1); read_data->session = session; read_data->stream = g_unix_output_stream_new (pipe_fds[1], TRUE); read_data->cancellable = g_cancellable_new (); meta_selection_transfer_async (selection, META_SELECTION_CLIPBOARD, mime_type, -1, read_data->stream, read_data->cancellable, (GAsyncReadyCallback) transfer_cb, read_data); meta_dbus_remote_desktop_session_complete_selection_read (skeleton, invocation, fd_list, fd_variant); return TRUE; } static MetaEisDeviceTypes device_types_to_eis_device_types (MetaRemoteDesktopDeviceTypes device_types) { MetaEisDeviceTypes eis_device_types = META_EIS_DEVICE_TYPE_NONE; if (device_types & META_REMOTE_DESKTOP_DEVICE_TYPE_KEYBOARD) eis_device_types |= META_EIS_DEVICE_TYPE_KEYBOARD; if (device_types & META_REMOTE_DESKTOP_DEVICE_TYPE_POINTER) eis_device_types |= META_EIS_DEVICE_TYPE_POINTER; if (device_types & META_REMOTE_DESKTOP_DEVICE_TYPE_TOUCHSCREEN) eis_device_types |= META_EIS_DEVICE_TYPE_TOUCHSCREEN; return eis_device_types; } static gboolean handle_connect_to_eis (MetaDBusRemoteDesktopSession *skeleton, GDBusMethodInvocation *invocation, GUnixFDList *fd_list_in, GVariant *arg_options) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); g_autoptr (GUnixFDList) fd_list = NULL; int fd_idx; GVariant *fd_variant; int fd; if (!session->eis) { MetaRemoteDesktopDeviceTypes device_types = META_REMOTE_DESKTOP_DEVICE_TYPE_NONE; MetaEisDeviceTypes eis_device_types; GVariant *v; v = g_variant_lookup_value (arg_options, "device-types", G_VARIANT_TYPE_UINT32); if (v) { device_types = g_variant_get_uint32 (v); } else { device_types = (META_REMOTE_DESKTOP_DEVICE_TYPE_KEYBOARD | META_REMOTE_DESKTOP_DEVICE_TYPE_POINTER | META_REMOTE_DESKTOP_DEVICE_TYPE_TOUCHSCREEN); } eis_device_types = device_types_to_eis_device_types (device_types); session->eis = meta_eis_new (backend, eis_device_types); if (session->started) initialize_viewports (session); } fd = meta_eis_add_client_get_fd (session->eis); if (fd < 0) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to create socket: %s", g_strerror (-fd)); return G_DBUS_METHOD_INVOCATION_HANDLED; } fd_list = g_unix_fd_list_new (); fd_idx = g_unix_fd_list_append (fd_list, fd, NULL); close (fd); fd_variant = g_variant_new_handle (fd_idx); meta_dbus_remote_desktop_session_complete_connect_to_eis (skeleton, invocation, fd_list, fd_variant); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean meta_remote_desktop_session_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (initable); MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); ClutterSeat *seat = meta_backend_get_default_seat (backend); ClutterKeymap *keymap = clutter_seat_get_keymap (seat); GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (session); MetaDBusRemoteDesktopSession *skeleton = META_DBUS_REMOTE_DESKTOP_SESSION (interface_skeleton); meta_dbus_remote_desktop_session_set_session_id (skeleton, session->session_id); session->connection = meta_dbus_session_manager_get_connection (session->session_manager); if (!g_dbus_interface_skeleton_export (interface_skeleton, session->connection, session->object_path, error)) return FALSE; g_object_bind_property (keymap, "caps-lock-state", session, "caps-lock-state", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_object_bind_property (keymap, "num-lock-state", session, "num-lock-state", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); session->mapping_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); return TRUE; } static void initable_init_iface (GInitableIface *iface) { iface->init = meta_remote_desktop_session_initable_init; } static void meta_remote_desktop_session_init_iface (MetaDBusRemoteDesktopSessionIface *iface) { iface->handle_start = handle_start; iface->handle_stop = handle_stop; iface->handle_notify_keyboard_keycode = handle_notify_keyboard_keycode; iface->handle_notify_keyboard_keysym = handle_notify_keyboard_keysym; iface->handle_notify_pointer_button = handle_notify_pointer_button; iface->handle_notify_pointer_axis = handle_notify_pointer_axis; iface->handle_notify_pointer_axis_discrete = handle_notify_pointer_axis_discrete; iface->handle_notify_pointer_motion_relative = handle_notify_pointer_motion_relative; iface->handle_notify_pointer_motion_absolute = handle_notify_pointer_motion_absolute; iface->handle_notify_touch_down = handle_notify_touch_down; iface->handle_notify_touch_motion = handle_notify_touch_motion; iface->handle_notify_touch_up = handle_notify_touch_up; iface->handle_enable_clipboard = handle_enable_clipboard; iface->handle_disable_clipboard = handle_disable_clipboard; iface->handle_set_selection = handle_set_selection; iface->handle_selection_write = handle_selection_write; iface->handle_selection_write_done = handle_selection_write_done; iface->handle_selection_read = handle_selection_read; iface->handle_connect_to_eis = handle_connect_to_eis; } static void meta_dbus_session_init_iface (MetaDbusSessionInterface *iface) { iface->close = meta_remote_desktop_session_close; } static void meta_remote_desktop_session_finalize (GObject *object) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (object); MetaDisplay *display = display_from_session (session); MetaSelection *selection = meta_display_get_selection (display); 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_pointer (&session->mapping_ids, g_hash_table_unref); g_clear_object (&session->handle); g_free (session->peer_name); g_free (session->session_id); g_free (session->object_path); G_OBJECT_CLASS (meta_remote_desktop_session_parent_class)->finalize (object); } static void meta_remote_desktop_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (object); switch (prop_id) { case N_PROPS + META_DBUS_SESSION_PROP_SESSION_MANAGER: session->session_manager = g_value_get_object (value); break; case N_PROPS + META_DBUS_SESSION_PROP_PEER_NAME: session->peer_name = g_value_dup_string (value); break; case N_PROPS + META_DBUS_SESSION_PROP_ID: session->session_id = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_remote_desktop_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (object); switch (prop_id) { case N_PROPS + META_DBUS_SESSION_PROP_SESSION_MANAGER: g_value_set_object (value, session->session_manager); break; case N_PROPS + META_DBUS_SESSION_PROP_PEER_NAME: g_value_set_string (value, session->peer_name); break; case N_PROPS + META_DBUS_SESSION_PROP_ID: g_value_set_string (value, session->session_id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_remote_desktop_session_init (MetaRemoteDesktopSession *session) { static unsigned int global_session_number = 0; 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 meta_remote_desktop_session_class_init (MetaRemoteDesktopSessionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = meta_remote_desktop_session_finalize; object_class->set_property = meta_remote_desktop_session_set_property; object_class->get_property = meta_remote_desktop_session_get_property; meta_dbus_session_install_properties (object_class, N_PROPS); } static MetaRemoteDesktopSessionHandle * meta_remote_desktop_session_handle_new (MetaRemoteDesktopSession *session) { MetaRemoteDesktopSessionHandle *handle; handle = g_object_new (META_TYPE_REMOTE_DESKTOP_SESSION_HANDLE, NULL); handle->session = session; return handle; } static void meta_remote_desktop_session_handle_stop (MetaRemoteAccessHandle *handle) { MetaRemoteDesktopSession *session; session = META_REMOTE_DESKTOP_SESSION_HANDLE (handle)->session; if (!session) return; meta_dbus_session_close (META_DBUS_SESSION (session)); } static void meta_remote_desktop_session_handle_init (MetaRemoteDesktopSessionHandle *handle) { } static void meta_remote_desktop_session_handle_class_init (MetaRemoteDesktopSessionHandleClass *klass) { MetaRemoteAccessHandleClass *remote_access_handle_class = META_REMOTE_ACCESS_HANDLE_CLASS (klass); remote_access_handle_class->stop = meta_remote_desktop_session_handle_stop; }