evdev: First stab at an evdev backend

This backend is a event backend that can be enabled for EGL (for now).
It uses udev (gudev) to query input devices on a linux system, listens to
keyboard events from input devices and xkbcommon to translate raw key
codes into key keysyms.

This commit only supports key events, more to follow.
This commit is contained in:
Damien Lespiau
2010-11-04 10:38:32 -04:00
parent 9f5f62b4b5
commit c6493885c3
12 changed files with 1034 additions and 13 deletions

View File

@ -0,0 +1,318 @@
/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2006-2007 OpenedHand
*
* 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>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <linux/input.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <glib.h>
#include <gudev/gudev.h>
#include "clutter-backend.h"
#include "clutter-debug.h"
#include "clutter-device-manager.h"
#include "clutter-event.h"
#include "clutter-input-device-evdev.h"
#include "clutter-main.h"
#include "clutter-private.h"
#include "clutter-xkb-utils.h"
/* List of all the event sources */
static GSList *event_sources = NULL;
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 */
};
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 gboolean
clutter_event_dispatch (GSource *g_source,
GSourceFunc callback,
gpointer user_data)
{
ClutterEventSource *source = (ClutterEventSource *) g_source;
ClutterInputDevice *input_device = (ClutterInputDevice *) source->device;
ClutterMainContext *clutter_context;
struct input_event ev[8];
ClutterEvent *event = NULL;
ClutterStage *stage;
gint len, i;
clutter_threads_enter ();
clutter_context = _clutter_context_get_default ();
stage = CLUTTER_STAGE (clutter_stage_get_default ());
/* 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)
{
g_warning ("Could not read device (%d)", errno);
}
goto out;
}
for (i = 0; i < len / sizeof (ev[0]); i++)
{
struct input_event *e = &ev[i];
uint32_t _time;
_time = e->time.tv_sec * 1000 + e->time.tv_usec / 1000;
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;
event =
_clutter_key_event_new_from_evdev (input_device,
stage,
source->xkb,
_time, e->code, e->value,
&source->modifier_state);
break;
case EV_SYN:
/* Nothing to do here? */
break;
case EV_MSC:
/* Nothing to do here? */
break;
case EV_ABS:
case EV_REL:
default:
g_warning ("Unhandled event of type %d", e->type);
break;
}
if (event)
{
g_queue_push_head (clutter_context->events_queue, event);
event = NULL;
}
}
}
/* 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;
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;
/* 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;
}
/* 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 void
_clutter_event_evdev_add_source (ClutterInputDeviceEvdev *input_device)
{
GSource *source;
source = clutter_event_source_new (input_device);
if (G_LIKELY (source))
event_sources = g_slist_prepend (event_sources, source);
}
static void
_clutter_event_evdev_remove_source (ClutterEventSource *source)
{
clutter_event_source_free (source);
event_sources = g_slist_remove (event_sources, source);
}
void
_clutter_events_evdev_init (ClutterBackend *backend)
{
ClutterDeviceManager *device_manager;
GSList *devices, *l;
CLUTTER_NOTE (EVENT, "Initializing evdev backend");
/* Of course, we assume that the singleton will be a
* ClutterDeviceManagerEvdev */
device_manager = clutter_device_manager_get_default ();
devices = clutter_device_manager_list_devices (device_manager);
for (l = devices; l; l = g_slist_next (l))
{
ClutterInputDeviceEvdev *input_device = l->data;
_clutter_event_evdev_add_source (input_device);
}
}
void
_clutter_events_evdev_uninit (ClutterBackend *backend)
{
GSList *l;
for (l = event_sources; l; l = g_slist_next (l))
{
ClutterEventSource *source = l->data;
clutter_event_source_free (source);
}
g_slist_free (event_sources);
}