/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Copyright (C) 2010 Intel Corp. * * 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 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "clutter-backend.h" #include "clutter-debug.h" #include "clutter-device-manager.h" #include "clutter-device-manager-private.h" #include "clutter-event-private.h" #include "clutter-input-device-evdev.h" #include "clutter-main.h" #include "clutter-private.h" #include "clutter-xkb-utils.h" #include "clutter-device-manager-evdev.h" #define CLUTTER_DEVICE_MANAGER_EVDEV_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ CLUTTER_TYPE_DEVICE_MANAGER_EVDEV, \ ClutterDeviceManagerEvdevPrivate)) G_DEFINE_TYPE (ClutterDeviceManagerEvdev, clutter_device_manager_evdev, CLUTTER_TYPE_DEVICE_MANAGER); struct _ClutterDeviceManagerEvdevPrivate { GUdevClient *udev_client; GSList *devices; /* list of ClutterInputDeviceEvdevs */ GSList *event_sources; /* list of the event sources */ ClutterInputDevice *core_pointer; ClutterInputDevice *core_keyboard; }; static const gchar *subsystems[] = { "input", NULL }; /* * ClutterEventSource management * * The device manager is responsible for managing the GSource when devices * appear and disappear from the system. * * FIXME: For now, we associate a GSource with every single device. Starting * from glib 2.28 we can use g_source_add_child_source() to have a single * GSource for the device manager, each device becoming a child source. Revisit * this once we depend on glib >= 2.28. */ const char *option_xkb_layout = "us"; const char *option_xkb_variant = ""; const char *option_xkb_options = ""; /* * ClutterEventSource for reading input devices */ typedef struct _ClutterEventSource ClutterEventSource; struct _ClutterEventSource { GSource source; ClutterInputDeviceEvdev *device; /* back pointer to the evdev device */ GPollFD event_poll_fd; /* file descriptor of the /dev node */ struct xkb_desc *xkb; /* compiled xkb keymap */ uint32_t modifier_state; /* remember the modifier state */ gint x, y; /* last x, y position for pointers */ }; static gboolean clutter_event_prepare (GSource *source, gint *timeout) { gboolean retval; clutter_threads_enter (); *timeout = -1; retval = clutter_events_pending (); clutter_threads_leave (); return retval; } static gboolean clutter_event_check (GSource *source) { ClutterEventSource *event_source = (ClutterEventSource *) source; gboolean retval; clutter_threads_enter (); retval = ((event_source->event_poll_fd.revents & G_IO_IN) || clutter_events_pending ()); clutter_threads_leave (); return retval; } static void queue_event (ClutterEvent *event) { if (event == NULL) return; _clutter_event_push (event, FALSE); } static void notify_key (ClutterEventSource *source, guint32 time_, guint32 key, guint32 state) { ClutterEvent *event = NULL; ClutterActor *stage; stage = clutter_stage_get_default (); /* if we have a mapping for that device, use it to generate the event */ if (source->xkb) event = _clutter_key_event_new_from_evdev ((ClutterInputDevice *) source->device, CLUTTER_STAGE (stage), source->xkb, time_, key, state, &source->modifier_state); queue_event (event); } static void notify_motion (ClutterEventSource *source, guint32 time_, gint x, gint y) { gfloat stage_width, stage_height, new_x, new_y; ClutterEvent *event; ClutterActor *stage; stage = clutter_stage_get_default (); stage_width = clutter_actor_get_width (stage); stage_height = clutter_actor_get_height (stage); event = clutter_event_new (CLUTTER_MOTION); if (x < 0) new_x = 0.f; else if (x >= stage_width) new_x = stage_width - 1; else new_x = x; if (y < 0) new_y = 0.f; else if (y >= stage_height) new_y = stage_height - 1; else new_y = y; source->x = new_x; source->y = new_y; event->motion.time = time_; event->motion.stage = CLUTTER_STAGE (stage); event->motion.device = (ClutterInputDevice *) source->device; event->motion.modifier_state = source->modifier_state; event->motion.x = new_x; event->motion.y = new_y; queue_event (event); } static void notify_button (ClutterEventSource *source, guint32 time_, guint32 button, guint32 state) { ClutterEvent *event; ClutterActor *stage; gint button_nr; static gint maskmap[8] = { CLUTTER_BUTTON1_MASK, CLUTTER_BUTTON2_MASK, CLUTTER_BUTTON3_MASK, CLUTTER_BUTTON4_MASK, CLUTTER_BUTTON5_MASK, 0, 0, 0 }; stage = clutter_stage_get_default (); button_nr = button - BTN_LEFT + 1; if (G_UNLIKELY (button_nr < 1 || button_nr > 8)) { 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); /* Update the modfiers */ if (state) source->modifier_state |= maskmap[button - BTN_LEFT]; else source->modifier_state &= ~maskmap[button - BTN_LEFT]; event->button.time = time_; event->button.stage = CLUTTER_STAGE (stage); event->button.device = (ClutterInputDevice *) source->device; event->button.modifier_state = source->modifier_state; event->button.button = button_nr; event->button.x = source->x; event->button.y = source->y; queue_event (event); } static gboolean clutter_event_dispatch (GSource *g_source, GSourceFunc callback, gpointer user_data) { ClutterEventSource *source = (ClutterEventSource *) g_source; struct input_event ev[8]; ClutterEvent *event; gint len, i, dx = 0, dy = 0; uint32_t _time; clutter_threads_enter (); /* Don't queue more events if we haven't finished handling the previous batch */ if (!clutter_events_pending ()) { len = read (source->event_poll_fd.fd, &ev, sizeof (ev)); if (len < 0 || len % sizeof (ev[0]) != 0) { if (errno != EAGAIN) { ClutterDeviceManager *manager; ClutterInputDevice *device; const gchar *device_path; device = CLUTTER_INPUT_DEVICE (source->device); if (CLUTTER_HAS_DEBUG (EVENT)) { device_path = _clutter_input_device_evdev_get_device_path (source->device); CLUTTER_NOTE (EVENT, "Could not read device (%s), removing.", device_path); } /* remove the faulty device */ manager = clutter_device_manager_get_default (); _clutter_device_manager_remove_device (manager, device); } goto out; } for (i = 0; i < len / sizeof (ev[0]); i++) { struct input_event *e = &ev[i]; _time = e->time.tv_sec * 1000 + e->time.tv_usec / 1000; event = NULL; switch (e->type) { case EV_KEY: /* don't repeat mouse buttons */ if (e->code >= BTN_MOUSE && e->code < KEY_OK) if (e->value == 2) continue; switch (e->code) { case BTN_TOUCH: case BTN_TOOL_PEN: case BTN_TOOL_RUBBER: case BTN_TOOL_BRUSH: case BTN_TOOL_PENCIL: case BTN_TOOL_AIRBRUSH: case BTN_TOOL_FINGER: case BTN_TOOL_MOUSE: case BTN_TOOL_LENS: break; case BTN_LEFT: case BTN_RIGHT: case BTN_MIDDLE: case BTN_SIDE: case BTN_EXTRA: case BTN_FORWARD: case BTN_BACK: case BTN_TASK: notify_button(source, _time, e->code, e->value); break; default: notify_key (source, _time, e->code, e->value); break; } break; case EV_SYN: /* Nothing to do here? */ break; case EV_MSC: /* Nothing to do here? */ break; case EV_REL: /* compress the EV_REL events in dx/dy */ switch (e->code) { case REL_X: dx += e->value; break; case REL_Y: dy += e->value; break; } break; case EV_ABS: default: g_warning ("Unhandled event of type %d", e->type); break; } queue_event (event); } if (dx != 0 || dy != 0) notify_motion (source, _time, source->x + dx, source->y + dy); } /* Pop an event off the queue if any */ event = clutter_event_get (); if (event) { /* forward the event into clutter for emission etc. */ clutter_do_event (event); clutter_event_free (event); } out: clutter_threads_leave (); return TRUE; } static GSourceFuncs event_funcs = { clutter_event_prepare, clutter_event_check, clutter_event_dispatch, NULL }; static GSource * clutter_event_source_new (ClutterInputDeviceEvdev *input_device) { GSource *source = g_source_new (&event_funcs, sizeof (ClutterEventSource)); ClutterEventSource *event_source = (ClutterEventSource *) source; ClutterInputDeviceType type; const gchar *node_path; gint fd; /* grab the udev input device node and open it */ node_path = _clutter_input_device_evdev_get_device_path (input_device); CLUTTER_NOTE (EVENT, "Creating GSource for device %s", node_path); fd = open (node_path, O_RDONLY | O_NONBLOCK); if (fd < 0) { g_warning ("Could not open device %s: %s", node_path, strerror (errno)); return NULL; } /* setup the source */ event_source->device = input_device; event_source->event_poll_fd.fd = fd; event_source->event_poll_fd.events = G_IO_IN; type = clutter_input_device_get_device_type (CLUTTER_INPUT_DEVICE (input_device)); if (type == CLUTTER_KEYBOARD_DEVICE) { /* create the xkb description */ event_source->xkb = _clutter_xkb_desc_new (NULL, option_xkb_layout, option_xkb_variant, option_xkb_options); if (G_UNLIKELY (event_source->xkb == NULL)) { g_warning ("Could not compile keymap %s:%s:%s", option_xkb_layout, option_xkb_variant, option_xkb_options); close (fd); g_source_unref (source); return NULL; } } else if (type == CLUTTER_POINTER_DEVICE) { /* initialize the pointer position to the center of the default stage */ ClutterActor *stage; gfloat stage_width, stage_height; stage = clutter_stage_get_default (); stage_width = clutter_actor_get_width (stage); stage_height = clutter_actor_get_height (stage); event_source->x = (gint) stage_width / 2; event_source->y = (gint) stage_height / 2; } /* 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 source; } static void clutter_event_source_free (ClutterEventSource *source) { GSource *g_source = (GSource *) source; const gchar *node_path; node_path = _clutter_input_device_evdev_get_device_path (source->device); CLUTTER_NOTE (EVENT, "Removing GSource for device %s", node_path); /* 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 ClutterEventSource * find_source_by_device (ClutterDeviceManagerEvdev *manager, ClutterInputDevice *device) { ClutterDeviceManagerEvdevPrivate *priv = manager->priv; GSList *l; for (l = priv->event_sources; l; l = g_slist_next (l)) { ClutterEventSource *source = l->data; if (source->device == (ClutterInputDeviceEvdev *) device) return source; } return NULL; } static gboolean is_evdev (const gchar *sysfs_path) { GRegex *regex; gboolean match; regex = g_regex_new ("/input[0-9]+/event[0-9]+$", 0, 0, NULL); match = g_regex_match (regex, sysfs_path, 0, NULL); g_regex_unref (regex); return match; } static void evdev_add_device (ClutterDeviceManagerEvdev *manager_evdev, GUdevDevice *udev_device) { ClutterDeviceManager *manager = (ClutterDeviceManager *) manager_evdev; ClutterInputDeviceType type = CLUTTER_EXTENSION_DEVICE; ClutterInputDevice *device; ClutterActor *stage; const gchar *device_file, *sysfs_path, *device_name; device_file = g_udev_device_get_device_file (udev_device); sysfs_path = g_udev_device_get_sysfs_path (udev_device); device_name = g_udev_device_get_name (udev_device); if (device_file == NULL || sysfs_path == NULL) return; if (g_udev_device_get_property (udev_device, "ID_INPUT") == NULL) return; /* Make sure to only add evdev devices, ie the device with a sysfs path that * finishes by input%d/event%d (We don't rely on the node name as this * policy is enforced by udev rules Vs API/ABI guarantees of sysfs) */ if (!is_evdev (sysfs_path)) return; /* Clutter assumes that device types are exclusive in the * ClutterInputDevice API */ if (g_udev_device_has_property (udev_device, "ID_INPUT_KEYBOARD")) type = CLUTTER_KEYBOARD_DEVICE; else if (g_udev_device_has_property (udev_device, "ID_INPUT_MOUSE")) type = CLUTTER_POINTER_DEVICE; else if (g_udev_device_has_property (udev_device, "ID_INPUT_JOYSTICK")) type = CLUTTER_JOYSTICK_DEVICE; else if (g_udev_device_has_property (udev_device, "ID_INPUT_TABLET")) type = CLUTTER_TABLET_DEVICE; else if (g_udev_device_has_property (udev_device, "ID_INPUT_TOUCHPAD")) type = CLUTTER_TOUCHPAD_DEVICE; else if (g_udev_device_has_property (udev_device, "ID_INPUT_TOUCHSCREEN")) type = CLUTTER_TOUCHSCREEN_DEVICE; device = g_object_new (CLUTTER_TYPE_INPUT_DEVICE_EVDEV, "id", 0, "name", device_name, "device-type", type, "sysfs-path", sysfs_path, "device-path", device_file, "enabled", TRUE, NULL); /* Always associate the device to the default stage */ stage = clutter_stage_get_default (); _clutter_input_device_set_stage (device, CLUTTER_STAGE (stage)); _clutter_device_manager_add_device (manager, device); CLUTTER_NOTE (EVENT, "Added device %s, type %d, sysfs %s", device_file, type, sysfs_path); } static ClutterInputDeviceEvdev * find_device_by_udev_device (ClutterDeviceManagerEvdev *manager_evdev, GUdevDevice *udev_device) { ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv; GSList *l; const gchar *sysfs_path; sysfs_path = g_udev_device_get_sysfs_path (udev_device); if (sysfs_path == NULL) { g_message ("device file is NULL"); return NULL; } for (l = priv->devices; l; l = g_slist_next (l)) { ClutterInputDeviceEvdev *device = l->data; if (strcmp (sysfs_path, _clutter_input_device_evdev_get_sysfs_path (device)) == 0) { return device; } } return NULL; } static void evdev_remove_device (ClutterDeviceManagerEvdev *manager_evdev, GUdevDevice *device) { ClutterDeviceManager *manager = CLUTTER_DEVICE_MANAGER (manager_evdev); ClutterInputDeviceEvdev *device_evdev; ClutterInputDevice *input_device; device_evdev = find_device_by_udev_device (manager_evdev, device); if (device_evdev == NULL) return; input_device = CLUTTER_INPUT_DEVICE (device_evdev); _clutter_device_manager_remove_device (manager, input_device); } static void on_uevent (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data) { ClutterDeviceManagerEvdev *manager = CLUTTER_DEVICE_MANAGER_EVDEV (data); if (g_strcmp0 (action, "add") == 0) evdev_add_device (manager, device); else if (g_strcmp0 (action, "remove") == 0) evdev_remove_device (manager, device); } /* * ClutterDeviceManager implementation */ static void clutter_device_manager_evdev_add_device (ClutterDeviceManager *manager, ClutterInputDevice *device) { ClutterDeviceManagerEvdev *manager_evdev; ClutterDeviceManagerEvdevPrivate *priv; ClutterInputDeviceType device_type; ClutterInputDeviceEvdev *device_evdev; gboolean is_pointer, is_keyboard; GSource *source; manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (manager); priv = manager_evdev->priv; device_evdev = CLUTTER_INPUT_DEVICE_EVDEV (device); device_type = clutter_input_device_get_device_type (device); is_pointer = device_type == CLUTTER_POINTER_DEVICE; is_keyboard = device_type == CLUTTER_KEYBOARD_DEVICE; priv->devices = g_slist_prepend (priv->devices, device); if (is_pointer && priv->core_pointer == NULL) priv->core_pointer = device; if (is_keyboard && priv->core_keyboard == NULL) priv->core_keyboard = device; /* Install the GSource for this device */ source = clutter_event_source_new (device_evdev); if (G_LIKELY (source)) priv->event_sources = g_slist_prepend (priv->event_sources, source); } static void clutter_device_manager_evdev_remove_device (ClutterDeviceManager *manager, ClutterInputDevice *device) { ClutterDeviceManagerEvdev *manager_evdev; ClutterDeviceManagerEvdevPrivate *priv; ClutterEventSource *source; manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (manager); priv = manager_evdev->priv; /* Remove the device */ priv->devices = g_slist_remove (priv->devices, device); /* Remove the source */ source = find_source_by_device (manager_evdev, device); if (G_UNLIKELY (source == NULL)) { g_warning ("Trying to remove a device without a source installed ?!"); return; } clutter_event_source_free (source); priv->event_sources = g_slist_remove (priv->event_sources, source); } static const GSList * clutter_device_manager_evdev_get_devices (ClutterDeviceManager *manager) { return CLUTTER_DEVICE_MANAGER_EVDEV (manager)->priv->devices; } static ClutterInputDevice * clutter_device_manager_evdev_get_core_device (ClutterDeviceManager *manager, ClutterInputDeviceType type) { ClutterDeviceManagerEvdev *manager_evdev; ClutterDeviceManagerEvdevPrivate *priv; manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (manager); priv = manager_evdev->priv; switch (type) { case CLUTTER_POINTER_DEVICE: return priv->core_pointer; case CLUTTER_KEYBOARD_DEVICE: return priv->core_keyboard; case CLUTTER_EXTENSION_DEVICE: default: return NULL; } return NULL; } static ClutterInputDevice * clutter_device_manager_evdev_get_device (ClutterDeviceManager *manager, gint id) { ClutterDeviceManagerEvdev *manager_evdev; ClutterDeviceManagerEvdevPrivate *priv; GSList *l; manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (manager); priv = manager_evdev->priv; for (l = priv->devices; l; l = l->next) { ClutterInputDevice *device = l->data; if (clutter_input_device_get_device_id (device) == id) return device; } return NULL; } /* * GObject implementation */ static void clutter_device_manager_evdev_constructed (GObject *gobject) { ClutterDeviceManagerEvdev *manager_evdev; ClutterDeviceManagerEvdevPrivate *priv; GList *devices, *l; manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (gobject); priv = manager_evdev->priv; priv->udev_client = g_udev_client_new (subsystems); devices = g_udev_client_query_by_subsystem (priv->udev_client, subsystems[0]); for (l = devices; l; l = g_list_next (l)) { GUdevDevice *device = l->data; evdev_add_device (manager_evdev, device); g_object_unref (device); } g_list_free (devices); /* subcribe for events on input devices */ g_signal_connect (priv->udev_client, "uevent", G_CALLBACK (on_uevent), manager_evdev); } static void clutter_device_manager_evdev_finalize (GObject *object) { ClutterDeviceManagerEvdev *manager_evdev; ClutterDeviceManagerEvdevPrivate *priv; GSList *l; manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (object); priv = manager_evdev->priv; g_object_unref (priv->udev_client); for (l = priv->devices; l; l = g_slist_next (l)) { ClutterInputDevice *device = l->data; g_object_unref (device); } g_slist_free (priv->devices); for (l = priv->event_sources; l; l = g_slist_next (l)) { ClutterEventSource *source = l->data; clutter_event_source_free (source); } g_slist_free (priv->event_sources); G_OBJECT_CLASS (clutter_device_manager_evdev_parent_class)->finalize (object); } static void clutter_device_manager_evdev_class_init (ClutterDeviceManagerEvdevClass *klass) { ClutterDeviceManagerClass *manager_class; GObjectClass *gobject_class = (GObjectClass *) klass; g_type_class_add_private (klass, sizeof (ClutterDeviceManagerEvdevPrivate)); gobject_class->constructed = clutter_device_manager_evdev_constructed; gobject_class->finalize = clutter_device_manager_evdev_finalize; manager_class = CLUTTER_DEVICE_MANAGER_CLASS (klass); manager_class->add_device = clutter_device_manager_evdev_add_device; manager_class->remove_device = clutter_device_manager_evdev_remove_device; manager_class->get_devices = clutter_device_manager_evdev_get_devices; manager_class->get_core_device = clutter_device_manager_evdev_get_core_device; manager_class->get_device = clutter_device_manager_evdev_get_device; } static void clutter_device_manager_evdev_init (ClutterDeviceManagerEvdev *self) { self->priv = CLUTTER_DEVICE_MANAGER_EVDEV_GET_PRIVATE (self); } /* * _clutter_events_evdev_init() and _clutter_events_evdev_uninit() are the two * symbol to use the evdev event backend from the EGL backend */ void _clutter_events_evdev_init (ClutterBackend *backend) { CLUTTER_NOTE (EVENT, "Initializing evdev backend"); /* We just have to create the singleon here */ clutter_device_manager_get_default (); } void _clutter_events_evdev_uninit (ClutterBackend *backend) { ClutterDeviceManager *manager; manager = clutter_device_manager_get_default (); g_object_unref (manager); }