mirror of
https://github.com/brl/mutter.git
synced 2025-02-19 14:44:10 +00:00

The seat core keyboard/pointer will be "master", the ones created through ClutterVirtualInputDevice will be "slaves". https://bugzilla.gnome.org/show_bug.cgi?id=765009
591 lines
17 KiB
C
591 lines
17 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Damien Lespiau <damien.lespiau@intel.com>
|
|
* Author: Jonas Ådahl <jadahl@gmail.com>
|
|
*/
|
|
|
|
#include "clutter-build-config.h"
|
|
|
|
#include "clutter-seat-evdev.h"
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include "clutter-event-private.h"
|
|
#include "clutter-input-device-evdev.h"
|
|
#include "clutter-main.h"
|
|
|
|
/* 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
|
|
|
|
void
|
|
clutter_seat_evdev_set_libinput_seat (ClutterSeatEvdev *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
|
|
clutter_seat_evdev_sync_leds (ClutterSeatEvdev *seat)
|
|
{
|
|
GSList *iter;
|
|
ClutterInputDeviceEvdev *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;
|
|
_clutter_input_device_evdev_update_leds (device_evdev, leds);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_touch_state_free (ClutterTouchState *touch_state)
|
|
{
|
|
g_slice_free (ClutterTouchState, touch_state);
|
|
}
|
|
|
|
ClutterTouchState *
|
|
clutter_seat_evdev_add_touch (ClutterSeatEvdev *seat,
|
|
guint32 id)
|
|
{
|
|
ClutterTouchState *touch;
|
|
|
|
touch = g_slice_new0 (ClutterTouchState);
|
|
touch->id = id;
|
|
|
|
g_hash_table_insert (seat->touches, GUINT_TO_POINTER (id), touch);
|
|
|
|
return touch;
|
|
}
|
|
|
|
void
|
|
clutter_seat_evdev_remove_touch (ClutterSeatEvdev *seat,
|
|
guint32 id)
|
|
{
|
|
g_hash_table_remove (seat->touches, GUINT_TO_POINTER (id));
|
|
}
|
|
|
|
ClutterTouchState *
|
|
clutter_seat_evdev_get_touch (ClutterSeatEvdev *seat,
|
|
guint32 id)
|
|
{
|
|
return g_hash_table_lookup (seat->touches, GUINT_TO_POINTER (id));
|
|
}
|
|
|
|
ClutterSeatEvdev *
|
|
clutter_seat_evdev_new (ClutterDeviceManagerEvdev *manager_evdev)
|
|
{
|
|
ClutterDeviceManager *manager = CLUTTER_DEVICE_MANAGER (manager_evdev);
|
|
ClutterSeatEvdev *seat;
|
|
ClutterInputDevice *device;
|
|
ClutterStage *stage;
|
|
struct xkb_keymap *keymap;
|
|
|
|
seat = g_new0 (ClutterSeatEvdev, 1);
|
|
if (!seat)
|
|
return NULL;
|
|
|
|
seat->manager_evdev = manager_evdev;
|
|
device = _clutter_input_device_evdev_new_virtual (
|
|
manager, seat, CLUTTER_POINTER_DEVICE,
|
|
CLUTTER_INPUT_MODE_MASTER);
|
|
stage = _clutter_device_manager_evdev_get_stage (manager_evdev);
|
|
_clutter_input_device_set_stage (device, stage);
|
|
seat->pointer_x = INITIAL_POINTER_X;
|
|
seat->pointer_y = INITIAL_POINTER_Y;
|
|
_clutter_input_device_set_coords (device, NULL,
|
|
seat->pointer_x, seat->pointer_y,
|
|
NULL);
|
|
_clutter_device_manager_add_device (manager, device);
|
|
seat->core_pointer = device;
|
|
|
|
device = _clutter_input_device_evdev_new_virtual (
|
|
manager, seat, CLUTTER_KEYBOARD_DEVICE,
|
|
CLUTTER_INPUT_MODE_MASTER);
|
|
_clutter_input_device_set_stage (device, stage);
|
|
_clutter_device_manager_add_device (manager, device);
|
|
seat->core_keyboard = device;
|
|
|
|
seat->touches = g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) clutter_touch_state_free);
|
|
|
|
seat->repeat = TRUE;
|
|
seat->repeat_delay = 250; /* ms */
|
|
seat->repeat_interval = 33; /* ms */
|
|
|
|
keymap = _clutter_device_manager_evdev_get_keymap (manager_evdev);
|
|
if (keymap)
|
|
{
|
|
seat->xkb = xkb_state_new (keymap);
|
|
|
|
seat->caps_lock_led =
|
|
xkb_keymap_led_get_index (keymap, XKB_LED_NAME_CAPS);
|
|
seat->num_lock_led =
|
|
xkb_keymap_led_get_index (keymap, XKB_LED_NAME_NUM);
|
|
seat->scroll_lock_led =
|
|
xkb_keymap_led_get_index (keymap, XKB_LED_NAME_SCROLL);
|
|
}
|
|
|
|
return seat;
|
|
}
|
|
|
|
void
|
|
clutter_seat_evdev_clear_repeat_timer (ClutterSeatEvdev *seat)
|
|
{
|
|
if (seat->repeat_timer)
|
|
{
|
|
g_source_remove (seat->repeat_timer);
|
|
seat->repeat_timer = 0;
|
|
g_clear_object (&seat->repeat_device);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
keyboard_repeat (gpointer data)
|
|
{
|
|
ClutterSeatEvdev *seat = data;
|
|
GSource *source;
|
|
guint32 time_ms;
|
|
|
|
g_return_val_if_fail (seat->repeat_device != NULL, G_SOURCE_REMOVE);
|
|
source = g_main_context_find_source_by_id (NULL, seat->repeat_timer);
|
|
time_ms = g_source_get_time (source) / 1000;
|
|
|
|
clutter_seat_evdev_notify_key (seat,
|
|
seat->repeat_device,
|
|
ms2us (time_ms),
|
|
seat->repeat_key,
|
|
AUTOREPEAT_VALUE,
|
|
FALSE);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
queue_event (ClutterEvent *event)
|
|
{
|
|
_clutter_event_push (event, FALSE);
|
|
}
|
|
|
|
static int
|
|
update_button_count (ClutterSeatEvdev *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)
|
|
return 0;
|
|
|
|
return --seat->button_count[button];
|
|
}
|
|
}
|
|
|
|
void
|
|
clutter_seat_evdev_notify_key (ClutterSeatEvdev *seat,
|
|
ClutterInputDevice *device,
|
|
uint64_t time_us,
|
|
uint32_t key,
|
|
uint32_t state,
|
|
gboolean update_keys)
|
|
{
|
|
ClutterStage *stage;
|
|
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)
|
|
return;
|
|
if (!state && count != 0)
|
|
return;
|
|
}
|
|
|
|
/* We can drop the event on the floor if no stage has been
|
|
* associated with the device yet. */
|
|
stage = _clutter_input_device_get_stage (device);
|
|
if (stage == NULL)
|
|
{
|
|
clutter_seat_evdev_clear_repeat_timer (seat);
|
|
return;
|
|
}
|
|
|
|
event = _clutter_key_event_new_from_evdev (device,
|
|
seat->core_keyboard,
|
|
stage,
|
|
seat->xkb,
|
|
seat->button_state,
|
|
us2ms (time_us), key, state);
|
|
_clutter_evdev_event_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_SYNTHETIC);
|
|
}
|
|
|
|
queue_event (event);
|
|
|
|
if (update_keys && (changed_state & XKB_STATE_LEDS))
|
|
clutter_seat_evdev_sync_leds (seat);
|
|
|
|
if (state == 0 || /* key release */
|
|
!seat->repeat ||
|
|
!xkb_keymap_key_repeats (xkb_state_get_keymap (seat->xkb),
|
|
event->key.hardware_keycode))
|
|
{
|
|
clutter_seat_evdev_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:
|
|
{
|
|
guint32 interval;
|
|
|
|
clutter_seat_evdev_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 (ClutterSeatEvdev *seat,
|
|
ClutterInputDevice *input_device,
|
|
guint64 time_us,
|
|
gfloat x,
|
|
gfloat y,
|
|
gdouble *axes)
|
|
{
|
|
ClutterStage *stage = _clutter_input_device_get_stage (input_device);
|
|
ClutterEvent *event;
|
|
|
|
event = clutter_event_new (CLUTTER_MOTION);
|
|
|
|
if (clutter_input_device_get_device_type (input_device) != CLUTTER_TABLET_DEVICE)
|
|
_clutter_device_manager_evdev_constrain_pointer (seat->manager_evdev,
|
|
seat->core_pointer,
|
|
time_us,
|
|
seat->pointer_x,
|
|
seat->pointer_y,
|
|
&x, &y);
|
|
|
|
_clutter_evdev_event_set_time_usec (event, time_us);
|
|
event->motion.time = us2ms (time_us);
|
|
event->motion.stage = stage;
|
|
event->motion.device = seat->core_pointer;
|
|
_clutter_xkb_translate_state (event, seat->xkb, seat->button_state);
|
|
event->motion.x = x;
|
|
event->motion.y = 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)
|
|
{
|
|
ClutterInputDeviceEvdev *device_evdev =
|
|
CLUTTER_INPUT_DEVICE_EVDEV (input_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);
|
|
}
|
|
|
|
_clutter_input_device_set_stage (seat->core_pointer, stage);
|
|
|
|
if (clutter_input_device_get_device_type (input_device) != CLUTTER_TABLET_DEVICE)
|
|
{
|
|
seat->pointer_x = x;
|
|
seat->pointer_y = y;
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
void
|
|
clutter_seat_evdev_notify_relative_motion (ClutterSeatEvdev *seat,
|
|
ClutterInputDevice *input_device,
|
|
uint64_t time_us,
|
|
float dx,
|
|
float dy,
|
|
float dx_unaccel,
|
|
float dy_unaccel)
|
|
{
|
|
gfloat new_x, new_y;
|
|
ClutterEvent *event;
|
|
|
|
/* We can drop the event on the floor if no stage has been
|
|
* associated with the device yet. */
|
|
if (!_clutter_input_device_get_stage (input_device))
|
|
return;
|
|
|
|
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);
|
|
|
|
_clutter_evdev_event_set_relative_motion (event,
|
|
dx, dy,
|
|
dx_unaccel, dy_unaccel);
|
|
|
|
queue_event (event);
|
|
}
|
|
|
|
void clutter_seat_evdev_notify_absolute_motion (ClutterSeatEvdev *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, x, axes);
|
|
|
|
queue_event (event);
|
|
}
|
|
|
|
void
|
|
clutter_seat_evdev_notify_button (ClutterSeatEvdev *seat,
|
|
ClutterInputDevice *input_device,
|
|
uint64_t time_us,
|
|
uint32_t button,
|
|
uint32_t state)
|
|
{
|
|
ClutterStage *stage;
|
|
ClutterEvent *event = NULL;
|
|
gint button_nr;
|
|
static gint 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)
|
|
return;
|
|
if (!state && button_count != 0)
|
|
return;
|
|
|
|
/* We can drop the event on the floor if no stage has been
|
|
* associated with the device yet. */
|
|
stage = _clutter_input_device_get_stage (input_device);
|
|
if (stage == NULL)
|
|
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;
|
|
|
|
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];
|
|
}
|
|
|
|
_clutter_evdev_event_set_time_usec (event, time_us);
|
|
event->button.time = us2ms (time_us);
|
|
event->button.stage = CLUTTER_STAGE (stage);
|
|
_clutter_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)
|
|
{
|
|
ClutterPoint point;
|
|
|
|
clutter_input_device_get_coords (input_device, NULL, &point);
|
|
event->button.x = point.x;
|
|
event->button.y = point.y;
|
|
}
|
|
else
|
|
{
|
|
event->button.x = seat->pointer_x;
|
|
event->button.y = seat->pointer_y;
|
|
}
|
|
|
|
clutter_event_set_device (event, seat->core_pointer);
|
|
clutter_event_set_source_device (event, input_device);
|
|
|
|
_clutter_evdev_event_set_event_code (event, button);
|
|
|
|
if (clutter_input_device_get_device_type (input_device) == CLUTTER_TABLET_DEVICE)
|
|
{
|
|
ClutterInputDeviceEvdev *device_evdev =
|
|
CLUTTER_INPUT_DEVICE_EVDEV (input_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);
|
|
}
|
|
|
|
_clutter_input_device_set_stage (seat->core_pointer, stage);
|
|
|
|
queue_event (event);
|
|
}
|
|
|
|
void
|
|
clutter_seat_evdev_free (ClutterSeatEvdev *seat)
|
|
{
|
|
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);
|
|
g_hash_table_unref (seat->touches);
|
|
|
|
xkb_state_unref (seat->xkb);
|
|
|
|
clutter_seat_evdev_clear_repeat_timer (seat);
|
|
|
|
if (seat->libinput_seat)
|
|
libinput_seat_unref (seat->libinput_seat);
|
|
|
|
g_free (seat);
|
|
}
|
|
|
|
void
|
|
clutter_seat_evdev_set_stage (ClutterSeatEvdev *seat,
|
|
ClutterStage *stage)
|
|
{
|
|
GSList *l;
|
|
|
|
_clutter_input_device_set_stage (seat->core_pointer, stage);
|
|
_clutter_input_device_set_stage (seat->core_keyboard, stage);
|
|
|
|
for (l = seat->devices; l; l = l->next)
|
|
{
|
|
ClutterInputDevice *device = l->data;
|
|
|
|
_clutter_input_device_set_stage (device, stage);
|
|
}
|
|
}
|