/* * 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: Jonas Ã…dahl */ #include "config.h" #include #include #include "backends/native/meta-input-device-native.h" #include "backends/native/meta-keymap-native.h" #include "backends/native/meta-seat-native.h" #include "backends/native/meta-virtual-input-device-native.h" #include "clutter/clutter-mutter.h" #include "meta/util.h" enum { PROP_0, PROP_SEAT, PROP_LAST }; static GParamSpec *obj_props[PROP_LAST]; struct _MetaVirtualInputDeviceNative { ClutterVirtualInputDevice parent; ClutterInputDevice *device; MetaSeatNative *seat; int button_count[KEY_CNT]; }; G_DEFINE_TYPE (MetaVirtualInputDeviceNative, meta_virtual_input_device_native, CLUTTER_TYPE_VIRTUAL_INPUT_DEVICE) typedef enum _EvdevButtonType { EVDEV_BUTTON_TYPE_NONE, EVDEV_BUTTON_TYPE_KEY, EVDEV_BUTTON_TYPE_BUTTON, } EvdevButtonType; static int update_button_count (MetaVirtualInputDeviceNative *virtual_evdev, uint32_t button, uint32_t state) { if (state) return ++virtual_evdev->button_count[button]; else return --virtual_evdev->button_count[button]; } static EvdevButtonType get_button_type (uint16_t code) { switch (code) { case BTN_TOOL_PEN: case BTN_TOOL_RUBBER: case BTN_TOOL_BRUSH: case BTN_TOOL_PENCIL: case BTN_TOOL_AIRBRUSH: case BTN_TOOL_MOUSE: case BTN_TOOL_LENS: case BTN_TOOL_QUINTTAP: case BTN_TOOL_DOUBLETAP: case BTN_TOOL_TRIPLETAP: case BTN_TOOL_QUADTAP: case BTN_TOOL_FINGER: case BTN_TOUCH: return EVDEV_BUTTON_TYPE_NONE; } if (code >= KEY_ESC && code <= KEY_MICMUTE) return EVDEV_BUTTON_TYPE_KEY; if (code >= BTN_MISC && code <= BTN_GEAR_UP) return EVDEV_BUTTON_TYPE_BUTTON; if (code >= KEY_OK && code <= KEY_LIGHTS_TOGGLE) return EVDEV_BUTTON_TYPE_KEY; if (code >= BTN_DPAD_UP && code <= BTN_DPAD_RIGHT) return EVDEV_BUTTON_TYPE_BUTTON; if (code >= KEY_ALS_TOGGLE && code <= KEY_KBDINPUTASSIST_CANCEL) return EVDEV_BUTTON_TYPE_KEY; if (code >= BTN_TRIGGER_HAPPY && code <= BTN_TRIGGER_HAPPY40) return EVDEV_BUTTON_TYPE_BUTTON; return EVDEV_BUTTON_TYPE_NONE; } static void release_pressed_buttons (ClutterVirtualInputDevice *virtual_device) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); int code; uint64_t time_us; time_us = g_get_monotonic_time (); meta_topic (META_DEBUG_INPUT, "Releasing pressed buttons while destroying virtual input device " "(device %p)", virtual_device); for (code = 0; code < G_N_ELEMENTS (virtual_evdev->button_count); code++) { if (virtual_evdev->button_count[code] == 0) continue; switch (get_button_type (code)) { case EVDEV_BUTTON_TYPE_KEY: meta_seat_native_notify_key (virtual_evdev->seat, virtual_evdev->device, time_us, code, CLUTTER_KEY_STATE_RELEASED, TRUE); break; case EVDEV_BUTTON_TYPE_BUTTON: meta_seat_native_notify_button (virtual_evdev->seat, virtual_evdev->device, time_us, code, CLUTTER_BUTTON_STATE_RELEASED); break; case EVDEV_BUTTON_TYPE_NONE: g_assert_not_reached (); } update_button_count (virtual_evdev, code, 0); } } static void meta_virtual_input_device_native_notify_relative_motion (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, double dx, double dy) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); meta_seat_native_notify_relative_motion (virtual_evdev->seat, virtual_evdev->device, time_us, dx, dy, dx, dy); } static void meta_virtual_input_device_native_notify_absolute_motion (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, double x, double y) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); meta_seat_native_notify_absolute_motion (virtual_evdev->seat, virtual_evdev->device, time_us, x, y, NULL); } static int translate_to_evdev_button (int clutter_button) { switch (clutter_button) { case CLUTTER_BUTTON_PRIMARY: return BTN_LEFT; case CLUTTER_BUTTON_SECONDARY: return BTN_RIGHT; case CLUTTER_BUTTON_MIDDLE: return BTN_MIDDLE; default: /* * For compatibility reasons, all additional buttons go after the old * 4-7 scroll ones. */ return clutter_button + (BTN_LEFT - 1) - 4; } } static void meta_virtual_input_device_native_notify_button (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, uint32_t button, ClutterButtonState button_state) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); int button_count; int evdev_button; g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); evdev_button = translate_to_evdev_button (button); if (get_button_type (evdev_button) != EVDEV_BUTTON_TYPE_BUTTON) { g_warning ("Unknown/invalid virtual device button 0x%x pressed", evdev_button); return; } button_count = update_button_count (virtual_evdev, evdev_button, button_state); if (button_count < 0 || button_count > 1) { g_warning ("Received multiple virtual 0x%x button %s (ignoring)", evdev_button, button_state == CLUTTER_BUTTON_STATE_PRESSED ? "presses" : "releases"); update_button_count (virtual_evdev, evdev_button, 1 - button_state); return; } meta_topic (META_DEBUG_INPUT, "Emitting virtual button-%s of button 0x%x (device %p)", button_state == CLUTTER_BUTTON_STATE_PRESSED ? "press" : "release", evdev_button, virtual_device); meta_seat_native_notify_button (virtual_evdev->seat, virtual_evdev->device, time_us, evdev_button, button_state); } static void meta_virtual_input_device_native_notify_key (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, uint32_t key, ClutterKeyState key_state) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); int key_count; g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); if (get_button_type (key) != EVDEV_BUTTON_TYPE_KEY) { g_warning ("Unknown/invalid virtual device key 0x%x pressed", key); return; } key_count = update_button_count (virtual_evdev, key, key_state); if (key_count < 0 || key_count > 1) { g_warning ("Received multiple virtual 0x%x key %s (ignoring)", key, key_state == CLUTTER_KEY_STATE_PRESSED ? "presses" : "releases"); update_button_count (virtual_evdev, key, 1 - key_state); return; } meta_topic (META_DEBUG_INPUT, "Emitting virtual key-%s of key 0x%x (device %p)", key_state == CLUTTER_KEY_STATE_PRESSED ? "press" : "release", key, virtual_device); meta_seat_native_notify_key (virtual_evdev->seat, virtual_evdev->device, time_us, key, key_state, TRUE); } static gboolean pick_keycode_for_keyval_in_current_group (ClutterVirtualInputDevice *virtual_device, guint keyval, guint *keycode_out, guint *level_out) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); ClutterBackend *backend; ClutterKeymap *keymap; struct xkb_keymap *xkb_keymap; struct xkb_state *state; guint keycode, layout; xkb_keycode_t min_keycode, max_keycode; backend = clutter_get_default_backend (); keymap = clutter_seat_get_keymap (clutter_backend_get_default_seat (backend)); xkb_keymap = meta_keymap_native_get_keyboard_map (META_KEYMAP_NATIVE (keymap)); state = virtual_evdev->seat->xkb; layout = xkb_state_serialize_layout (state, XKB_STATE_LAYOUT_EFFECTIVE); min_keycode = xkb_keymap_min_keycode (xkb_keymap); max_keycode = xkb_keymap_max_keycode (xkb_keymap); for (keycode = min_keycode; keycode < max_keycode; keycode++) { gint num_levels, level; num_levels = xkb_keymap_num_levels_for_key (xkb_keymap, keycode, layout); for (level = 0; level < num_levels; level++) { const xkb_keysym_t *syms; gint num_syms, sym; num_syms = xkb_keymap_key_get_syms_by_level (xkb_keymap, keycode, layout, level, &syms); for (sym = 0; sym < num_syms; sym++) { if (syms[sym] == keyval) { *keycode_out = keycode; if (level_out) *level_out = level; return TRUE; } } } } return FALSE; } static void apply_level_modifiers (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, uint32_t level, uint32_t key_state) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); guint keysym, keycode, evcode; if (level == 0) return; if (level == 1) { keysym = XKB_KEY_Shift_L; } else if (level == 2) { keysym = XKB_KEY_ISO_Level3_Shift; } else { g_warning ("Unhandled level: %d", level); return; } if (!pick_keycode_for_keyval_in_current_group (virtual_device, keysym, &keycode, NULL)) return; clutter_input_device_keycode_to_evdev (virtual_evdev->device, keycode, &evcode); meta_topic (META_DEBUG_INPUT, "Emitting virtual key-%s of modifier key 0x%x (device %p)", key_state == CLUTTER_KEY_STATE_PRESSED ? "press" : "release", evcode, virtual_device); meta_seat_native_notify_key (virtual_evdev->seat, virtual_evdev->device, time_us, evcode, key_state, TRUE); } static void meta_virtual_input_device_native_notify_keyval (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, uint32_t keyval, ClutterKeyState key_state) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); int key_count; guint keycode = 0, level = 0, evcode = 0; g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); if (!pick_keycode_for_keyval_in_current_group (virtual_device, keyval, &keycode, &level)) { g_warning ("No keycode found for keyval %x in current group", keyval); return; } clutter_input_device_keycode_to_evdev (virtual_evdev->device, keycode, &evcode); if (get_button_type (evcode) != EVDEV_BUTTON_TYPE_KEY) { g_warning ("Unknown/invalid virtual device key 0x%x pressed", evcode); return; } key_count = update_button_count (virtual_evdev, evcode, key_state); if (key_count < 0 || key_count > 1) { g_warning ("Received multiple virtual 0x%x key %s (ignoring)", evcode, key_state == CLUTTER_KEY_STATE_PRESSED ? "presses" : "releases"); update_button_count (virtual_evdev, evcode, 1 - key_state); return; } meta_topic (META_DEBUG_INPUT, "Emitting virtual key-%s of key 0x%x with modifier level %d, " "press count %d (device %p)", key_state == CLUTTER_KEY_STATE_PRESSED ? "press" : "release", evcode, level, key_count, virtual_device); if (key_state) apply_level_modifiers (virtual_device, time_us, level, key_state); meta_seat_native_notify_key (virtual_evdev->seat, virtual_evdev->device, time_us, evcode, key_state, TRUE); if (!key_state) apply_level_modifiers (virtual_device, time_us, level, key_state); } static void direction_to_discrete (ClutterScrollDirection direction, double *discrete_dx, double *discrete_dy) { switch (direction) { case CLUTTER_SCROLL_UP: *discrete_dx = 0.0; *discrete_dy = -1.0; break; case CLUTTER_SCROLL_DOWN: *discrete_dx = 0.0; *discrete_dy = 1.0; break; case CLUTTER_SCROLL_LEFT: *discrete_dx = -1.0; *discrete_dy = 0.0; break; case CLUTTER_SCROLL_RIGHT: *discrete_dx = 1.0; *discrete_dy = 0.0; break; case CLUTTER_SCROLL_SMOOTH: g_assert_not_reached (); break; } } static void meta_virtual_input_device_native_notify_discrete_scroll (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, ClutterScrollDirection direction, ClutterScrollSource scroll_source) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); double discrete_dx = 0.0, discrete_dy = 0.0; g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); direction_to_discrete (direction, &discrete_dx, &discrete_dy); meta_seat_native_notify_discrete_scroll (virtual_evdev->seat, virtual_evdev->device, time_us, discrete_dx, discrete_dy, scroll_source); } static void meta_virtual_input_device_native_notify_scroll_continuous (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, double dx, double dy, ClutterScrollSource scroll_source, ClutterScrollFinishFlags finish_flags) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); meta_seat_native_notify_scroll_continuous (virtual_evdev->seat, virtual_evdev->device, time_us, dx, dy, scroll_source, CLUTTER_SCROLL_FINISHED_NONE); } static void meta_virtual_input_device_native_notify_touch_down (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, int device_slot, double x, double y) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); MetaInputDeviceNative *device_evdev = META_INPUT_DEVICE_NATIVE (virtual_evdev->device); MetaTouchState *touch_state; g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); touch_state = meta_input_device_native_acquire_touch_state (device_evdev, device_slot); if (!touch_state) return; touch_state->coords.x = x; touch_state->coords.y = y; meta_seat_native_notify_touch_event (virtual_evdev->seat, virtual_evdev->device, CLUTTER_TOUCH_BEGIN, time_us, touch_state->seat_slot, touch_state->coords.x, touch_state->coords.y); } static void meta_virtual_input_device_native_notify_touch_motion (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, int device_slot, double x, double y) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); MetaInputDeviceNative *device_evdev = META_INPUT_DEVICE_NATIVE (virtual_evdev->device); MetaTouchState *touch_state; g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); touch_state = meta_input_device_native_lookup_touch_state (device_evdev, device_slot); if (!touch_state) return; touch_state->coords.x = x; touch_state->coords.y = y; meta_seat_native_notify_touch_event (virtual_evdev->seat, virtual_evdev->device, CLUTTER_TOUCH_BEGIN, time_us, touch_state->seat_slot, touch_state->coords.x, touch_state->coords.y); } static void meta_virtual_input_device_native_notify_touch_up (ClutterVirtualInputDevice *virtual_device, uint64_t time_us, int device_slot) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_device); MetaInputDeviceNative *device_evdev = META_INPUT_DEVICE_NATIVE (virtual_evdev->device); MetaTouchState *touch_state; g_return_if_fail (virtual_evdev->device != NULL); if (time_us == CLUTTER_CURRENT_TIME) time_us = g_get_monotonic_time (); touch_state = meta_input_device_native_lookup_touch_state (device_evdev, device_slot); if (!touch_state) return; meta_seat_native_notify_touch_event (virtual_evdev->seat, virtual_evdev->device, CLUTTER_TOUCH_BEGIN, time_us, touch_state->seat_slot, touch_state->coords.x, touch_state->coords.y); meta_input_device_native_release_touch_state (device_evdev, touch_state); } static void meta_virtual_input_device_native_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (object); switch (prop_id) { case PROP_SEAT: g_value_set_pointer (value, virtual_evdev->seat); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_virtual_input_device_native_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (object); switch (prop_id) { case PROP_SEAT: virtual_evdev->seat = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_virtual_input_device_native_constructed (GObject *object) { ClutterVirtualInputDevice *virtual_device = CLUTTER_VIRTUAL_INPUT_DEVICE (object); MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (object); ClutterInputDeviceType device_type; ClutterStage *stage; device_type = clutter_virtual_input_device_get_device_type (virtual_device); meta_topic (META_DEBUG_INPUT, "Creating new virtual input device of type %d (%p)", device_type, virtual_device); virtual_evdev->device = meta_input_device_native_new_virtual (virtual_evdev->seat, device_type, CLUTTER_INPUT_MODE_PHYSICAL); stage = meta_seat_native_get_stage (virtual_evdev->seat); _clutter_input_device_set_stage (virtual_evdev->device, stage); g_signal_emit_by_name (virtual_evdev->seat, "device-added", virtual_evdev->device); } static void meta_virtual_input_device_native_dispose (GObject *object) { ClutterVirtualInputDevice *virtual_device = CLUTTER_VIRTUAL_INPUT_DEVICE (object); MetaVirtualInputDeviceNative *virtual_evdev = META_VIRTUAL_INPUT_DEVICE_NATIVE (object); GObjectClass *object_class = G_OBJECT_CLASS (meta_virtual_input_device_native_parent_class); if (virtual_evdev->device) { release_pressed_buttons (virtual_device); g_signal_emit_by_name (virtual_evdev->seat, "device-removed", virtual_evdev->device); g_clear_object (&virtual_evdev->device); } object_class->dispose (object); } static void meta_virtual_input_device_native_init (MetaVirtualInputDeviceNative *virtual_device_evdev) { } static void meta_virtual_input_device_native_class_init (MetaVirtualInputDeviceNativeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); ClutterVirtualInputDeviceClass *virtual_input_device_class = CLUTTER_VIRTUAL_INPUT_DEVICE_CLASS (klass); object_class->get_property = meta_virtual_input_device_native_get_property; object_class->set_property = meta_virtual_input_device_native_set_property; object_class->constructed = meta_virtual_input_device_native_constructed; object_class->dispose = meta_virtual_input_device_native_dispose; virtual_input_device_class->notify_relative_motion = meta_virtual_input_device_native_notify_relative_motion; virtual_input_device_class->notify_absolute_motion = meta_virtual_input_device_native_notify_absolute_motion; virtual_input_device_class->notify_button = meta_virtual_input_device_native_notify_button; virtual_input_device_class->notify_key = meta_virtual_input_device_native_notify_key; virtual_input_device_class->notify_keyval = meta_virtual_input_device_native_notify_keyval; virtual_input_device_class->notify_discrete_scroll = meta_virtual_input_device_native_notify_discrete_scroll; virtual_input_device_class->notify_scroll_continuous = meta_virtual_input_device_native_notify_scroll_continuous; virtual_input_device_class->notify_touch_down = meta_virtual_input_device_native_notify_touch_down; virtual_input_device_class->notify_touch_motion = meta_virtual_input_device_native_notify_touch_motion; virtual_input_device_class->notify_touch_up = meta_virtual_input_device_native_notify_touch_up; obj_props[PROP_SEAT] = g_param_spec_pointer ("seat", "Seat", "Seat", CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, PROP_LAST, obj_props); }