/* * Copyright (C) 2022 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include "backends/meta-input-capture-session.h" #include #include #include #include "backends/meta-dbus-session-watcher.h" #include "backends/meta-dbus-session-manager.h" #include "backends/meta-fd-source.h" #include "backends/meta-input-capture-private.h" #include "backends/meta-monitor-manager-private.h" #include "backends/meta-logical-monitor.h" #include "backends/meta-remote-access-controller-private.h" #include "core/meta-anonymous-file.h" #include "meta/barrier.h" #include "meta/boxes.h" #include "meta/meta-backend.h" #include "meta-dbus-input-capture.h" #define META_INPUT_CAPTURE_SESSION_DBUS_PATH "/org/gnome/Mutter/InputCapture/Session" static GQuark quark_barrier_id; enum { PROP_0, N_PROPS }; typedef enum _InputCaptureState { INPUT_CAPTURE_STATE_INIT, INPUT_CAPTURE_STATE_ENABLED, INPUT_CAPTURE_STATE_ACTIVATED, INPUT_CAPTURE_STATE_CLOSED, } InputCaptureState; typedef struct _InputCaptureBarrier { int x1; int y1; int x2; int y2; unsigned int id; MetaBarrier *barrier; } InputCaptureBarrier; struct _MetaInputCaptureSession { MetaDBusInputCaptureSessionSkeleton parent; MetaDbusSessionManager *session_manager; GDBusConnection *connection; char *peer_name; char *session_id; char *object_path; InputCaptureState state; GHashTable *barriers; uint32_t zones_serial; uint32_t activation_id; MetaInputCaptureSessionHandle *handle; struct eis *eis; struct eis_client *eis_client; struct eis_seat *eis_seat; struct eis_device *eis_pointer; struct eis_device *eis_keyboard; GSource *eis_source; MetaAnonymousFile *keymap_file; MetaViewportInfo *viewports; gboolean cancel_requested; unsigned int buttons_pressed; unsigned int keys_pressed; }; static void initable_init_iface (GInitableIface *iface); static void meta_input_capture_session_init_iface (MetaDBusInputCaptureSessionIface *iface); static void meta_dbus_session_init_iface (MetaDbusSessionInterface *iface); static MetaInputCaptureSessionHandle * meta_input_capture_session_handle_new (MetaInputCaptureSession *session); G_DEFINE_TYPE_WITH_CODE (MetaInputCaptureSession, meta_input_capture_session, META_DBUS_TYPE_INPUT_CAPTURE_SESSION_SKELETON, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_init_iface) G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_INPUT_CAPTURE_SESSION, meta_input_capture_session_init_iface) G_IMPLEMENT_INTERFACE (META_TYPE_DBUS_SESSION, meta_dbus_session_init_iface)) struct _MetaInputCaptureSessionHandle { MetaRemoteAccessHandle parent; MetaInputCaptureSession *session; }; G_DEFINE_TYPE (MetaInputCaptureSessionHandle, meta_input_capture_session_handle, META_TYPE_REMOTE_ACCESS_HANDLE) static void init_remote_access_handle (MetaInputCaptureSession *session) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaRemoteAccessController *remote_access_controller; MetaRemoteAccessHandle *remote_access_handle; session->handle = meta_input_capture_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 release_remote_access_handle (MetaInputCaptureSession *session) { MetaRemoteAccessHandle *remote_access_handle = META_REMOTE_ACCESS_HANDLE (session->handle); meta_remote_access_handle_notify_stopped (remote_access_handle); g_clear_object (&session->handle); } static void setup_client (MetaInputCaptureSession *session, struct eis_client *eis_client) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); ClutterSeat *seat = meta_backend_get_default_seat (backend); struct eis_seat *eis_seat; session->eis_client = eis_client_ref (eis_client); eis_client_connect (eis_client); eis_seat = eis_client_new_seat (eis_client, clutter_seat_get_name (seat)); eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_POINTER); eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_BUTTON); eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_SCROLL); eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_KEYBOARD); eis_seat_add (eis_seat); session->eis_seat = eis_seat; } static void ensure_eis_pointer_regions (MetaInputCaptureSession *session, struct eis_device *eis_pointer) { int idx = 0; cairo_rectangle_int_t rect; float scale; if (!session->viewports) return; while (meta_viewport_info_get_view_info (session->viewports, idx++, &rect, &scale)) { struct eis_region *r = eis_device_new_region (eis_pointer); eis_region_set_offset (r, rect.x, rect.y); eis_region_set_size (r, rect.width, rect.height); eis_region_set_physical_scale (r, scale); eis_region_add (r); eis_region_unref (r); } } static void ensure_eis_pointer (MetaInputCaptureSession *session) { struct eis_device *eis_pointer; if (session->eis_pointer) return; eis_pointer = eis_seat_new_device (session->eis_seat); eis_device_configure_name (eis_pointer, "captured relative pointer"); eis_device_configure_capability (eis_pointer, EIS_DEVICE_CAP_POINTER); eis_device_configure_capability (eis_pointer, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability (eis_pointer, EIS_DEVICE_CAP_SCROLL); ensure_eis_pointer_regions (session, eis_pointer); eis_device_add (eis_pointer); eis_device_resume (eis_pointer); session->eis_pointer = eis_pointer; } static MetaAnonymousFile * ensure_xkb_keymap_file (MetaInputCaptureSession *session, GError **error) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); struct xkb_keymap *keymap; g_autofree char *keymap_string = NULL; size_t keymap_size; if (session->keymap_file) return session->keymap_file; keymap = meta_backend_get_keymap (backend); if (!keymap) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Backend has no keymap"); return NULL; } keymap_string = xkb_keymap_get_as_string (keymap, XKB_KEYMAP_FORMAT_TEXT_V1); keymap_size = strlen (keymap_string) + 1; session->keymap_file = meta_anonymous_file_new (keymap_size, (const uint8_t *) keymap_string); return session->keymap_file; } static void ensure_eis_keyboard (MetaInputCaptureSession *session) { struct eis_device *eis_keyboard; g_autoptr (GError) error = NULL; struct eis_keymap *eis_keymap; MetaAnonymousFile *keymap_file; int keymap_fd; size_t keymap_size; if (session->eis_keyboard) return; keymap_file = ensure_xkb_keymap_file (session, &error); if (!keymap_file) { g_warning ("Failed to create input capture keymap file: %s", error->message); return; } eis_keyboard = eis_seat_new_device (session->eis_seat); eis_device_configure_name (eis_keyboard, "captured keyboard"); eis_device_configure_capability (eis_keyboard, EIS_DEVICE_CAP_KEYBOARD); keymap_fd = meta_anonymous_file_open_fd (keymap_file, META_ANONYMOUS_FILE_MAPMODE_PRIVATE); keymap_size = meta_anonymous_file_size (keymap_file); eis_keymap = eis_device_new_keymap (eis_keyboard, EIS_KEYMAP_TYPE_XKB, keymap_fd, keymap_size); eis_keymap_add (eis_keymap); eis_keymap_unref (eis_keymap); meta_anonymous_file_close_fd (keymap_fd); eis_device_add (eis_keyboard); eis_device_resume (eis_keyboard); session->eis_keyboard = eis_keyboard; } static void clear_eis_pointer (MetaInputCaptureSession *session) { if (!session->eis_pointer) return; eis_device_remove (session->eis_pointer); g_clear_pointer (&session->eis_pointer, eis_device_unref); } static void remove_eis_pointer (MetaInputCaptureSession *session) { clear_eis_pointer (session); /* The pointer is removed, all its buttons are cleared */ session->buttons_pressed = 0; } static void clear_eis_keyboard (MetaInputCaptureSession *session) { if (!session->eis_keyboard) return; eis_device_remove (session->eis_keyboard); g_clear_pointer (&session->eis_keyboard, eis_device_unref); } static void remove_eis_keyboard (MetaInputCaptureSession *session) { clear_eis_keyboard (session); /* The pointer is removed, all its buttons are cleared */ session->keys_pressed = 0; } static void on_keymap_changed (MetaBackend *backend, gpointer user_data) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (user_data); g_clear_pointer (&session->keymap_file, meta_anonymous_file_free); if (session->eis_keyboard) { clear_eis_keyboard (session); ensure_eis_keyboard (session); } } static void process_eis_event (MetaInputCaptureSession *session, struct eis_event *eis_event) { struct eis_client *eis_client; struct eis_device *eis_device; switch (eis_event_get_type (eis_event)) { case EIS_EVENT_CLIENT_CONNECT: eis_client = eis_event_get_client (eis_event); if (eis_client_is_sender (eis_client)) { g_warning ("Unexpected sender libei client '%s' connected to " "input capture session", eis_client_get_name (eis_client)); eis_client_disconnect (eis_client); return; } if (session->eis_client) { g_warning ("Unexpected additional libei client '%s' connected to " "input capture session", eis_client_get_name (eis_client)); eis_client_disconnect (eis_client); return; } setup_client (session, eis_client); break; case EIS_EVENT_CLIENT_DISCONNECT: g_clear_pointer (&session->eis_seat, eis_seat_unref); g_clear_pointer (&session->eis_client, eis_client_unref); break; case EIS_EVENT_SEAT_BIND: if (eis_event_seat_has_capability (eis_event, EIS_DEVICE_CAP_POINTER) && eis_event_seat_has_capability (eis_event, EIS_DEVICE_CAP_BUTTON) && eis_event_seat_has_capability (eis_event, EIS_DEVICE_CAP_SCROLL)) ensure_eis_pointer (session); else if (session->eis_pointer) clear_eis_pointer (session); if (eis_event_seat_has_capability (eis_event, EIS_DEVICE_CAP_KEYBOARD)) ensure_eis_keyboard (session); else if (session->eis_keyboard) clear_eis_keyboard (session); break; case EIS_EVENT_DEVICE_CLOSED: eis_device = eis_event_get_device (eis_event); if (eis_device == session->eis_pointer) remove_eis_pointer (session); else if (eis_device == session->eis_keyboard) remove_eis_keyboard (session); break; default: break; } } static void on_barrier_hit (MetaBarrier *barrier, const MetaBarrierEvent *event, MetaInputCaptureSession *session) { MetaDBusInputCaptureSession *skeleton = META_DBUS_INPUT_CAPTURE_SESSION (session); MetaInputCapture *input_capture = META_INPUT_CAPTURE (session->session_manager); GVariant *cursor_position; unsigned int barrier_id; switch (session->state) { case INPUT_CAPTURE_STATE_ACTIVATED: return; case INPUT_CAPTURE_STATE_ENABLED: break; case INPUT_CAPTURE_STATE_INIT: case INPUT_CAPTURE_STATE_CLOSED: g_warn_if_reached (); return; } session->state = INPUT_CAPTURE_STATE_ACTIVATED; barrier_id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (barrier), quark_barrier_id)); cursor_position = g_variant_new ("(dd)", event->x, event->y); meta_input_capture_activate (input_capture, session); meta_dbus_input_capture_session_emit_activated (skeleton, barrier_id, ++session->activation_id, cursor_position); if (session->eis_pointer) eis_device_start_emulating (session->eis_pointer, session->activation_id); if (session->eis_keyboard) eis_device_start_emulating (session->eis_keyboard, session->activation_id); init_remote_access_handle (session); } static void clear_all_barriers (MetaInputCaptureSession *session) { GHashTableIter iter; InputCaptureBarrier *input_capture_barrier; g_hash_table_iter_init (&iter, session->barriers); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &input_capture_barrier)) g_clear_pointer (&input_capture_barrier->barrier, meta_barrier_destroy); } static void release_all_barriers (MetaInputCaptureSession *session) { GHashTableIter iter; InputCaptureBarrier *input_capture_barrier; g_hash_table_iter_init (&iter, session->barriers); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &input_capture_barrier)) { MetaBarrier *barrier; barrier = input_capture_barrier->barrier; if (!barrier) continue; meta_barrier_release (barrier, NULL); } } static gboolean meta_input_capture_session_enable (MetaInputCaptureSession *session, GError **error) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); GHashTableIter iter; gpointer key, value; g_warn_if_fail (session->state == INPUT_CAPTURE_STATE_INIT); g_hash_table_iter_init (&iter, session->barriers); while (g_hash_table_iter_next (&iter, &key, &value)) { unsigned int barrier_id = GPOINTER_TO_UINT (key); InputCaptureBarrier *input_capture_barrier = value; g_autoptr (MetaBarrier) barrier = NULL; barrier = meta_barrier_new (backend, input_capture_barrier->x1, input_capture_barrier->y1, input_capture_barrier->x2, input_capture_barrier->y2, 0, META_BARRIER_FLAG_STICKY, error); if (!barrier) goto err; g_object_set_qdata (G_OBJECT (barrier), quark_barrier_id, GUINT_TO_POINTER (barrier_id)); g_signal_connect (barrier, "hit", G_CALLBACK (on_barrier_hit), session); input_capture_barrier->barrier = barrier; } session->state = INPUT_CAPTURE_STATE_ENABLED; session->cancel_requested = FALSE; return TRUE; err: clear_all_barriers (session); return FALSE; } static void meta_input_capture_session_deactivate (MetaInputCaptureSession *session) { MetaDBusInputCaptureSession *skeleton = META_DBUS_INPUT_CAPTURE_SESSION (session); MetaInputCapture *input_capture = META_INPUT_CAPTURE (session->session_manager); meta_input_capture_deactivate (input_capture, session); if (session->eis_pointer) eis_device_stop_emulating (session->eis_pointer); if (session->eis_keyboard) eis_device_stop_emulating (session->eis_keyboard); meta_dbus_input_capture_session_emit_deactivated (skeleton, session->activation_id); session->state = INPUT_CAPTURE_STATE_ENABLED; } static void meta_input_capture_session_disable (MetaInputCaptureSession *session) { switch (session->state) { case INPUT_CAPTURE_STATE_INIT: return; case INPUT_CAPTURE_STATE_ACTIVATED: meta_input_capture_session_deactivate (session); G_GNUC_FALLTHROUGH; case INPUT_CAPTURE_STATE_ENABLED: break; case INPUT_CAPTURE_STATE_CLOSED: g_warn_if_reached (); return; } clear_all_barriers (session); g_clear_pointer (&session->eis_pointer, eis_device_unref); g_clear_pointer (&session->eis_keyboard, eis_device_unref); g_clear_pointer (&session->eis_seat, eis_seat_unref); session->state = INPUT_CAPTURE_STATE_INIT; if (session->handle) release_remote_access_handle (session); } static void meta_input_capture_session_close (MetaDbusSession *dbus_session) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (dbus_session); MetaDBusInputCaptureSession *skeleton = META_DBUS_INPUT_CAPTURE_SESSION (session); meta_input_capture_session_disable (session); session->state = INPUT_CAPTURE_STATE_CLOSED; meta_dbus_session_notify_closed (META_DBUS_SESSION (session)); meta_dbus_input_capture_session_emit_closed (skeleton); g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (session)); g_object_unref (session); } static gboolean check_permission (MetaInputCaptureSession *session, GDBusMethodInvocation *invocation) { return g_strcmp0 (session->peer_name, g_dbus_method_invocation_get_sender (invocation)) == 0; } typedef enum { LINE_ADJACENCY_ERROR, LINE_ADJACENCY_NONE, LINE_ADJACENCY_OVERLAP, LINE_ADJACENCY_CONTAINED, LINE_ADJACENCY_PARTIAL, } LineAdjacency; static LineAdjacency get_barrier_adjacency (MetaRectangle *rect, int x1, int y1, int x2, int y2, GError **error) { int x_min, x_max; int y_min, y_max; x_min = MIN (x1, x2); x_max = MAX (x1, x2); y_min = MIN (y1, y2); y_max = MAX (y1, y2); if (x1 == x2) { int x = x1; if (x < rect->x || x > rect->x + rect->width) return LINE_ADJACENCY_NONE; if (y_max < rect->y || y_min > rect->y + rect->height) return LINE_ADJACENCY_NONE; if (rect->x + rect->width == x || rect->x == x) { if (y_max > rect->y + rect->height || y_min < rect->y) return LINE_ADJACENCY_PARTIAL; else return LINE_ADJACENCY_CONTAINED; } else { return LINE_ADJACENCY_OVERLAP; } } else if (y1 == y2) { int y = y1; if (y < rect->y || y > rect->y + rect->height) return LINE_ADJACENCY_NONE; if (x_max < rect->x || x_min > rect->x + rect->width) return LINE_ADJACENCY_NONE; if (rect->y + rect->height == y || rect->y == y) { if (x_max > rect->x + rect->width || x_min < rect->x) return LINE_ADJACENCY_PARTIAL; else return LINE_ADJACENCY_CONTAINED; } else { return LINE_ADJACENCY_OVERLAP; } } return LINE_ADJACENCY_NONE; } static gboolean check_barrier (MetaInputCaptureSession *session, int x1, int y1, int x2, int y2, GError **error) { MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); gboolean has_adjacent_monitor = FALSE; GList *logical_monitors; GList *l; if (x1 != x2 && y1 != y2) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Barrier coordinates not axis aligned"); return FALSE; } if (x1 == x2 && y1 == y2) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Barrier cannot be a singularity"); return FALSE; } logical_monitors = meta_monitor_manager_get_logical_monitors (monitor_manager); for (l = logical_monitors; l; l = l->next) { MetaLogicalMonitor *logical_monitor = l->data; MetaRectangle layout; layout = meta_logical_monitor_get_layout (logical_monitor); switch (get_barrier_adjacency (&layout, x1, y1, x2, y2, error)) { case LINE_ADJACENCY_ERROR: return FALSE; case LINE_ADJACENCY_NONE: break; case LINE_ADJACENCY_CONTAINED: if (has_adjacent_monitor) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Adjacent to multiple monitor edges"); return FALSE; } has_adjacent_monitor = TRUE; break; case LINE_ADJACENCY_OVERLAP: g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Line overlaps with monitor region"); return FALSE; case LINE_ADJACENCY_PARTIAL: g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Line partially with monitor region"); return FALSE; } } return has_adjacent_monitor; } static unsigned int find_available_barrier_id (MetaInputCaptureSession *session) { unsigned int id; for (id = 1;; id++) { if (!g_hash_table_contains (session->barriers, GUINT_TO_POINTER (id))) return id; } } static void input_capture_barrier_free (gpointer user_data) { InputCaptureBarrier *input_capture_barrier = user_data; g_clear_pointer (&input_capture_barrier->barrier, meta_barrier_destroy); g_free (input_capture_barrier); } static gboolean handle_add_barrier (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation, unsigned int serial, GVariant *position) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); int x1, y1, x2, y2; g_autoptr (GError) error = NULL; InputCaptureBarrier *input_capture_barrier; unsigned int barrier_id; if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (session->zones_serial != serial) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_BAD_ADDRESS, "State out of date"); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (session->state != INPUT_CAPTURE_STATE_INIT) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Session already enabled"); return G_DBUS_METHOD_INVOCATION_HANDLED; } g_variant_get (position, "(iiii)", &x1, &y1, &x2, &y2); if (!check_barrier (session, x1, y1, x2, y2, &error)) { g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, error->message); return G_DBUS_METHOD_INVOCATION_HANDLED; } barrier_id = find_available_barrier_id (session); input_capture_barrier = g_new0 (InputCaptureBarrier, 1); *input_capture_barrier = (InputCaptureBarrier) { .id = barrier_id, .x1 = x1, .y1 = y1, .x2 = x2, .y2 = y2, }; g_hash_table_insert (session->barriers, GUINT_TO_POINTER (barrier_id), input_capture_barrier); meta_dbus_input_capture_session_complete_add_barrier (object, invocation, barrier_id); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean handle_clear_barriers (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return G_DBUS_METHOD_INVOCATION_HANDLED; } g_hash_table_remove_all (session->barriers); meta_dbus_input_capture_session_complete_clear_barriers (object, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean handle_get_zones (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); GVariant *zones_variant; GVariantBuilder zones_builder; GList *logical_monitors; GList *l; g_variant_builder_init (&zones_builder, G_VARIANT_TYPE ("a(uuii)")); logical_monitors = meta_monitor_manager_get_logical_monitors (monitor_manager); for (l = logical_monitors; l; l = l->next) { MetaLogicalMonitor *logical_monitor = l->data; MetaRectangle layout; layout = meta_logical_monitor_get_layout (logical_monitor); g_variant_builder_add (&zones_builder, "(uuii)", layout.width, layout.height, layout.x, layout.y); } zones_variant = g_variant_builder_end (&zones_builder); meta_dbus_input_capture_session_complete_get_zones (object, invocation, session->zones_serial, zones_variant); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean handle_enable (MetaDBusInputCaptureSession *skeleton, GDBusMethodInvocation *invocation) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (skeleton); g_autoptr (GError) error = NULL; if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (session->state != INPUT_CAPTURE_STATE_INIT) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Already enabled"); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (!meta_input_capture_session_enable (session, &error)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to enable input capture: %s", error->message); return G_DBUS_METHOD_INVOCATION_HANDLED; } meta_dbus_input_capture_session_complete_enable (skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean handle_disable (MetaDBusInputCaptureSession *skeleton, GDBusMethodInvocation *invocation) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (skeleton); if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (session->state != INPUT_CAPTURE_STATE_ENABLED && session->state != INPUT_CAPTURE_STATE_ACTIVATED) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Session not enabled"); return G_DBUS_METHOD_INVOCATION_HANDLED; } meta_input_capture_session_disable (session); meta_dbus_input_capture_session_complete_disable (skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean handle_release (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation, GVariant *arg_options) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); ClutterSeat *seat = meta_backend_get_default_seat (backend); double x, y; if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (session->state != INPUT_CAPTURE_STATE_ACTIVATED) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Capture not active"); return G_DBUS_METHOD_INVOCATION_HANDLED; } release_all_barriers (session); meta_input_capture_session_deactivate (session); if (g_variant_lookup (arg_options, "cursor_position", "(dd)", &x, &y)) clutter_seat_warp_pointer (seat, x, y); if (session->handle) release_remote_access_handle (session); meta_dbus_input_capture_session_complete_release (object, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean handle_connect_to_eis (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation, GUnixFDList *fd_list_in) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); int fd; g_autoptr (GUnixFDList) fd_list = NULL; int fd_idx; GVariant *fd_variant; if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return G_DBUS_METHOD_INVOCATION_HANDLED; } fd = eis_backend_fd_add_client (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_input_capture_session_complete_connect_to_eis (object, invocation, fd_list, fd_variant); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean handle_close (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); if (!check_permission (session, invocation)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Permission denied"); return G_DBUS_METHOD_INVOCATION_HANDLED; } meta_dbus_session_close (META_DBUS_SESSION (session)); meta_dbus_input_capture_session_complete_close (object, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static void meta_input_capture_session_set_viewports (MetaInputCaptureSession *session, MetaViewportInfo *viewports) { g_clear_object (&session->viewports); session->viewports = g_object_ref (viewports); if (!session->eis_pointer) return; clear_eis_pointer (session); ensure_eis_pointer (session); } static void on_monitors_changed (MetaMonitorManager *monitor_manager, MetaInputCaptureSession *session) { MetaDBusInputCaptureSession *skeleton = META_DBUS_INPUT_CAPTURE_SESSION (session); MetaViewportInfo *viewports; viewports = meta_monitor_manager_get_viewports (monitor_manager); meta_input_capture_session_set_viewports (session, viewports); session->zones_serial++; meta_input_capture_session_disable (session); meta_dbus_input_capture_session_emit_zones_changed (skeleton); } static gboolean meta_input_capture_session_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (initable); GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (session); MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaViewportInfo *viewports = meta_monitor_manager_get_viewports (monitor_manager); 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; meta_input_capture_session_set_viewports (session, viewports); g_signal_connect_object (monitor_manager, "monitors-changed", G_CALLBACK (on_monitors_changed), session, 0); return TRUE; } static void initable_init_iface (GInitableIface *iface) { iface->init = meta_input_capture_session_initable_init; } static void meta_input_capture_session_init_iface (MetaDBusInputCaptureSessionIface *iface) { iface->handle_add_barrier = handle_add_barrier; iface->handle_clear_barriers = handle_clear_barriers; iface->handle_enable = handle_enable; iface->handle_disable = handle_disable; iface->handle_release = handle_release; iface->handle_connect_to_eis = handle_connect_to_eis; iface->handle_close = handle_close; iface->handle_get_zones = handle_get_zones; } static void meta_dbus_session_init_iface (MetaDbusSessionInterface *iface) { iface->close = meta_input_capture_session_close; } static void meta_input_capture_session_finalize (GObject *object) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); g_clear_pointer (&session->barriers, 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_clear_object (&session->viewports); g_clear_pointer (&session->keymap_file, meta_anonymous_file_free); g_clear_pointer (&session->eis_source, g_source_destroy); g_clear_pointer (&session->eis, eis_unref); G_OBJECT_CLASS (meta_input_capture_session_parent_class)->finalize (object); } static void meta_eis_log_handler (struct eis *eis, enum eis_log_priority priority, const char *message, struct eis_log_context *ctx) { int message_length = strlen (message); if (priority >= EIS_LOG_PRIORITY_ERROR) g_critical ("EIS: %.*s", message_length, message); else if (priority >= EIS_LOG_PRIORITY_WARNING) g_warning ("EIS: %.*s", message_length, message); else if (priority >= EIS_LOG_PRIORITY_INFO) g_info ("EIS: %.*s", message_length, message); else meta_topic (META_DEBUG_INPUT, "EIS: %.*s", message_length, message); } static gboolean meta_eis_source_prepare (gpointer user_data) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (user_data); struct eis_event *eis_event; gboolean retval; eis_event = eis_peek_event (session->eis); retval = !!eis_event; eis_event_unref (eis_event); return retval; } static gboolean meta_eis_source_dispatch (gpointer user_data) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (user_data); eis_dispatch (session->eis); while (TRUE) { struct eis_event *eis_event; eis_event = eis_get_event (session->eis); if (!eis_event) break; process_eis_event (session, eis_event); eis_event_unref (eis_event); } return G_SOURCE_CONTINUE; } static void meta_input_capture_session_constructed (GObject *object) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); MetaBackend *backend = meta_dbus_session_manager_get_backend (session->session_manager); static unsigned int global_session_number = 0; int fd; GSource *source; session->object_path = g_strdup_printf (META_INPUT_CAPTURE_SESSION_DBUS_PATH "/u%u", ++global_session_number); session->barriers = g_hash_table_new_full (NULL, NULL, NULL, input_capture_barrier_free); session->eis = eis_new (session); eis_log_set_handler (session->eis, meta_eis_log_handler); eis_log_set_priority (session->eis, EIS_LOG_PRIORITY_DEBUG); eis_setup_backend_fd (session->eis); fd = eis_get_fd (session->eis); source = meta_create_fd_source (fd, "[mutter] eis", meta_eis_source_prepare, meta_eis_source_dispatch, session, NULL); session->eis_source = source; g_source_attach (source, NULL); g_source_unref (source); g_signal_connect (backend, "keymap-changed", G_CALLBACK (on_keymap_changed), session); G_OBJECT_CLASS (meta_input_capture_session_parent_class)->constructed (object); } static void meta_input_capture_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_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_input_capture_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_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_input_capture_session_class_init (MetaInputCaptureSessionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = meta_input_capture_session_finalize; object_class->constructed = meta_input_capture_session_constructed; object_class->set_property = meta_input_capture_session_set_property; object_class->get_property = meta_input_capture_session_get_property; meta_dbus_session_install_properties (object_class, N_PROPS); quark_barrier_id = g_quark_from_static_string ("meta-input-capture-barrier-id-quark"); } static void meta_input_capture_session_init (MetaInputCaptureSession *session) { } char * meta_input_capture_session_get_object_path (MetaInputCaptureSession *session) { return session->object_path; } static void maybe_disable_cancelled_session (MetaInputCaptureSession *session) { if (!session->cancel_requested) return; if (session->keys_pressed == 0 && session->buttons_pressed == 0) meta_input_capture_session_disable (session); } static void update_keys_pressed (MetaInputCaptureSession *session, gboolean is_pressed) { if (is_pressed) session->keys_pressed++; else if (session->keys_pressed > 0) session->keys_pressed--; else g_warning ("Unbalanced key release"); maybe_disable_cancelled_session (session); } static void update_buttons_pressed (MetaInputCaptureSession *session, gboolean is_pressed) { if (is_pressed) session->buttons_pressed++; else if (session->buttons_pressed > 0) session->buttons_pressed--; else g_warning ("Unbalanced button release"); maybe_disable_cancelled_session (session); } gboolean meta_input_capture_session_process_event (MetaInputCaptureSession *session, const ClutterEvent *event) { double dx, dy, dx_constrained, dy_constrained; switch (clutter_event_type (event)) { case CLUTTER_MOTION: if (!session->eis_pointer) return TRUE; clutter_event_get_relative_motion (event, &dx, &dy, NULL, NULL, &dx_constrained, &dy_constrained); eis_device_pointer_motion (session->eis_pointer, dx - dx_constrained, dy - dy_constrained); eis_device_frame (session->eis_pointer, eis_now (session->eis)); break; case CLUTTER_BUTTON_PRESS: update_buttons_pressed (session, TRUE); if (!session->eis_pointer) return TRUE; eis_device_button_button (session->eis_pointer, clutter_event_get_event_code (event), true); eis_device_frame (session->eis_pointer, eis_now (session->eis)); break; case CLUTTER_BUTTON_RELEASE: update_buttons_pressed (session, FALSE); if (!session->eis_pointer) return TRUE; eis_device_button_button (session->eis_pointer, clutter_event_get_event_code (event), false); eis_device_frame (session->eis_pointer, eis_now (session->eis)); break; case CLUTTER_SCROLL: { ClutterScrollFinishFlags finish_flags; const double factor = 10.0; bool stop_x = false, stop_y = false; double dx, dy; if (!session->eis_pointer) return TRUE; finish_flags = clutter_event_get_scroll_finish_flags (event); if ((finish_flags & CLUTTER_SCROLL_FINISHED_HORIZONTAL)) stop_x = true; if ((finish_flags & CLUTTER_SCROLL_FINISHED_HORIZONTAL)) stop_y = true; if (stop_x || stop_y) eis_device_scroll_stop (session->eis_pointer, stop_x, stop_y); switch (clutter_event_get_scroll_direction (event)) { case CLUTTER_SCROLL_UP: eis_device_scroll_discrete (session->eis_pointer, 0, -120); break; case CLUTTER_SCROLL_DOWN: eis_device_scroll_discrete (session->eis_pointer, 0, 120); break; case CLUTTER_SCROLL_LEFT: eis_device_scroll_discrete (session->eis_pointer, -120, 0); break; case CLUTTER_SCROLL_RIGHT: eis_device_scroll_discrete (session->eis_pointer, 120, 0); break; case CLUTTER_SCROLL_SMOOTH: clutter_event_get_scroll_delta (event, &dx, &dy); eis_device_scroll_delta (session->eis_pointer, dx * factor, dy * factor); break; } eis_device_frame (session->eis_pointer, eis_now (session->eis)); break; } case CLUTTER_KEY_PRESS: update_keys_pressed (session, TRUE); if (!session->eis_keyboard) return TRUE; eis_device_keyboard_key (session->eis_keyboard, clutter_event_get_event_code (event), true); eis_device_frame (session->eis_keyboard, eis_now (session->eis)); break; case CLUTTER_KEY_RELEASE: update_keys_pressed (session, FALSE); if (!session->eis_keyboard) return TRUE; eis_device_keyboard_key (session->eis_keyboard, clutter_event_get_event_code (event), false); eis_device_frame (session->eis_keyboard, eis_now (session->eis)); break; default: return FALSE; } return TRUE; } void meta_input_capture_session_notify_cancelled (MetaInputCaptureSession *session) { if (session->cancel_requested) return; session->cancel_requested = TRUE; maybe_disable_cancelled_session (session); } static MetaInputCaptureSessionHandle * meta_input_capture_session_handle_new (MetaInputCaptureSession *session) { MetaInputCaptureSessionHandle *handle; handle = g_object_new (META_TYPE_INPUT_CAPTURE_SESSION_HANDLE, NULL); handle->session = session; return handle; } static void meta_input_capture_session_handle_stop (MetaRemoteAccessHandle *handle) { MetaInputCaptureSession *session; session = META_INPUT_CAPTURE_SESSION_HANDLE (handle)->session; if (!session) return; meta_dbus_session_close (META_DBUS_SESSION (session)); } static void meta_input_capture_session_handle_class_init (MetaInputCaptureSessionHandleClass *klass) { MetaRemoteAccessHandleClass *remote_access_handle_class = META_REMOTE_ACCESS_HANDLE_CLASS (klass); remote_access_handle_class->stop = meta_input_capture_session_handle_stop; } static void meta_input_capture_session_handle_init (MetaInputCaptureSessionHandle *handle) { }