/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Copyright (C) 2010 Intel Corp. * Copyright (C) 2014 Jonas Ådahl * Copyright (C) 2016 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Author: Damien Lespiau * Author: Jonas Ådahl */ #include "config.h" #include "backends/native/meta-seat-native.h" #include #include #include #include #include #include "backends/meta-cursor-tracker-private.h" #include "backends/native/meta-barrier-native.h" #include "backends/native/meta-event-native.h" #include "backends/native/meta-input-device-native.h" #include "backends/native/meta-input-device-tool-native.h" #include "backends/native/meta-keymap-native.h" #include "backends/native/meta-virtual-input-device-native.h" #include "clutter/clutter-mutter.h" #include "core/bell.h" /* * Clutter makes the assumption that two core devices have ID's 2 and 3 (core * pointer and core keyboard). * * Since the two first devices that will ever be created will be the virtual * pointer and virtual keyboard of the first seat, we fulfill the made * assumptions by having the first device having ID 2 and following 3. */ #define INITIAL_DEVICE_ID 2 /* Try to keep the pointer inside the stage. Hopefully no one is using * this backend with stages smaller than this. */ #define INITIAL_POINTER_X 16 #define INITIAL_POINTER_Y 16 #define AUTOREPEAT_VALUE 2 #define DISCRETE_SCROLL_STEP 10.0 #ifndef BTN_STYLUS3 #define BTN_STYLUS3 0x149 /* Linux 4.15 */ #endif struct _MetaEventSource { GSource source; MetaSeatNative *seat; GPollFD event_poll_fd; }; static MetaOpenDeviceCallback device_open_callback; static MetaCloseDeviceCallback device_close_callback; static gpointer device_callback_data; #ifdef CLUTTER_ENABLE_DEBUG static const char *device_type_str[] = { "pointer", /* CLUTTER_POINTER_DEVICE */ "keyboard", /* CLUTTER_KEYBOARD_DEVICE */ "extension", /* CLUTTER_EXTENSION_DEVICE */ "joystick", /* CLUTTER_JOYSTICK_DEVICE */ "tablet", /* CLUTTER_TABLET_DEVICE */ "touchpad", /* CLUTTER_TOUCHPAD_DEVICE */ "touchscreen", /* CLUTTER_TOUCHSCREEN_DEVICE */ "pen", /* CLUTTER_PEN_DEVICE */ "eraser", /* CLUTTER_ERASER_DEVICE */ "cursor", /* CLUTTER_CURSOR_DEVICE */ "pad", /* CLUTTER_PAD_DEVICE */ }; #endif /* CLUTTER_ENABLE_DEBUG */ enum { PROP_0, PROP_SEAT_ID, N_PROPS, /* This property is overridden */ PROP_TOUCH_MODE, }; GParamSpec *props[N_PROPS] = { NULL }; G_DEFINE_TYPE (MetaSeatNative, meta_seat_native, CLUTTER_TYPE_SEAT) static void process_events (MetaSeatNative *seat); void meta_seat_native_set_libinput_seat (MetaSeatNative *seat, struct libinput_seat *libinput_seat) { g_assert (seat->libinput_seat == NULL); libinput_seat_ref (libinput_seat); libinput_seat_set_user_data (libinput_seat, seat); seat->libinput_seat = libinput_seat; } void meta_seat_native_sync_leds (MetaSeatNative *seat) { GSList *iter; MetaInputDeviceNative *device_evdev; int caps_lock, num_lock, scroll_lock; enum libinput_led leds = 0; caps_lock = xkb_state_led_index_is_active (seat->xkb, seat->caps_lock_led); num_lock = xkb_state_led_index_is_active (seat->xkb, seat->num_lock_led); scroll_lock = xkb_state_led_index_is_active (seat->xkb, seat->scroll_lock_led); if (caps_lock) leds |= LIBINPUT_LED_CAPS_LOCK; if (num_lock) leds |= LIBINPUT_LED_NUM_LOCK; if (scroll_lock) leds |= LIBINPUT_LED_SCROLL_LOCK; for (iter = seat->devices; iter; iter = iter->next) { device_evdev = iter->data; meta_input_device_native_update_leds (device_evdev, leds); } } MetaTouchState * meta_seat_native_lookup_touch_state (MetaSeatNative *seat, int seat_slot) { if (!seat->touch_states) return NULL; return g_hash_table_lookup (seat->touch_states, GINT_TO_POINTER (seat_slot)); } static void meta_touch_state_free (MetaTouchState *state) { g_slice_free (MetaTouchState, state); } MetaTouchState * meta_seat_native_acquire_touch_state (MetaSeatNative *seat, int seat_slot) { MetaTouchState *touch_state; if (!seat->touch_states) { seat->touch_states = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) meta_touch_state_free); } g_assert (!g_hash_table_contains (seat->touch_states, GINT_TO_POINTER (seat_slot))); touch_state = g_slice_new0 (MetaTouchState); *touch_state = (MetaTouchState) { .seat = seat, .seat_slot = seat_slot, }; g_hash_table_insert (seat->touch_states, GINT_TO_POINTER (seat_slot), touch_state); return touch_state; } void meta_seat_native_release_touch_state (MetaSeatNative *seat, int seat_slot) { if (!seat->touch_states) return; g_hash_table_remove (seat->touch_states, GINT_TO_POINTER (seat_slot)); } void meta_seat_native_clear_repeat_timer (MetaSeatNative *seat) { if (seat->repeat_timer) { g_clear_handle_id (&seat->repeat_timer, g_source_remove); g_clear_object (&seat->repeat_device); } } static void dispatch_libinput (MetaSeatNative *seat) { libinput_dispatch (seat->libinput); process_events (seat); } static gboolean keyboard_repeat (gpointer data) { MetaSeatNative *seat = data; GSource *source; /* There might be events queued in libinput that could cancel the repeat timer. */ dispatch_libinput (seat); if (!seat->repeat_timer) return G_SOURCE_REMOVE; g_return_val_if_fail (seat->repeat_device != NULL, G_SOURCE_REMOVE); source = g_main_context_find_source_by_id (NULL, seat->repeat_timer); meta_seat_native_notify_key (seat, seat->repeat_device, g_source_get_time (source), seat->repeat_key, AUTOREPEAT_VALUE, FALSE); return G_SOURCE_CONTINUE; } static void queue_event (MetaSeatNative *seat, ClutterEvent *event) { _clutter_event_push (event, FALSE); } static int update_button_count (MetaSeatNative *seat, uint32_t button, uint32_t state) { if (state) { return ++seat->button_count[button]; } else { /* Handle cases where we newer saw the initial pressed event. */ if (seat->button_count[button] == 0) { meta_topic (META_DEBUG_INPUT, "Counting release of key 0x%x and count is already 0", button); return 0; } return --seat->button_count[button]; } } void meta_seat_native_notify_key (MetaSeatNative *seat, ClutterInputDevice *device, uint64_t time_us, uint32_t key, uint32_t state, gboolean update_keys) { ClutterEvent *event = NULL; enum xkb_state_component changed_state; if (state != AUTOREPEAT_VALUE) { /* Drop any repeated button press (for example from virtual devices. */ int count = update_button_count (seat, key, state); if ((state && count > 1) || (!state && count != 0)) { meta_topic (META_DEBUG_INPUT, "Dropping repeated %s of key 0x%x, count %d, state %d", state ? "press" : "release", key, count, state); return; } } event = meta_key_event_new_from_evdev (device, seat->core_keyboard, seat->xkb, seat->button_state, us2ms (time_us), key, state); meta_event_native_set_event_code (event, key); /* We must be careful and not pass multiple releases to xkb, otherwise it gets confused and locks the modifiers */ if (state != AUTOREPEAT_VALUE) { changed_state = xkb_state_update_key (seat->xkb, event->key.hardware_keycode, state ? XKB_KEY_DOWN : XKB_KEY_UP); } else { changed_state = 0; clutter_event_set_flags (event, CLUTTER_EVENT_FLAG_REPEATED); } queue_event (seat, event); if (update_keys && (changed_state & XKB_STATE_LEDS)) { g_signal_emit_by_name (seat->keymap, "state-changed"); meta_seat_native_sync_leds (seat); meta_input_device_native_a11y_maybe_notify_toggle_keys (META_INPUT_DEVICE_NATIVE (seat->core_keyboard)); } if (state == 0 || /* key release */ !seat->repeat || !xkb_keymap_key_repeats (xkb_state_get_keymap (seat->xkb), event->key.hardware_keycode)) { meta_seat_native_clear_repeat_timer (seat); return; } if (state == 1) /* key press */ seat->repeat_count = 0; seat->repeat_count += 1; seat->repeat_key = key; switch (seat->repeat_count) { case 1: case 2: { uint32_t interval; meta_seat_native_clear_repeat_timer (seat); seat->repeat_device = g_object_ref (device); if (seat->repeat_count == 1) interval = seat->repeat_delay; else interval = seat->repeat_interval; seat->repeat_timer = clutter_threads_add_timeout_full (CLUTTER_PRIORITY_EVENTS, interval, keyboard_repeat, seat, NULL); return; } default: return; } } static ClutterEvent * new_absolute_motion_event (MetaSeatNative *seat, ClutterInputDevice *input_device, uint64_t time_us, float x, float y, double *axes) { ClutterEvent *event; event = clutter_event_new (CLUTTER_MOTION); if (clutter_input_device_get_device_type (input_device) != CLUTTER_TABLET_DEVICE) { meta_seat_native_constrain_pointer (seat, seat->core_pointer, time_us, seat->pointer_x, seat->pointer_y, &x, &y); } meta_event_native_set_time_usec (event, time_us); event->motion.time = us2ms (time_us); meta_xkb_translate_state (event, seat->xkb, seat->button_state); event->motion.x = x; event->motion.y = y; /* This may happen early at startup */ if (seat->viewports) { meta_input_device_native_translate_coordinates (input_device, seat->viewports, &event->motion.x, &event->motion.y); } event->motion.axes = axes; clutter_event_set_device (event, seat->core_pointer); clutter_event_set_source_device (event, input_device); if (clutter_input_device_get_device_type (input_device) == CLUTTER_TABLET_DEVICE) { MetaInputDeviceNative *device_evdev = META_INPUT_DEVICE_NATIVE (input_device); clutter_event_set_device_tool (event, device_evdev->last_tool); clutter_event_set_device (event, input_device); meta_input_device_native_set_coords (META_INPUT_DEVICE_NATIVE (input_device), x, y); } else { clutter_event_set_device (event, seat->core_pointer); meta_input_device_native_set_coords (META_INPUT_DEVICE_NATIVE (seat->core_pointer), x, y); } if (clutter_input_device_get_device_type (input_device) != CLUTTER_TABLET_DEVICE) { seat->pointer_x = x; seat->pointer_y = y; } return event; } void meta_seat_native_notify_relative_motion (MetaSeatNative *seat, ClutterInputDevice *input_device, uint64_t time_us, float dx, float dy, float dx_unaccel, float dy_unaccel) { float new_x, new_y; ClutterEvent *event; meta_seat_native_filter_relative_motion (seat, input_device, seat->pointer_x, seat->pointer_y, &dx, &dy); new_x = seat->pointer_x + dx; new_y = seat->pointer_y + dy; event = new_absolute_motion_event (seat, input_device, time_us, new_x, new_y, NULL); meta_event_native_set_relative_motion (event, dx, dy, dx_unaccel, dy_unaccel); queue_event (seat, event); } void meta_seat_native_notify_absolute_motion (MetaSeatNative *seat, ClutterInputDevice *input_device, uint64_t time_us, float x, float y, double *axes) { ClutterEvent *event; event = new_absolute_motion_event (seat, input_device, time_us, x, y, axes); queue_event (seat, event); } void meta_seat_native_notify_button (MetaSeatNative *seat, ClutterInputDevice *input_device, uint64_t time_us, uint32_t button, uint32_t state) { MetaInputDeviceNative *device_evdev = (MetaInputDeviceNative *) input_device; ClutterEvent *event = NULL; int button_nr; static int maskmap[8] = { CLUTTER_BUTTON1_MASK, CLUTTER_BUTTON3_MASK, CLUTTER_BUTTON2_MASK, CLUTTER_BUTTON4_MASK, CLUTTER_BUTTON5_MASK, 0, 0, 0 }; int button_count; /* Drop any repeated button press (for example from virtual devices. */ button_count = update_button_count (seat, button, state); if ((state && button_count > 1) || (!state && button_count != 0)) { meta_topic (META_DEBUG_INPUT, "Dropping repeated %s of button 0x%x, count %d", state ? "press" : "release", button, button_count); return; } /* The evdev button numbers don't map sequentially to clutter button * numbers (the right and middle mouse buttons are in the opposite * order) so we'll map them directly with a switch statement */ switch (button) { case BTN_LEFT: case BTN_TOUCH: button_nr = CLUTTER_BUTTON_PRIMARY; break; case BTN_RIGHT: case BTN_STYLUS: button_nr = CLUTTER_BUTTON_SECONDARY; break; case BTN_MIDDLE: case BTN_STYLUS2: button_nr = CLUTTER_BUTTON_MIDDLE; break; case 0x149: /* BTN_STYLUS3 */ button_nr = 8; break; default: /* For compatibility reasons, all additional buttons go after the old 4-7 scroll ones */ if (clutter_input_device_get_device_type (input_device) == CLUTTER_TABLET_DEVICE) button_nr = button - BTN_TOOL_PEN + 4; else button_nr = button - (BTN_LEFT - 1) + 4; break; } if (button_nr < 1 || button_nr > 12) { g_warning ("Unhandled button event 0x%x", button); return; } if (state) event = clutter_event_new (CLUTTER_BUTTON_PRESS); else event = clutter_event_new (CLUTTER_BUTTON_RELEASE); if (button_nr < G_N_ELEMENTS (maskmap)) { /* Update the modifiers */ if (state) seat->button_state |= maskmap[button_nr - 1]; else seat->button_state &= ~maskmap[button_nr - 1]; } meta_event_native_set_time_usec (event, time_us); event->button.time = us2ms (time_us); meta_xkb_translate_state (event, seat->xkb, seat->button_state); event->button.button = button_nr; if (clutter_input_device_get_device_type (input_device) == CLUTTER_TABLET_DEVICE) { meta_input_device_native_get_coords (device_evdev, &event->button.x, &event->button.y); } else { meta_input_device_native_get_coords (META_INPUT_DEVICE_NATIVE (seat->core_pointer), &event->button.x, &event->button.y); } clutter_event_set_device (event, seat->core_pointer); clutter_event_set_source_device (event, input_device); if (device_evdev->last_tool) { /* Apply the button event code as per the tool mapping */ uint32_t mapped_button; mapped_button = meta_input_device_tool_native_get_button_code (device_evdev->last_tool, button_nr); if (mapped_button != 0) button = mapped_button; } meta_event_native_set_event_code (event, button); if (clutter_input_device_get_device_type (input_device) == CLUTTER_TABLET_DEVICE) { clutter_event_set_device_tool (event, device_evdev->last_tool); clutter_event_set_device (event, input_device); } else { clutter_event_set_device (event, seat->core_pointer); } queue_event (seat, event); } static void notify_scroll (ClutterInputDevice *input_device, uint64_t time_us, double dx, double dy, ClutterScrollSource scroll_source, ClutterScrollFinishFlags flags, gboolean emulated) { MetaInputDeviceNative *device_evdev; MetaSeatNative *seat; ClutterEvent *event = NULL; double scroll_factor; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); event = clutter_event_new (CLUTTER_SCROLL); meta_event_native_set_time_usec (event, time_us); event->scroll.time = us2ms (time_us); meta_xkb_translate_state (event, seat->xkb, seat->button_state); /* libinput pointer axis events are in pointer motion coordinate space. * To convert to Xi2 discrete step coordinate space, multiply the factor * 1/10. */ event->scroll.direction = CLUTTER_SCROLL_SMOOTH; scroll_factor = 1.0 / DISCRETE_SCROLL_STEP; clutter_event_set_scroll_delta (event, scroll_factor * dx, scroll_factor * dy); event->scroll.x = seat->pointer_x; event->scroll.y = seat->pointer_y; clutter_event_set_device (event, seat->core_pointer); clutter_event_set_source_device (event, input_device); event->scroll.scroll_source = scroll_source; event->scroll.finish_flags = flags; _clutter_event_set_pointer_emulated (event, emulated); queue_event (seat, event); } static void notify_discrete_scroll (ClutterInputDevice *input_device, uint64_t time_us, ClutterScrollDirection direction, ClutterScrollSource scroll_source, gboolean emulated) { MetaInputDeviceNative *device_evdev; MetaSeatNative *seat; ClutterEvent *event = NULL; if (direction == CLUTTER_SCROLL_SMOOTH) return; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); event = clutter_event_new (CLUTTER_SCROLL); meta_event_native_set_time_usec (event, time_us); event->scroll.time = us2ms (time_us); meta_xkb_translate_state (event, seat->xkb, seat->button_state); event->scroll.direction = direction; event->scroll.x = seat->pointer_x; event->scroll.y = seat->pointer_y; clutter_event_set_device (event, seat->core_pointer); clutter_event_set_source_device (event, input_device); event->scroll.scroll_source = scroll_source; _clutter_event_set_pointer_emulated (event, emulated); queue_event (seat, event); } static void check_notify_discrete_scroll (MetaSeatNative *seat, ClutterInputDevice *device, uint64_t time_us, ClutterScrollSource scroll_source) { int i, n_xscrolls, n_yscrolls; n_xscrolls = floor (fabs (seat->accum_scroll_dx) / DISCRETE_SCROLL_STEP); n_yscrolls = floor (fabs (seat->accum_scroll_dy) / DISCRETE_SCROLL_STEP); for (i = 0; i < n_xscrolls; i++) { notify_discrete_scroll (device, time_us, seat->accum_scroll_dx > 0 ? CLUTTER_SCROLL_RIGHT : CLUTTER_SCROLL_LEFT, scroll_source, TRUE); } for (i = 0; i < n_yscrolls; i++) { notify_discrete_scroll (device, time_us, seat->accum_scroll_dy > 0 ? CLUTTER_SCROLL_DOWN : CLUTTER_SCROLL_UP, scroll_source, TRUE); } seat->accum_scroll_dx = fmodf (seat->accum_scroll_dx, DISCRETE_SCROLL_STEP); seat->accum_scroll_dy = fmodf (seat->accum_scroll_dy, DISCRETE_SCROLL_STEP); } void meta_seat_native_notify_scroll_continuous (MetaSeatNative *seat, ClutterInputDevice *input_device, uint64_t time_us, double dx, double dy, ClutterScrollSource scroll_source, ClutterScrollFinishFlags finish_flags) { if (finish_flags & CLUTTER_SCROLL_FINISHED_HORIZONTAL) seat->accum_scroll_dx = 0; else seat->accum_scroll_dx += dx; if (finish_flags & CLUTTER_SCROLL_FINISHED_VERTICAL) seat->accum_scroll_dy = 0; else seat->accum_scroll_dy += dy; notify_scroll (input_device, time_us, dx, dy, scroll_source, finish_flags, FALSE); check_notify_discrete_scroll (seat, input_device, time_us, scroll_source); } static ClutterScrollDirection discrete_to_direction (double discrete_dx, double discrete_dy) { if (discrete_dx > 0) return CLUTTER_SCROLL_RIGHT; else if (discrete_dx < 0) return CLUTTER_SCROLL_LEFT; else if (discrete_dy > 0) return CLUTTER_SCROLL_DOWN; else if (discrete_dy < 0) return CLUTTER_SCROLL_UP; else g_assert_not_reached (); return 0; } void meta_seat_native_notify_discrete_scroll (MetaSeatNative *seat, ClutterInputDevice *input_device, uint64_t time_us, double discrete_dx, double discrete_dy, ClutterScrollSource scroll_source) { notify_scroll (input_device, time_us, discrete_dx * DISCRETE_SCROLL_STEP, discrete_dy * DISCRETE_SCROLL_STEP, scroll_source, CLUTTER_SCROLL_FINISHED_NONE, TRUE); notify_discrete_scroll (input_device, time_us, discrete_to_direction (discrete_dx, discrete_dy), scroll_source, FALSE); } void meta_seat_native_notify_touch_event (MetaSeatNative *seat, ClutterInputDevice *input_device, ClutterEventType evtype, uint64_t time_us, int slot, double x, double y) { ClutterEvent *event = NULL; event = clutter_event_new (evtype); meta_event_native_set_time_usec (event, time_us); event->touch.time = us2ms (time_us); event->touch.x = x; event->touch.y = y; meta_input_device_native_translate_coordinates (input_device, seat->viewports, &event->touch.x, &event->touch.y); /* "NULL" sequences are special cased in clutter */ event->touch.sequence = GINT_TO_POINTER (MAX (1, slot + 1)); meta_xkb_translate_state (event, seat->xkb, seat->button_state); if (evtype == CLUTTER_TOUCH_BEGIN || evtype == CLUTTER_TOUCH_UPDATE) event->touch.modifier_state |= CLUTTER_BUTTON1_MASK; clutter_event_set_device (event, seat->core_pointer); clutter_event_set_source_device (event, input_device); queue_event (seat, event); } /* * MetaEventSource for reading input devices */ static gboolean meta_event_prepare (GSource *source, gint *timeout) { gboolean retval; *timeout = -1; retval = clutter_events_pending (); return retval; } static gboolean meta_event_check (GSource *source) { MetaEventSource *event_source = (MetaEventSource *) source; gboolean retval; retval = ((event_source->event_poll_fd.revents & G_IO_IN) || clutter_events_pending ()); return retval; } static void constrain_to_barriers (MetaSeatNative *seat, ClutterInputDevice *device, uint32_t time, float *new_x, float *new_y) { meta_barrier_manager_native_process (seat->barrier_manager, device, time, new_x, new_y); } /* * The pointer constrain code is mostly a rip-off of the XRandR code from Xorg. * (from xserver/randr/rrcrtc.c, RRConstrainCursorHarder) * * Copyright © 2006 Keith Packard * Copyright 2010 Red Hat, Inc * */ static void constrain_all_screen_monitors (ClutterInputDevice *device, MetaViewportInfo *viewports, float *x, float *y) { float cx, cy; int i, n_views; meta_input_device_native_get_coords (META_INPUT_DEVICE_NATIVE (device), &cx, &cy); /* if we're trying to escape, clamp to the CRTC we're coming from */ n_views = meta_viewport_info_get_num_views (viewports); for (i = 0; i < n_views; i++) { int left, right, top, bottom; cairo_rectangle_int_t rect; meta_viewport_info_get_view_info (viewports, i, &rect, NULL); left = rect.x; right = left + rect.width; top = rect.y; bottom = top + rect.height; if ((cx >= left) && (cx < right) && (cy >= top) && (cy < bottom)) { if (*x < left) *x = left; if (*x >= right) *x = right - 1; if (*y < top) *y = top; if (*y >= bottom) *y = bottom - 1; return; } } } void meta_seat_native_constrain_pointer (MetaSeatNative *seat, ClutterInputDevice *core_pointer, uint64_t time_us, float x, float y, float *new_x, float *new_y) { constrain_to_barriers (seat, core_pointer, us2ms (time_us), new_x, new_y); /* Bar to constraints */ if (seat->pointer_constraint) { meta_pointer_constraint_impl_constrain (seat->pointer_constraint, core_pointer, us2ms (time_us), x, y, new_x, new_y); } if (seat->viewports) { /* if we're moving inside a monitor, we're fine */ if (meta_viewport_info_get_view_at (seat->viewports, *new_x, *new_y) >= 0) return; /* if we're trying to escape, clamp to the CRTC we're coming from */ constrain_all_screen_monitors (core_pointer, seat->viewports, new_x, new_y); } } static void relative_motion_across_outputs (MetaViewportInfo *viewports, int view, float cur_x, float cur_y, float *dx_inout, float *dy_inout) { int cur_view = view; float x = cur_x, y = cur_y; float target_x = cur_x, target_y = cur_y; float dx = *dx_inout, dy = *dy_inout; MetaDisplayDirection direction = -1; while (cur_view >= 0) { MetaLine2 left, right, top, bottom, motion; MetaVector2 intersection; cairo_rectangle_int_t rect; float scale; meta_viewport_info_get_view_info (viewports, cur_view, &rect, &scale); motion = (MetaLine2) { .a = { x, y }, .b = { x + (dx * scale), y + (dy * scale) } }; left = (MetaLine2) { { rect.x, rect.y }, { rect.x, rect.y + rect.height } }; right = (MetaLine2) { { rect.x + rect.width, rect.y }, { rect.x + rect.width, rect.y + rect.height } }; top = (MetaLine2) { { rect.x, rect.y }, { rect.x + rect.width, rect.y } }; bottom = (MetaLine2) { { rect.x, rect.y + rect.height }, { rect.x + rect.width, rect.y + rect.height } }; target_x = motion.b.x; target_y = motion.b.y; if (direction != META_DISPLAY_RIGHT && meta_line2_intersects_with (&motion, &left, &intersection)) direction = META_DISPLAY_LEFT; else if (direction != META_DISPLAY_LEFT && meta_line2_intersects_with (&motion, &right, &intersection)) direction = META_DISPLAY_RIGHT; else if (direction != META_DISPLAY_DOWN && meta_line2_intersects_with (&motion, &top, &intersection)) direction = META_DISPLAY_UP; else if (direction != META_DISPLAY_UP && meta_line2_intersects_with (&motion, &bottom, &intersection)) direction = META_DISPLAY_DOWN; else /* We reached the dest logical monitor */ break; x = intersection.x; y = intersection.y; dx -= intersection.x - motion.a.x; dy -= intersection.y - motion.a.y; cur_view = meta_viewport_info_get_neighbor (viewports, cur_view, direction); } *dx_inout = target_x - cur_x; *dy_inout = target_y - cur_y; } void meta_seat_native_filter_relative_motion (MetaSeatNative *seat, ClutterInputDevice *device, float x, float y, float *dx, float *dy) { int view = -1, dest_view; float new_dx, new_dy, scale; if (meta_is_stage_views_scaled ()) return; if (seat->viewports) view = meta_viewport_info_get_view_at (seat->viewports, x, y); if (view < 0) return; meta_viewport_info_get_view_info (seat->viewports, view, NULL, &scale); new_dx = (*dx) * scale; new_dy = (*dy) * scale; dest_view = meta_viewport_info_get_view_at (seat->viewports, x + new_dx, y + new_dy); if (dest_view >= 0 && dest_view != view) { /* If we are crossing monitors, attempt to bisect the distance on each * axis and apply the relative scale for each of them. */ new_dx = *dx; new_dy = *dy; relative_motion_across_outputs (seat->viewports, view, x, y, &new_dx, &new_dy); } *dx = new_dx; *dy = new_dy; } static void notify_absolute_motion (ClutterInputDevice *input_device, uint64_t time_us, float x, float y, double *axes) { MetaSeatNative *seat; ClutterEvent *event; seat = meta_input_device_native_get_seat (META_INPUT_DEVICE_NATIVE (input_device)); event = new_absolute_motion_event (seat, input_device, time_us, x, y, axes); queue_event (seat, event); } static void notify_relative_tool_motion (ClutterInputDevice *input_device, uint64_t time_us, float dx, float dy, double *axes) { MetaInputDeviceNative *device_evdev; ClutterEvent *event; MetaSeatNative *seat; gfloat x, y; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); x = device_evdev->pointer_x + dx; y = device_evdev->pointer_y + dy; meta_seat_native_filter_relative_motion (seat, input_device, seat->pointer_x, seat->pointer_y, &dx, &dy); event = new_absolute_motion_event (seat, input_device, time_us, x, y, axes); meta_event_native_set_relative_motion (event, dx, dy, 0, 0); queue_event (seat, event); } static void notify_pinch_gesture_event (ClutterInputDevice *input_device, ClutterTouchpadGesturePhase phase, uint64_t time_us, double dx, double dy, double angle_delta, double scale, uint32_t n_fingers) { MetaInputDeviceNative *device_evdev; MetaSeatNative *seat; ClutterEvent *event = NULL; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); event = clutter_event_new (CLUTTER_TOUCHPAD_PINCH); meta_input_device_native_get_coords (META_INPUT_DEVICE_NATIVE (seat->core_pointer), &event->touchpad_pinch.x, &event->touchpad_pinch.y); meta_event_native_set_time_usec (event, time_us); event->touchpad_pinch.phase = phase; event->touchpad_pinch.time = us2ms (time_us); event->touchpad_pinch.dx = dx; event->touchpad_pinch.dy = dy; event->touchpad_pinch.angle_delta = angle_delta; event->touchpad_pinch.scale = scale; event->touchpad_pinch.n_fingers = n_fingers; meta_xkb_translate_state (event, seat->xkb, seat->button_state); clutter_event_set_device (event, seat->core_pointer); clutter_event_set_source_device (event, input_device); queue_event (seat, event); } static void notify_swipe_gesture_event (ClutterInputDevice *input_device, ClutterTouchpadGesturePhase phase, uint64_t time_us, uint32_t n_fingers, double dx, double dy) { MetaInputDeviceNative *device_evdev; MetaSeatNative *seat; ClutterEvent *event = NULL; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); event = clutter_event_new (CLUTTER_TOUCHPAD_SWIPE); meta_event_native_set_time_usec (event, time_us); event->touchpad_swipe.phase = phase; event->touchpad_swipe.time = us2ms (time_us); meta_input_device_native_get_coords (META_INPUT_DEVICE_NATIVE (seat->core_pointer), &event->touchpad_swipe.x, &event->touchpad_swipe.y); event->touchpad_swipe.dx = dx; event->touchpad_swipe.dy = dy; event->touchpad_swipe.n_fingers = n_fingers; meta_xkb_translate_state (event, seat->xkb, seat->button_state); clutter_event_set_device (event, seat->core_pointer); clutter_event_set_source_device (event, input_device); queue_event (seat, event); } static void notify_proximity (ClutterInputDevice *input_device, uint64_t time_us, gboolean in) { MetaInputDeviceNative *device_evdev; MetaSeatNative *seat; ClutterEvent *event = NULL; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); if (in) event = clutter_event_new (CLUTTER_PROXIMITY_IN); else event = clutter_event_new (CLUTTER_PROXIMITY_OUT); meta_event_native_set_time_usec (event, time_us); event->proximity.time = us2ms (time_us); clutter_event_set_device_tool (event, device_evdev->last_tool); clutter_event_set_device (event, seat->core_pointer); clutter_event_set_source_device (event, input_device); queue_event (seat, event); } static void notify_pad_button (ClutterInputDevice *input_device, uint64_t time_us, uint32_t button, uint32_t mode_group, uint32_t mode, uint32_t pressed) { MetaInputDeviceNative *device_evdev; MetaSeatNative *seat; ClutterEvent *event; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); if (pressed) event = clutter_event_new (CLUTTER_PAD_BUTTON_PRESS); else event = clutter_event_new (CLUTTER_PAD_BUTTON_RELEASE); meta_event_native_set_time_usec (event, time_us); event->pad_button.button = button; event->pad_button.group = mode_group; event->pad_button.mode = mode; clutter_event_set_device (event, input_device); clutter_event_set_source_device (event, input_device); clutter_event_set_time (event, us2ms (time_us)); queue_event (seat, event); } static void notify_pad_strip (ClutterInputDevice *input_device, uint64_t time_us, uint32_t strip_number, uint32_t strip_source, uint32_t mode_group, uint32_t mode, double value) { ClutterInputDevicePadSource source; MetaInputDeviceNative *device_evdev; MetaSeatNative *seat; ClutterEvent *event; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); if (strip_source == LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER) source = CLUTTER_INPUT_DEVICE_PAD_SOURCE_FINGER; else source = CLUTTER_INPUT_DEVICE_PAD_SOURCE_UNKNOWN; event = clutter_event_new (CLUTTER_PAD_STRIP); meta_event_native_set_time_usec (event, time_us); event->pad_strip.strip_source = source; event->pad_strip.strip_number = strip_number; event->pad_strip.value = value; event->pad_strip.group = mode_group; event->pad_strip.mode = mode; clutter_event_set_device (event, input_device); clutter_event_set_source_device (event, input_device); clutter_event_set_time (event, us2ms (time_us)); queue_event (seat, event); } static void notify_pad_ring (ClutterInputDevice *input_device, uint64_t time_us, uint32_t ring_number, uint32_t ring_source, uint32_t mode_group, uint32_t mode, double angle) { ClutterInputDevicePadSource source; MetaInputDeviceNative *device_evdev; MetaSeatNative *seat; ClutterEvent *event; device_evdev = META_INPUT_DEVICE_NATIVE (input_device); seat = meta_input_device_native_get_seat (device_evdev); if (ring_source == LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER) source = CLUTTER_INPUT_DEVICE_PAD_SOURCE_FINGER; else source = CLUTTER_INPUT_DEVICE_PAD_SOURCE_UNKNOWN; event = clutter_event_new (CLUTTER_PAD_RING); meta_event_native_set_time_usec (event, time_us); event->pad_ring.ring_source = source; event->pad_ring.ring_number = ring_number; event->pad_ring.angle = angle; event->pad_ring.group = mode_group; event->pad_ring.mode = mode; clutter_event_set_device (event, input_device); clutter_event_set_source_device (event, input_device); clutter_event_set_time (event, us2ms (time_us)); queue_event (seat, event); } static gboolean meta_event_dispatch (GSource *g_source, GSourceFunc callback, gpointer user_data) { MetaEventSource *source = (MetaEventSource *) g_source; MetaSeatNative *seat; seat = source->seat; /* Don't queue more events if we haven't finished handling the previous batch */ if (clutter_events_pending ()) goto queue_event; dispatch_libinput (seat); queue_event: return TRUE; } static GSourceFuncs event_funcs = { meta_event_prepare, meta_event_check, meta_event_dispatch, NULL }; static MetaEventSource * meta_event_source_new (MetaSeatNative *seat) { GSource *source; MetaEventSource *event_source; gint fd; source = g_source_new (&event_funcs, sizeof (MetaEventSource)); event_source = (MetaEventSource *) source; /* setup the source */ event_source->seat = seat; fd = libinput_get_fd (seat->libinput); event_source->event_poll_fd.fd = fd; event_source->event_poll_fd.events = G_IO_IN; /* and finally configure and attach the GSource */ g_source_set_priority (source, CLUTTER_PRIORITY_EVENTS); g_source_add_poll (source, &event_source->event_poll_fd); g_source_set_can_recurse (source, TRUE); g_source_attach (source, NULL); return event_source; } static void meta_event_source_free (MetaEventSource *source) { GSource *g_source = (GSource *) source; /* ignore the return value of close, it's not like we can do something * about it */ close (source->event_poll_fd.fd); g_source_destroy (g_source); g_source_unref (g_source); } static gboolean has_touchscreen (MetaSeatNative *seat) { GSList *l; for (l = seat->devices; l; l = l->next) { ClutterInputDeviceType device_type; device_type = clutter_input_device_get_device_type (l->data); if (device_type == CLUTTER_TOUCHSCREEN_DEVICE) return TRUE; } return FALSE; } static gboolean has_tablet_switch (MetaSeatNative *seat) { GSList *l; for (l = seat->devices; l; l = l->next) { MetaInputDeviceNative *device_native = META_INPUT_DEVICE_NATIVE (l->data); if (libinput_device_has_capability (device_native->libinput_device, LIBINPUT_DEVICE_CAP_SWITCH) && libinput_device_switch_has_switch (device_native->libinput_device, LIBINPUT_SWITCH_TABLET_MODE)) return TRUE; } return FALSE; } static void update_touch_mode (MetaSeatNative *seat) { gboolean touch_mode; /* No touch mode if we don't have a touchscreen, easy */ if (!seat->has_touchscreen) touch_mode = FALSE; /* If we have a tablet mode switch, honor it being unset */ else if (seat->has_tablet_switch && !seat->tablet_mode_switch_state) touch_mode = FALSE; /* If tablet mode is enabled, or if there is no tablet mode switch * (eg. kiosk machines), assume touch-mode. */ else touch_mode = TRUE; if (seat->touch_mode != touch_mode) { seat->touch_mode = touch_mode; g_object_notify (G_OBJECT (seat), "touch-mode"); } } static ClutterInputDevice * evdev_add_device (MetaSeatNative *seat, struct libinput_device *libinput_device) { ClutterInputDeviceType type; ClutterInputDevice *device, *logical = NULL; device = meta_input_device_native_new (seat, libinput_device); seat->devices = g_slist_prepend (seat->devices, device); /* Clutter assumes that device types are exclusive in the * ClutterInputDevice API */ type = meta_input_device_native_determine_type (libinput_device); if (type == CLUTTER_KEYBOARD_DEVICE) logical = seat->core_keyboard; else if (type == CLUTTER_POINTER_DEVICE) logical = seat->core_pointer; if (logical) { _clutter_input_device_set_associated_device (device, logical); _clutter_input_device_add_physical_device (logical, device); } return device; } static void evdev_remove_device (MetaSeatNative *seat, MetaInputDeviceNative *device_evdev) { ClutterInputDevice *device; device = CLUTTER_INPUT_DEVICE (device_evdev); seat->devices = g_slist_remove (seat->devices, device); g_object_unref (device); } static gboolean meta_seat_native_handle_device_event (ClutterSeat *seat, ClutterEvent *event) { MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); ClutterInputDevice *device = event->device.device; MetaInputDeviceNative *device_native = META_INPUT_DEVICE_NATIVE (device); gboolean is_touchscreen, is_tablet_switch; is_touchscreen = clutter_input_device_get_device_type (device) == CLUTTER_TOUCHSCREEN_DEVICE; is_tablet_switch = libinput_device_has_capability (device_native->libinput_device, LIBINPUT_DEVICE_CAP_SWITCH) && libinput_device_switch_has_switch (device_native->libinput_device, LIBINPUT_SWITCH_TABLET_MODE); switch (event->type) { case CLUTTER_DEVICE_ADDED: if (is_touchscreen) seat_native->has_touchscreen = TRUE; if (is_tablet_switch) seat_native->has_tablet_switch = TRUE; break; case CLUTTER_DEVICE_REMOVED: if (is_touchscreen) seat_native->has_touchscreen = has_touchscreen (seat_native); if (is_tablet_switch) seat_native->has_tablet_switch = has_tablet_switch (seat_native); if (seat_native->repeat_timer && seat_native->repeat_device == device) meta_seat_native_clear_repeat_timer (seat_native); break; default: break; } if (is_touchscreen || is_tablet_switch) update_touch_mode (seat_native); return TRUE; } static gboolean process_base_event (MetaSeatNative *seat, struct libinput_event *event) { ClutterInputDevice *device = NULL; ClutterEvent *device_event = NULL; struct libinput_device *libinput_device; switch (libinput_event_get_type (event)) { case LIBINPUT_EVENT_DEVICE_ADDED: libinput_device = libinput_event_get_device (event); device = evdev_add_device (seat, libinput_device); device_event = clutter_event_new (CLUTTER_DEVICE_ADDED); clutter_event_set_device (device_event, device); break; case LIBINPUT_EVENT_DEVICE_REMOVED: libinput_device = libinput_event_get_device (event); device = libinput_device_get_user_data (libinput_device); device_event = clutter_event_new (CLUTTER_DEVICE_REMOVED); clutter_event_set_device (device_event, device); evdev_remove_device (seat, META_INPUT_DEVICE_NATIVE (device)); break; default: break; } if (device_event) { queue_event (seat, device_event); return TRUE; } return FALSE; } static ClutterScrollSource translate_scroll_source (enum libinput_pointer_axis_source source) { switch (source) { case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: return CLUTTER_SCROLL_SOURCE_WHEEL; case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: return CLUTTER_SCROLL_SOURCE_FINGER; case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: return CLUTTER_SCROLL_SOURCE_CONTINUOUS; default: return CLUTTER_SCROLL_SOURCE_UNKNOWN; } } static ClutterInputDeviceToolType translate_tool_type (struct libinput_tablet_tool *libinput_tool) { enum libinput_tablet_tool_type tool; tool = libinput_tablet_tool_get_type (libinput_tool); switch (tool) { case LIBINPUT_TABLET_TOOL_TYPE_PEN: return CLUTTER_INPUT_DEVICE_TOOL_PEN; case LIBINPUT_TABLET_TOOL_TYPE_ERASER: return CLUTTER_INPUT_DEVICE_TOOL_ERASER; case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: return CLUTTER_INPUT_DEVICE_TOOL_BRUSH; case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: return CLUTTER_INPUT_DEVICE_TOOL_PENCIL; case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: return CLUTTER_INPUT_DEVICE_TOOL_AIRBRUSH; case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: return CLUTTER_INPUT_DEVICE_TOOL_MOUSE; case LIBINPUT_TABLET_TOOL_TYPE_LENS: return CLUTTER_INPUT_DEVICE_TOOL_LENS; default: return CLUTTER_INPUT_DEVICE_TOOL_NONE; } } static void input_device_update_tool (ClutterInputDevice *input_device, struct libinput_tablet_tool *libinput_tool) { MetaInputDeviceNative *evdev_device = META_INPUT_DEVICE_NATIVE (input_device); MetaSeatNative *seat = meta_input_device_native_get_seat (evdev_device); ClutterInputDeviceTool *tool = NULL; ClutterInputDeviceToolType tool_type; uint64_t tool_serial; if (libinput_tool) { tool_serial = libinput_tablet_tool_get_serial (libinput_tool); tool_type = translate_tool_type (libinput_tool); tool = clutter_input_device_lookup_tool (input_device, tool_serial, tool_type); if (!tool) { tool = meta_input_device_tool_native_new (libinput_tool, tool_serial, tool_type); clutter_input_device_add_tool (input_device, tool); } } if (evdev_device->last_tool != tool) { evdev_device->last_tool = tool; g_signal_emit_by_name (seat, "tool-changed", input_device, tool); } } static gdouble * translate_tablet_axes (struct libinput_event_tablet_tool *tablet_event, ClutterInputDeviceTool *tool) { GArray *axes = g_array_new (FALSE, FALSE, sizeof (gdouble)); struct libinput_tablet_tool *libinput_tool; gdouble value; libinput_tool = libinput_event_tablet_tool_get_tool (tablet_event); value = libinput_event_tablet_tool_get_x (tablet_event); g_array_append_val (axes, value); value = libinput_event_tablet_tool_get_y (tablet_event); g_array_append_val (axes, value); if (libinput_tablet_tool_has_distance (libinput_tool)) { value = libinput_event_tablet_tool_get_distance (tablet_event); g_array_append_val (axes, value); } if (libinput_tablet_tool_has_pressure (libinput_tool)) { value = libinput_event_tablet_tool_get_pressure (tablet_event); value = meta_input_device_tool_native_translate_pressure (tool, value); g_array_append_val (axes, value); } if (libinput_tablet_tool_has_tilt (libinput_tool)) { value = libinput_event_tablet_tool_get_tilt_x (tablet_event); g_array_append_val (axes, value); value = libinput_event_tablet_tool_get_tilt_y (tablet_event); g_array_append_val (axes, value); } if (libinput_tablet_tool_has_rotation (libinput_tool)) { value = libinput_event_tablet_tool_get_rotation (tablet_event); g_array_append_val (axes, value); } if (libinput_tablet_tool_has_slider (libinput_tool)) { value = libinput_event_tablet_tool_get_slider_position (tablet_event); g_array_append_val (axes, value); } if (libinput_tablet_tool_has_wheel (libinput_tool)) { value = libinput_event_tablet_tool_get_wheel_delta (tablet_event); g_array_append_val (axes, value); } if (axes->len == 0) { g_array_free (axes, TRUE); return NULL; } else return (gdouble *) g_array_free (axes, FALSE); } static MetaSeatNative * seat_from_device (ClutterInputDevice *device) { MetaInputDeviceNative *device_evdev = META_INPUT_DEVICE_NATIVE (device); return meta_input_device_native_get_seat (device_evdev); } static void notify_continuous_axis (MetaSeatNative *seat, ClutterInputDevice *device, uint64_t time_us, ClutterScrollSource scroll_source, struct libinput_event_pointer *axis_event) { gdouble dx = 0.0, dy = 0.0; ClutterScrollFinishFlags finish_flags = CLUTTER_SCROLL_FINISHED_NONE; if (libinput_event_pointer_has_axis (axis_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) { dx = libinput_event_pointer_get_axis_value ( axis_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); if (fabs (dx) < DBL_EPSILON) finish_flags |= CLUTTER_SCROLL_FINISHED_HORIZONTAL; } if (libinput_event_pointer_has_axis (axis_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { dy = libinput_event_pointer_get_axis_value ( axis_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); if (fabs (dy) < DBL_EPSILON) finish_flags |= CLUTTER_SCROLL_FINISHED_VERTICAL; } meta_seat_native_notify_scroll_continuous (seat, device, time_us, dx, dy, scroll_source, finish_flags); } static void notify_discrete_axis (MetaSeatNative *seat, ClutterInputDevice *device, uint64_t time_us, ClutterScrollSource scroll_source, struct libinput_event_pointer *axis_event) { gdouble discrete_dx = 0.0, discrete_dy = 0.0; if (libinput_event_pointer_has_axis (axis_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) { discrete_dx = libinput_event_pointer_get_axis_value_discrete ( axis_event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); } if (libinput_event_pointer_has_axis (axis_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { discrete_dy = libinput_event_pointer_get_axis_value_discrete ( axis_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); } meta_seat_native_notify_discrete_scroll (seat, device, time_us, discrete_dx, discrete_dy, scroll_source); } static void process_tablet_axis (MetaSeatNative *seat, struct libinput_event *event) { struct libinput_device *libinput_device = libinput_event_get_device (event); uint64_t time; double x, y, dx, dy, *axes; float stage_width, stage_height; ClutterInputDevice *device; struct libinput_event_tablet_tool *tablet_event = libinput_event_get_tablet_tool_event (event); MetaInputDeviceNative *evdev_device; device = libinput_device_get_user_data (libinput_device); evdev_device = META_INPUT_DEVICE_NATIVE (device); axes = translate_tablet_axes (tablet_event, evdev_device->last_tool); if (!axes) return; meta_viewport_info_get_extents (seat->viewports, &stage_width, &stage_height); time = libinput_event_tablet_tool_get_time_usec (tablet_event); if (meta_input_device_native_get_mapping_mode (device) == META_INPUT_DEVICE_MAPPING_RELATIVE || clutter_input_device_tool_get_tool_type (evdev_device->last_tool) == CLUTTER_INPUT_DEVICE_TOOL_MOUSE || clutter_input_device_tool_get_tool_type (evdev_device->last_tool) == CLUTTER_INPUT_DEVICE_TOOL_LENS) { dx = libinput_event_tablet_tool_get_dx (tablet_event); dy = libinput_event_tablet_tool_get_dy (tablet_event); notify_relative_tool_motion (device, time, dx, dy, axes); } else { x = libinput_event_tablet_tool_get_x_transformed (tablet_event, stage_width); y = libinput_event_tablet_tool_get_y_transformed (tablet_event, stage_height); notify_absolute_motion (device, time, x, y, axes); } } static void update_tablet_cursor_state (MetaSeatNative *seat_native, ClutterInputDevice *device, gboolean in) { if (in) { MetaCursorRendererNative *cursor_renderer_native; if (!seat_native->tablet_cursors) { seat_native->tablet_cursors = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref); } cursor_renderer_native = meta_cursor_renderer_native_new (meta_get_backend (), device); g_hash_table_insert (seat_native->tablet_cursors, device, cursor_renderer_native); } else { if (seat_native->tablet_cursors) g_hash_table_remove (seat_native->tablet_cursors, device); } } static gboolean process_device_event (MetaSeatNative *seat, struct libinput_event *event) { gboolean handled = TRUE; struct libinput_device *libinput_device = libinput_event_get_device(event); ClutterInputDevice *device; MetaInputDeviceNative *device_evdev; switch (libinput_event_get_type (event)) { case LIBINPUT_EVENT_KEYBOARD_KEY: { uint32_t key, key_state, seat_key_count; uint64_t time_us; struct libinput_event_keyboard *key_event = libinput_event_get_keyboard_event (event); device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_keyboard_get_time_usec (key_event); key = libinput_event_keyboard_get_key (key_event); key_state = libinput_event_keyboard_get_key_state (key_event) == LIBINPUT_KEY_STATE_PRESSED; seat_key_count = libinput_event_keyboard_get_seat_key_count (key_event); /* Ignore key events that are not seat wide state changes. */ if ((key_state == LIBINPUT_KEY_STATE_PRESSED && seat_key_count != 1) || (key_state == LIBINPUT_KEY_STATE_RELEASED && seat_key_count != 0)) { meta_topic (META_DEBUG_INPUT, "Dropping key-%s of key 0x%x because seat-wide " "key count is %d", key_state == LIBINPUT_KEY_STATE_PRESSED ? "press" : "release", key, seat_key_count); break; } meta_seat_native_notify_key (seat_from_device (device), device, time_us, key, key_state, TRUE); break; } case LIBINPUT_EVENT_POINTER_MOTION: { struct libinput_event_pointer *pointer_event = libinput_event_get_pointer_event (event); uint64_t time_us; double dx; double dy; double dx_unaccel; double dy_unaccel; device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_pointer_get_time_usec (pointer_event); dx = libinput_event_pointer_get_dx (pointer_event); dy = libinput_event_pointer_get_dy (pointer_event); dx_unaccel = libinput_event_pointer_get_dx_unaccelerated (pointer_event); dy_unaccel = libinput_event_pointer_get_dy_unaccelerated (pointer_event); meta_seat_native_notify_relative_motion (seat_from_device (device), device, time_us, dx, dy, dx_unaccel, dy_unaccel); break; } case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { uint64_t time_us; double x, y; float stage_width, stage_height; struct libinput_event_pointer *motion_event = libinput_event_get_pointer_event (event); device = libinput_device_get_user_data (libinput_device); meta_viewport_info_get_extents (seat->viewports, &stage_width, &stage_height); time_us = libinput_event_pointer_get_time_usec (motion_event); x = libinput_event_pointer_get_absolute_x_transformed (motion_event, stage_width); y = libinput_event_pointer_get_absolute_y_transformed (motion_event, stage_height); meta_seat_native_notify_absolute_motion (seat_from_device (device), device, time_us, x, y, NULL); break; } case LIBINPUT_EVENT_POINTER_BUTTON: { uint32_t button, button_state, seat_button_count; uint64_t time_us; struct libinput_event_pointer *button_event = libinput_event_get_pointer_event (event); device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_pointer_get_time_usec (button_event); button = libinput_event_pointer_get_button (button_event); button_state = libinput_event_pointer_get_button_state (button_event) == LIBINPUT_BUTTON_STATE_PRESSED; seat_button_count = libinput_event_pointer_get_seat_button_count (button_event); /* Ignore button events that are not seat wide state changes. */ if ((button_state == LIBINPUT_BUTTON_STATE_PRESSED && seat_button_count != 1) || (button_state == LIBINPUT_BUTTON_STATE_RELEASED && seat_button_count != 0)) { meta_topic (META_DEBUG_INPUT, "Dropping button-%s of button 0x%x because seat-wide " "button count is %d", button_state == LIBINPUT_BUTTON_STATE_PRESSED ? "press" : "release", button, seat_button_count); break; } meta_seat_native_notify_button (seat_from_device (device), device, time_us, button, button_state); break; } case LIBINPUT_EVENT_POINTER_AXIS: { uint64_t time_us; enum libinput_pointer_axis_source source; struct libinput_event_pointer *axis_event = libinput_event_get_pointer_event (event); MetaSeatNative *seat; ClutterScrollSource scroll_source; device = libinput_device_get_user_data (libinput_device); seat = meta_input_device_native_get_seat (META_INPUT_DEVICE_NATIVE (device)); time_us = libinput_event_pointer_get_time_usec (axis_event); source = libinput_event_pointer_get_axis_source (axis_event); scroll_source = translate_scroll_source (source); /* libinput < 0.8 sent wheel click events with value 10. Since 0.8 the value is the angle of the click in degrees. To keep backwards-compat with existing clients, we just send multiples of the click count. */ switch (scroll_source) { case CLUTTER_SCROLL_SOURCE_WHEEL: notify_discrete_axis (seat, device, time_us, scroll_source, axis_event); break; case CLUTTER_SCROLL_SOURCE_FINGER: case CLUTTER_SCROLL_SOURCE_CONTINUOUS: case CLUTTER_SCROLL_SOURCE_UNKNOWN: notify_continuous_axis (seat, device, time_us, scroll_source, axis_event); break; } break; } case LIBINPUT_EVENT_TOUCH_DOWN: { int seat_slot; uint64_t time_us; double x, y; float stage_width, stage_height; MetaSeatNative *seat; MetaTouchState *touch_state; struct libinput_event_touch *touch_event = libinput_event_get_touch_event (event); device = libinput_device_get_user_data (libinput_device); device_evdev = META_INPUT_DEVICE_NATIVE (device); seat = meta_input_device_native_get_seat (device_evdev); meta_viewport_info_get_extents (seat->viewports, &stage_width, &stage_height); seat_slot = libinput_event_touch_get_seat_slot (touch_event); time_us = libinput_event_touch_get_time_usec (touch_event); x = libinput_event_touch_get_x_transformed (touch_event, stage_width); y = libinput_event_touch_get_y_transformed (touch_event, stage_height); touch_state = meta_seat_native_acquire_touch_state (seat, seat_slot); touch_state->coords.x = x; touch_state->coords.y = y; meta_seat_native_notify_touch_event (seat, device, CLUTTER_TOUCH_BEGIN, time_us, touch_state->seat_slot, touch_state->coords.x, touch_state->coords.y); break; } case LIBINPUT_EVENT_TOUCH_UP: { int seat_slot; uint64_t time_us; MetaSeatNative *seat; MetaTouchState *touch_state; struct libinput_event_touch *touch_event = libinput_event_get_touch_event (event); device = libinput_device_get_user_data (libinput_device); device_evdev = META_INPUT_DEVICE_NATIVE (device); seat = meta_input_device_native_get_seat (device_evdev); seat_slot = libinput_event_touch_get_seat_slot (touch_event); time_us = libinput_event_touch_get_time_usec (touch_event); touch_state = meta_seat_native_lookup_touch_state (seat, seat_slot); if (!touch_state) break; meta_seat_native_notify_touch_event (seat, device, CLUTTER_TOUCH_END, time_us, touch_state->seat_slot, touch_state->coords.x, touch_state->coords.y); meta_seat_native_release_touch_state (seat, seat_slot); break; } case LIBINPUT_EVENT_TOUCH_MOTION: { int seat_slot; uint64_t time_us; double x, y; float stage_width, stage_height; MetaSeatNative *seat; MetaTouchState *touch_state; struct libinput_event_touch *touch_event = libinput_event_get_touch_event (event); device = libinput_device_get_user_data (libinput_device); device_evdev = META_INPUT_DEVICE_NATIVE (device); seat = meta_input_device_native_get_seat (device_evdev); meta_viewport_info_get_extents (seat->viewports, &stage_width, &stage_height); seat_slot = libinput_event_touch_get_seat_slot (touch_event); time_us = libinput_event_touch_get_time_usec (touch_event); x = libinput_event_touch_get_x_transformed (touch_event, stage_width); y = libinput_event_touch_get_y_transformed (touch_event, stage_height); touch_state = meta_seat_native_lookup_touch_state (seat, seat_slot); if (!touch_state) break; touch_state->coords.x = x; touch_state->coords.y = y; meta_seat_native_notify_touch_event (seat, device, CLUTTER_TOUCH_UPDATE, time_us, touch_state->seat_slot, touch_state->coords.x, touch_state->coords.y); break; } case LIBINPUT_EVENT_TOUCH_CANCEL: { int seat_slot; MetaTouchState *touch_state; uint64_t time_us; MetaSeatNative *seat; struct libinput_event_touch *touch_event = libinput_event_get_touch_event (event); device = libinput_device_get_user_data (libinput_device); device_evdev = META_INPUT_DEVICE_NATIVE (device); seat = meta_input_device_native_get_seat (device_evdev); time_us = libinput_event_touch_get_time_usec (touch_event); seat_slot = libinput_event_touch_get_seat_slot (touch_event); touch_state = meta_seat_native_lookup_touch_state (seat, seat_slot); if (!touch_state) break; meta_seat_native_notify_touch_event (touch_state->seat, device, CLUTTER_TOUCH_CANCEL, time_us, touch_state->seat_slot, touch_state->coords.x, touch_state->coords.y); meta_seat_native_release_touch_state (seat, seat_slot); break; } case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: case LIBINPUT_EVENT_GESTURE_PINCH_END: { struct libinput_event_gesture *gesture_event = libinput_event_get_gesture_event (event); ClutterTouchpadGesturePhase phase; uint32_t n_fingers; uint64_t time_us; if (libinput_event_get_type (event) == LIBINPUT_EVENT_GESTURE_PINCH_BEGIN) phase = CLUTTER_TOUCHPAD_GESTURE_PHASE_BEGIN; else phase = libinput_event_gesture_get_cancelled (gesture_event) ? CLUTTER_TOUCHPAD_GESTURE_PHASE_CANCEL : CLUTTER_TOUCHPAD_GESTURE_PHASE_END; n_fingers = libinput_event_gesture_get_finger_count (gesture_event); device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_gesture_get_time_usec (gesture_event); notify_pinch_gesture_event (device, phase, time_us, 0, 0, 0, 0, n_fingers); break; } case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: { struct libinput_event_gesture *gesture_event = libinput_event_get_gesture_event (event); gdouble angle_delta, scale, dx, dy; uint32_t n_fingers; uint64_t time_us; n_fingers = libinput_event_gesture_get_finger_count (gesture_event); device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_gesture_get_time_usec (gesture_event); angle_delta = libinput_event_gesture_get_angle_delta (gesture_event); scale = libinput_event_gesture_get_scale (gesture_event); dx = libinput_event_gesture_get_dx (gesture_event); dy = libinput_event_gesture_get_dy (gesture_event); notify_pinch_gesture_event (device, CLUTTER_TOUCHPAD_GESTURE_PHASE_UPDATE, time_us, dx, dy, angle_delta, scale, n_fingers); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: case LIBINPUT_EVENT_GESTURE_SWIPE_END: { struct libinput_event_gesture *gesture_event = libinput_event_get_gesture_event (event); ClutterTouchpadGesturePhase phase; uint32_t n_fingers; uint64_t time_us; device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_gesture_get_time_usec (gesture_event); n_fingers = libinput_event_gesture_get_finger_count (gesture_event); if (libinput_event_get_type (event) == LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN) phase = CLUTTER_TOUCHPAD_GESTURE_PHASE_BEGIN; else phase = libinput_event_gesture_get_cancelled (gesture_event) ? CLUTTER_TOUCHPAD_GESTURE_PHASE_CANCEL : CLUTTER_TOUCHPAD_GESTURE_PHASE_END; notify_swipe_gesture_event (device, phase, time_us, n_fingers, 0, 0); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: { struct libinput_event_gesture *gesture_event = libinput_event_get_gesture_event (event); uint32_t n_fingers; uint64_t time_us; double dx, dy; device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_gesture_get_time_usec (gesture_event); n_fingers = libinput_event_gesture_get_finger_count (gesture_event); dx = libinput_event_gesture_get_dx (gesture_event); dy = libinput_event_gesture_get_dy (gesture_event); notify_swipe_gesture_event (device, CLUTTER_TOUCHPAD_GESTURE_PHASE_UPDATE, time_us, n_fingers, dx, dy); break; } case LIBINPUT_EVENT_TABLET_TOOL_AXIS: { process_tablet_axis (seat, event); break; } case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: { uint64_t time; struct libinput_event_tablet_tool *tablet_event = libinput_event_get_tablet_tool_event (event); struct libinput_tablet_tool *libinput_tool = NULL; enum libinput_tablet_tool_proximity_state state; gboolean in; state = libinput_event_tablet_tool_get_proximity_state (tablet_event); time = libinput_event_tablet_tool_get_time_usec (tablet_event); device = libinput_device_get_user_data (libinput_device); in = state == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN; libinput_tool = libinput_event_tablet_tool_get_tool (tablet_event); if (in) input_device_update_tool (device, libinput_tool); notify_proximity (device, time, in); if (!in) input_device_update_tool (device, NULL); update_tablet_cursor_state (seat, device, in); break; } case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: { uint64_t time_us; uint32_t button_state; struct libinput_event_tablet_tool *tablet_event = libinput_event_get_tablet_tool_event (event); uint32_t tablet_button; process_tablet_axis (seat, event); device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_tablet_tool_get_time_usec (tablet_event); tablet_button = libinput_event_tablet_tool_get_button (tablet_event); button_state = libinput_event_tablet_tool_get_button_state (tablet_event) == LIBINPUT_BUTTON_STATE_PRESSED; meta_seat_native_notify_button (seat_from_device (device), device, time_us, tablet_button, button_state); break; } case LIBINPUT_EVENT_TABLET_TOOL_TIP: { uint64_t time_us; uint32_t button_state; struct libinput_event_tablet_tool *tablet_event = libinput_event_get_tablet_tool_event (event); device = libinput_device_get_user_data (libinput_device); time_us = libinput_event_tablet_tool_get_time_usec (tablet_event); button_state = libinput_event_tablet_tool_get_tip_state (tablet_event) == LIBINPUT_TABLET_TOOL_TIP_DOWN; /* To avoid jumps on tip, notify axes before the tip down event but after the tip up event */ if (button_state) process_tablet_axis (seat, event); meta_seat_native_notify_button (seat_from_device (device), device, time_us, BTN_TOUCH, button_state); if (!button_state) process_tablet_axis (seat, event); break; } case LIBINPUT_EVENT_TABLET_PAD_BUTTON: { uint64_t time; uint32_t button_state, button, group, mode; struct libinput_tablet_pad_mode_group *mode_group; struct libinput_event_tablet_pad *pad_event = libinput_event_get_tablet_pad_event (event); device = libinput_device_get_user_data (libinput_device); time = libinput_event_tablet_pad_get_time_usec (pad_event); mode_group = libinput_event_tablet_pad_get_mode_group (pad_event); group = libinput_tablet_pad_mode_group_get_index (mode_group); mode = libinput_event_tablet_pad_get_mode (pad_event); button = libinput_event_tablet_pad_get_button_number (pad_event); button_state = libinput_event_tablet_pad_get_button_state (pad_event) == LIBINPUT_BUTTON_STATE_PRESSED; notify_pad_button (device, time, button, group, mode, button_state); break; } case LIBINPUT_EVENT_TABLET_PAD_STRIP: { uint64_t time; uint32_t number, source, group, mode; struct libinput_tablet_pad_mode_group *mode_group; struct libinput_event_tablet_pad *pad_event = libinput_event_get_tablet_pad_event (event); double value; device = libinput_device_get_user_data (libinput_device); time = libinput_event_tablet_pad_get_time_usec (pad_event); number = libinput_event_tablet_pad_get_strip_number (pad_event); value = libinput_event_tablet_pad_get_strip_position (pad_event); source = libinput_event_tablet_pad_get_strip_source (pad_event); mode_group = libinput_event_tablet_pad_get_mode_group (pad_event); group = libinput_tablet_pad_mode_group_get_index (mode_group); mode = libinput_event_tablet_pad_get_mode (pad_event); notify_pad_strip (device, time, number, source, group, mode, value); break; } case LIBINPUT_EVENT_TABLET_PAD_RING: { uint64_t time; uint32_t number, source, group, mode; struct libinput_tablet_pad_mode_group *mode_group; struct libinput_event_tablet_pad *pad_event = libinput_event_get_tablet_pad_event (event); double angle; device = libinput_device_get_user_data (libinput_device); time = libinput_event_tablet_pad_get_time_usec (pad_event); number = libinput_event_tablet_pad_get_ring_number (pad_event); angle = libinput_event_tablet_pad_get_ring_position (pad_event); source = libinput_event_tablet_pad_get_ring_source (pad_event); mode_group = libinput_event_tablet_pad_get_mode_group (pad_event); group = libinput_tablet_pad_mode_group_get_index (mode_group); mode = libinput_event_tablet_pad_get_mode (pad_event); notify_pad_ring (device, time, number, source, group, mode, angle); break; } case LIBINPUT_EVENT_SWITCH_TOGGLE: { struct libinput_event_switch *switch_event = libinput_event_get_switch_event (event); enum libinput_switch sw = libinput_event_switch_get_switch (switch_event); enum libinput_switch_state state = libinput_event_switch_get_switch_state (switch_event); if (sw == LIBINPUT_SWITCH_TABLET_MODE) { seat->tablet_mode_switch_state = (state == LIBINPUT_SWITCH_STATE_ON); update_touch_mode (seat); } break; } default: handled = FALSE; } return handled; } static void process_event (MetaSeatNative *seat, struct libinput_event *event) { if (process_base_event (seat, event)) return; if (process_device_event (seat, event)) return; } static void process_events (MetaSeatNative *seat) { struct libinput_event *event; while ((event = libinput_get_event (seat->libinput))) { process_event(seat, event); libinput_event_destroy(event); } } static int open_restricted (const char *path, int flags, void *user_data) { gint fd; if (device_open_callback) { GError *error = NULL; fd = device_open_callback (path, flags, device_callback_data, &error); if (fd < 0) { g_warning ("Could not open device %s: %s", path, error->message); g_error_free (error); } } else { fd = open (path, O_RDWR | O_NONBLOCK); if (fd < 0) { g_warning ("Could not open device %s: %s", path, strerror (errno)); } } return fd; } static void close_restricted (int fd, void *user_data) { if (device_close_callback) device_close_callback (fd, device_callback_data); else close (fd); } static const struct libinput_interface libinput_interface = { open_restricted, close_restricted }; static void meta_seat_native_constructed (GObject *object) { MetaSeatNative *seat = META_SEAT_NATIVE (object); ClutterInputDevice *device; MetaEventSource *source; struct udev *udev; struct xkb_keymap *xkb_keymap; device = meta_input_device_native_new_virtual ( seat, CLUTTER_POINTER_DEVICE, CLUTTER_INPUT_MODE_LOGICAL); seat->pointer_x = INITIAL_POINTER_X; seat->pointer_y = INITIAL_POINTER_Y; meta_input_device_native_set_coords (META_INPUT_DEVICE_NATIVE (device), seat->pointer_x, seat->pointer_y); seat->core_pointer = device; device = meta_input_device_native_new_virtual ( seat, CLUTTER_KEYBOARD_DEVICE, CLUTTER_INPUT_MODE_LOGICAL); seat->core_keyboard = device; udev = udev_new (); if (G_UNLIKELY (udev == NULL)) { g_warning ("Failed to create udev object"); return; } seat->libinput = libinput_udev_create_context (&libinput_interface, seat, udev); if (seat->libinput == NULL) { g_critical ("Failed to create the libinput object."); return; } if (libinput_udev_assign_seat (seat->libinput, seat->seat_id) == -1) { g_critical ("Failed to assign a seat to the libinput object."); libinput_unref (seat->libinput); seat->libinput = NULL; return; } udev_unref (udev); seat->udev_client = g_udev_client_new ((const gchar *[]) { "input", NULL }); source = meta_event_source_new (seat); seat->event_source = source; seat->keymap = g_object_new (META_TYPE_KEYMAP_NATIVE, NULL); xkb_keymap = meta_keymap_native_get_keyboard_map (seat->keymap); if (xkb_keymap) { seat->xkb = xkb_state_new (xkb_keymap); seat->caps_lock_led = xkb_keymap_led_get_index (xkb_keymap, XKB_LED_NAME_CAPS); seat->num_lock_led = xkb_keymap_led_get_index (xkb_keymap, XKB_LED_NAME_NUM); seat->scroll_lock_led = xkb_keymap_led_get_index (xkb_keymap, XKB_LED_NAME_SCROLL); } seat->has_touchscreen = has_touchscreen (seat); seat->has_tablet_switch = has_tablet_switch (seat); update_touch_mode (seat); if (G_OBJECT_CLASS (meta_seat_native_parent_class)->constructed) G_OBJECT_CLASS (meta_seat_native_parent_class)->constructed (object); } static void meta_seat_native_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaSeatNative *seat_native = META_SEAT_NATIVE (object); switch (prop_id) { case PROP_SEAT_ID: seat_native->seat_id = g_value_dup_string (value); break; case PROP_TOUCH_MODE: default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void meta_seat_native_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaSeatNative *seat_native = META_SEAT_NATIVE (object); switch (prop_id) { case PROP_SEAT_ID: g_value_set_string (value, seat_native->seat_id); break; case PROP_TOUCH_MODE: g_value_set_boolean (value, seat_native->touch_mode); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void meta_seat_native_dispose (GObject *object) { MetaSeatNative *seat = META_SEAT_NATIVE (object); if (seat->libinput) { libinput_unref (seat->libinput); seat->libinput = NULL; } G_OBJECT_CLASS (meta_seat_native_parent_class)->dispose (object); } static void meta_seat_native_finalize (GObject *object) { MetaSeatNative *seat = META_SEAT_NATIVE (object); GSList *iter; for (iter = seat->devices; iter; iter = g_slist_next (iter)) { ClutterInputDevice *device = iter->data; g_object_unref (device); } g_slist_free (seat->devices); if (seat->touch_states) g_hash_table_destroy (seat->touch_states); g_hash_table_destroy (seat->reserved_virtual_slots); g_clear_pointer (&seat->tablet_cursors, g_hash_table_unref); g_object_unref (seat->cursor_renderer); g_object_unref (seat->udev_client); meta_event_source_free (seat->event_source); xkb_state_unref (seat->xkb); meta_seat_native_clear_repeat_timer (seat); if (seat->libinput_seat) libinput_seat_unref (seat->libinput_seat); g_list_free (seat->free_device_ids); g_free (seat->seat_id); G_OBJECT_CLASS (meta_seat_native_parent_class)->finalize (object); } static ClutterInputDevice * meta_seat_native_get_pointer (ClutterSeat *seat) { MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); return seat_native->core_pointer; } static ClutterInputDevice * meta_seat_native_get_keyboard (ClutterSeat *seat) { MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); return seat_native->core_keyboard; } static const GList * meta_seat_native_peek_devices (ClutterSeat *seat) { MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); return (const GList *) seat_native->devices; } static void meta_seat_native_bell_notify (ClutterSeat *seat) { MetaDisplay *display = meta_get_display (); meta_bell_notify (display, NULL); } static ClutterKeymap * meta_seat_native_get_keymap (ClutterSeat *seat) { MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); return CLUTTER_KEYMAP (seat_native->keymap); } static void meta_seat_native_copy_event_data (ClutterSeat *seat, const ClutterEvent *src, ClutterEvent *dest) { MetaEventNative *event_evdev; event_evdev = _clutter_event_get_platform_data (src); if (event_evdev != NULL) _clutter_event_set_platform_data (dest, meta_event_native_copy (event_evdev)); } static void meta_seat_native_free_event_data (ClutterSeat *seat, ClutterEvent *event) { MetaEventNative *event_evdev; event_evdev = _clutter_event_get_platform_data (event); if (event_evdev != NULL) meta_event_native_free (event_evdev); } static guint bump_virtual_touch_slot_base (MetaSeatNative *seat_native) { while (TRUE) { if (seat_native->virtual_touch_slot_base < 0x100) seat_native->virtual_touch_slot_base = 0x100; seat_native->virtual_touch_slot_base += CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS; if (!g_hash_table_lookup (seat_native->reserved_virtual_slots, GUINT_TO_POINTER (seat_native->virtual_touch_slot_base))) break; } return seat_native->virtual_touch_slot_base; } static ClutterVirtualInputDevice * meta_seat_native_create_virtual_device (ClutterSeat *seat, ClutterInputDeviceType device_type) { MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); guint slot_base; slot_base = bump_virtual_touch_slot_base (seat_native); g_hash_table_add (seat_native->reserved_virtual_slots, GUINT_TO_POINTER (slot_base)); return g_object_new (META_TYPE_VIRTUAL_INPUT_DEVICE_NATIVE, "seat", seat, "slot-base", slot_base, "device-type", device_type, NULL); } void meta_seat_native_release_touch_slots (MetaSeatNative *seat, guint base_slot) { g_hash_table_remove (seat->reserved_virtual_slots, GUINT_TO_POINTER (base_slot)); } static ClutterVirtualDeviceType meta_seat_native_get_supported_virtual_device_types (ClutterSeat *seat) { return (CLUTTER_VIRTUAL_DEVICE_TYPE_KEYBOARD | CLUTTER_VIRTUAL_DEVICE_TYPE_POINTER | CLUTTER_VIRTUAL_DEVICE_TYPE_TOUCHSCREEN); } static void meta_seat_native_compress_motion (ClutterSeat *seat, ClutterEvent *event, const ClutterEvent *to_discard) { double dx, dy; double dx_unaccel, dy_unaccel; double dst_dx = 0.0, dst_dy = 0.0; double dst_dx_unaccel = 0.0, dst_dy_unaccel = 0.0; if (!meta_event_native_get_relative_motion (to_discard, &dx, &dy, &dx_unaccel, &dy_unaccel)) return; meta_event_native_get_relative_motion (event, &dst_dx, &dst_dy, &dst_dx_unaccel, &dst_dy_unaccel); meta_event_native_set_relative_motion (event, dx + dst_dx, dy + dst_dy, dx_unaccel + dst_dx_unaccel, dy_unaccel + dst_dy_unaccel); } static void meta_seat_native_warp_pointer (ClutterSeat *seat, int x, int y) { MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); MetaBackend *backend = meta_get_backend (); MetaCursorRenderer *cursor_renderer = meta_backend_get_cursor_renderer (backend); MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); notify_absolute_motion (seat_native->core_pointer, 0, x, y, NULL); meta_cursor_renderer_update_position (cursor_renderer); meta_cursor_tracker_invalidate_position (cursor_tracker); } static gboolean meta_seat_native_query_state (ClutterSeat *seat, ClutterInputDevice *device, ClutterEventSequence *sequence, graphene_point_t *coords, ClutterModifierType *modifiers) { MetaInputDeviceNative *device_native = META_INPUT_DEVICE_NATIVE (device); MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); if (sequence) { MetaTouchState *touch_state; int slot; slot = meta_event_native_sequence_get_slot (sequence); touch_state = meta_seat_native_lookup_touch_state (seat_native, slot); if (!touch_state) return FALSE; if (coords) { coords->x = touch_state->coords.x; coords->y = touch_state->coords.y; } if (modifiers) *modifiers = meta_xkb_translate_modifiers (seat_native->xkb, 0); return TRUE; } else { if (coords) { coords->x = device_native->pointer_x; coords->y = device_native->pointer_y; } if (modifiers) { *modifiers = meta_xkb_translate_modifiers (seat_native->xkb, seat_native->button_state); } return TRUE; } } static void meta_seat_native_class_init (MetaSeatNativeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); ClutterSeatClass *seat_class = CLUTTER_SEAT_CLASS (klass); object_class->constructed = meta_seat_native_constructed; object_class->set_property = meta_seat_native_set_property; object_class->get_property = meta_seat_native_get_property; object_class->dispose = meta_seat_native_dispose; object_class->finalize = meta_seat_native_finalize; seat_class->get_pointer = meta_seat_native_get_pointer; seat_class->get_keyboard = meta_seat_native_get_keyboard; seat_class->peek_devices = meta_seat_native_peek_devices; seat_class->bell_notify = meta_seat_native_bell_notify; seat_class->get_keymap = meta_seat_native_get_keymap; seat_class->copy_event_data = meta_seat_native_copy_event_data; seat_class->free_event_data = meta_seat_native_free_event_data; seat_class->create_virtual_device = meta_seat_native_create_virtual_device; seat_class->get_supported_virtual_device_types = meta_seat_native_get_supported_virtual_device_types; seat_class->compress_motion = meta_seat_native_compress_motion; seat_class->warp_pointer = meta_seat_native_warp_pointer; seat_class->handle_device_event = meta_seat_native_handle_device_event; seat_class->query_state = meta_seat_native_query_state; props[PROP_SEAT_ID] = g_param_spec_string ("seat-id", "Seat ID", "Seat ID", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, N_PROPS, props); g_object_class_override_property (object_class, PROP_TOUCH_MODE, "touch-mode"); } static void meta_seat_native_init (MetaSeatNative *seat) { seat->device_id_next = INITIAL_DEVICE_ID; seat->repeat = TRUE; seat->repeat_delay = 250; /* ms */ seat->repeat_interval = 33; /* ms */ seat->barrier_manager = meta_barrier_manager_native_new (); seat->reserved_virtual_slots = g_hash_table_new (NULL, NULL); } /** * meta_seat_native_set_device_callbacks: (skip) * @open_callback: the user replacement for open() * @close_callback: the user replacement for close() * @user_data: user data for @callback * * Through this function, the application can set a custom callback * to be invoked when Clutter is about to open an evdev device. It can do * so if special handling is needed, for example to circumvent permission * problems. * * Setting @callback to %NULL will reset the default behavior. * * For reliable effects, this function must be called before clutter_init(). */ void meta_seat_native_set_device_callbacks (MetaOpenDeviceCallback open_callback, MetaCloseDeviceCallback close_callback, gpointer user_data) { device_open_callback = open_callback; device_close_callback = close_callback; device_callback_data = user_data; } void meta_seat_native_update_xkb_state (MetaSeatNative *seat) { xkb_mod_mask_t latched_mods; xkb_mod_mask_t locked_mods; struct xkb_keymap *xkb_keymap; xkb_keymap = meta_keymap_native_get_keyboard_map (seat->keymap); latched_mods = xkb_state_serialize_mods (seat->xkb, XKB_STATE_MODS_LATCHED); locked_mods = xkb_state_serialize_mods (seat->xkb, XKB_STATE_MODS_LOCKED); xkb_state_unref (seat->xkb); seat->xkb = xkb_state_new (xkb_keymap); xkb_state_update_mask (seat->xkb, 0, /* depressed */ latched_mods, locked_mods, 0, 0, seat->layout_idx); seat->caps_lock_led = xkb_keymap_led_get_index (xkb_keymap, XKB_LED_NAME_CAPS); seat->num_lock_led = xkb_keymap_led_get_index (xkb_keymap, XKB_LED_NAME_NUM); seat->scroll_lock_led = xkb_keymap_led_get_index (xkb_keymap, XKB_LED_NAME_SCROLL); meta_seat_native_sync_leds (seat); } /** * meta_seat_native_release_devices: * * Releases all the evdev devices that Clutter is currently managing. This api * is typically used when switching away from the Clutter application when * switching tty. The devices can be reclaimed later with a call to * meta_seat_native_reclaim_devices(). * * This function should only be called after clutter has been initialized. */ void meta_seat_native_release_devices (MetaSeatNative *seat) { g_return_if_fail (META_IS_SEAT_NATIVE (seat)); if (seat->released) { g_warning ("meta_seat_native_release_devices() shouldn't be called " "multiple times without a corresponding call to " "meta_seat_native_reclaim_devices() first"); return; } libinput_suspend (seat->libinput); process_events (seat); seat->released = TRUE; } /** * meta_seat_native_reclaim_devices: * * This causes Clutter to re-probe for evdev devices. This is must only be * called after a corresponding call to meta_seat_native_release_devices() * was previously used to release all evdev devices. This API is typically * used when a clutter application using evdev has regained focus due to * switching ttys. * * This function should only be called after clutter has been initialized. */ void meta_seat_native_reclaim_devices (MetaSeatNative *seat) { if (!seat->released) { g_warning ("Spurious call to meta_seat_native_reclaim_devices() without " "previous call to meta_seat_native_release_devices"); return; } libinput_resume (seat->libinput); meta_seat_native_update_xkb_state (seat); process_events (seat); seat->released = FALSE; } /** * meta_seat_native_set_keyboard_map: (skip) * @seat: the #ClutterSeat created by the evdev backend * @keymap: the new keymap * * Instructs @evdev to use the specified keyboard map. This will cause * the backend to drop the state and create a new one with the new * map. To avoid state being lost, callers should ensure that no key * is pressed when calling this function. */ void meta_seat_native_set_keyboard_map (MetaSeatNative *seat, struct xkb_keymap *xkb_keymap) { ClutterKeymap *keymap; g_return_if_fail (META_IS_SEAT_NATIVE (seat)); g_return_if_fail (xkb_keymap != NULL); keymap = clutter_seat_get_keymap (CLUTTER_SEAT (seat)); meta_keymap_native_set_keyboard_map (META_KEYMAP_NATIVE (keymap), xkb_keymap); meta_seat_native_update_xkb_state (seat); } /** * meta_seat_native_get_keyboard_map: (skip) * @seat: the #ClutterSeat created by the evdev backend * * Retrieves the #xkb_keymap in use by the evdev backend. * * Return value: the #xkb_keymap. */ struct xkb_keymap * meta_seat_native_get_keyboard_map (MetaSeatNative *seat) { g_return_val_if_fail (META_IS_SEAT_NATIVE (seat), NULL); return xkb_state_get_keymap (seat->xkb); } /** * meta_seat_native_set_keyboard_layout_index: (skip) * @seat: the #ClutterSeat created by the evdev backend * @idx: the xkb layout index to set * * Sets the xkb layout index on the backend's #xkb_state . */ void meta_seat_native_set_keyboard_layout_index (MetaSeatNative *seat, xkb_layout_index_t idx) { xkb_mod_mask_t depressed_mods; xkb_mod_mask_t latched_mods; xkb_mod_mask_t locked_mods; struct xkb_state *state; g_return_if_fail (META_IS_SEAT_NATIVE (seat)); state = seat->xkb; depressed_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_DEPRESSED); latched_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_LATCHED); locked_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_LOCKED); xkb_state_update_mask (state, depressed_mods, latched_mods, locked_mods, 0, 0, idx); seat->layout_idx = idx; } /** * meta_seat_native_get_keyboard_layout_index: (skip) */ xkb_layout_index_t meta_seat_native_get_keyboard_layout_index (MetaSeatNative *seat) { return seat->layout_idx; } /** * meta_seat_native_set_keyboard_numlock: (skip) * @seat: the #ClutterSeat created by the evdev backend * @numlock_set: TRUE to set NumLock ON, FALSE otherwise. * * Sets the NumLock state on the backend's #xkb_state . */ void meta_seat_native_set_keyboard_numlock (MetaSeatNative *seat, gboolean numlock_state) { xkb_mod_mask_t depressed_mods; xkb_mod_mask_t latched_mods; xkb_mod_mask_t locked_mods; xkb_mod_mask_t group_mods; xkb_mod_mask_t numlock; struct xkb_keymap *xkb_keymap; ClutterKeymap *keymap; g_return_if_fail (META_IS_SEAT_NATIVE (seat)); keymap = clutter_seat_get_keymap (CLUTTER_SEAT (seat)); xkb_keymap = meta_keymap_native_get_keyboard_map (META_KEYMAP_NATIVE (keymap)); numlock = (1 << xkb_keymap_mod_get_index (xkb_keymap, "Mod2")); depressed_mods = xkb_state_serialize_mods (seat->xkb, XKB_STATE_MODS_DEPRESSED); latched_mods = xkb_state_serialize_mods (seat->xkb, XKB_STATE_MODS_LATCHED); locked_mods = xkb_state_serialize_mods (seat->xkb, XKB_STATE_MODS_LOCKED); group_mods = xkb_state_serialize_layout (seat->xkb, XKB_STATE_LAYOUT_EFFECTIVE); if (numlock_state) locked_mods |= numlock; else locked_mods &= ~numlock; xkb_state_update_mask (seat->xkb, depressed_mods, latched_mods, locked_mods, 0, 0, group_mods); meta_seat_native_sync_leds (seat); } /** * meta_seat_native_set_keyboard_repeat: * @seat: the #ClutterSeat created by the evdev backend * @repeat: whether to enable or disable keyboard repeat events * @delay: the delay in ms between the hardware key press event and * the first synthetic event * @interval: the period in ms between consecutive synthetic key * press events * * Enables or disables sythetic key press events, allowing for initial * delay and interval period to be specified. */ void meta_seat_native_set_keyboard_repeat (MetaSeatNative *seat, gboolean repeat, uint32_t delay, uint32_t interval) { g_return_if_fail (META_IS_SEAT_NATIVE (seat)); seat->repeat = repeat; seat->repeat_delay = delay; seat->repeat_interval = interval; } struct xkb_state * meta_seat_native_get_xkb_state (MetaSeatNative *seat) { return seat->xkb; } MetaBarrierManagerNative * meta_seat_native_get_barrier_manager (MetaSeatNative *seat) { return seat->barrier_manager; } void meta_seat_native_set_pointer_constraint (MetaSeatNative *seat, MetaPointerConstraintImpl *constraint_impl) { if (!g_set_object (&seat->pointer_constraint, constraint_impl)) return; if (constraint_impl) { meta_pointer_constraint_impl_ensure_constrained (constraint_impl, seat->core_pointer); } } MetaCursorRenderer * meta_seat_native_maybe_ensure_cursor_renderer (MetaSeatNative *seat_native, ClutterInputDevice *device) { if (device == seat_native->core_pointer) { if (!seat_native->cursor_renderer) { MetaCursorRendererNative *cursor_renderer_native; cursor_renderer_native = meta_cursor_renderer_native_new (meta_get_backend (), seat_native->core_pointer); seat_native->cursor_renderer = META_CURSOR_RENDERER (cursor_renderer_native); } return seat_native->cursor_renderer; } if (seat_native->tablet_cursors && clutter_input_device_get_device_type (device) == CLUTTER_TABLET_DEVICE) return g_hash_table_lookup (seat_native->tablet_cursors, device); return NULL; } void meta_seat_native_set_viewports (MetaSeatNative *seat, MetaViewportInfo *viewports) { g_set_object (&seat->viewports, viewports); }