diff options
Diffstat (limited to '')
-rw-r--r-- | modules/controller-linux-input.c | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/modules/controller-linux-input.c b/modules/controller-linux-input.c new file mode 100644 index 0000000..760ccd8 --- /dev/null +++ b/modules/controller-linux-input.c @@ -0,0 +1,711 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * controller_linux_input.c + * Copyright (C) 2004-2007 Sven Neumann <sven@gimp.org> + * Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <linux/input.h> + +#include <glib/gstdio.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpmodule/gimpmodule.h" +#include "libgimpwidgets/gimpwidgets.h" + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "libgimpwidgets/gimpcontroller.h" + +#include "gimpinputdevicestore.h" + +#include "libgimp/libgimp-intl.h" + + +typedef struct +{ + guint16 code; + const gchar *name; + const gchar *blurb; +} LinuxInputEvent; + +static const LinuxInputEvent key_events[] = +{ + { BTN_0, "button-0", N_("Button 0") }, + { BTN_1, "button-1", N_("Button 1") }, + { BTN_2, "button-2", N_("Button 2") }, + { BTN_3, "button-3", N_("Button 3") }, + { BTN_4, "button-4", N_("Button 4") }, + { BTN_5, "button-5", N_("Button 5") }, + { BTN_6, "button-6", N_("Button 6") }, + { BTN_7, "button-7", N_("Button 7") }, + { BTN_8, "button-8", N_("Button 8") }, + { BTN_9, "button-9", N_("Button 9") }, + { BTN_MOUSE, "button-mouse", N_("Button Mouse") }, + { BTN_LEFT, "button-left", N_("Button Left") }, + { BTN_RIGHT, "button-right", N_("Button Right") }, + { BTN_MIDDLE, "button-middle", N_("Button Middle") }, + { BTN_SIDE, "button-side", N_("Button Side") }, + { BTN_EXTRA, "button-extra", N_("Button Extra") }, + { BTN_FORWARD, "button-forward", N_("Button Forward") }, + { BTN_BACK, "button-back", N_("Button Back") }, + { BTN_TASK, "button-task", N_("Button Task") }, +#ifdef BTN_WHEEL + { BTN_WHEEL, "button-wheel", N_("Button Wheel") }, +#endif +#ifdef BTN_GEAR_DOWN + { BTN_GEAR_DOWN, "button-gear-down", N_("Button Gear Down") }, +#endif +#ifdef BTN_GEAR_UP + { BTN_GEAR_UP, "button-gear-up", N_("Button Gear Up") } +#endif +}; + +static const LinuxInputEvent rel_events[] = +{ + { REL_X, "x-move-left", N_("X Move Left") }, + { REL_X, "x-move-right", N_("X Move Right") }, + { REL_Y, "y-move-forward", N_("Y Move Forward") }, + { REL_Y, "y-move-back", N_("Y Move Back") }, + { REL_Z, "z-move-up", N_("Z Move Up") }, + { REL_Z, "z-move-down", N_("Z Move Down") }, +#ifdef REL_RX + { REL_RX, "x-axis-tilt-forward", N_("X Axis Tilt Forward") }, + { REL_RX, "x-axis-tilt-back", N_("X Axis Tilt Back") }, + { REL_RY, "y-axis-tilt-right", N_("Y Axis Tilt Right") }, + { REL_RY, "y-axis-tilt-left", N_("Y Axis Tilt Left") }, + { REL_RZ, "z-axis-turn-left", N_("Z Axis Turn Left") }, + { REL_RZ, "z-axis-turn-right", N_("Z Axis Turn Right") }, +#endif /* REL_RX */ + { REL_HWHEEL, "horizontal-wheel-turn-back", N_("Horiz. Wheel Turn Back") }, + { REL_HWHEEL, "horizontal-wheel-turn-forward", N_("Horiz. Wheel Turn Forward") }, + { REL_DIAL, "dial-turn-left", N_("Dial Turn Left") }, + { REL_DIAL, "dial-turn-right", N_("Dial Turn Right") }, + { REL_WHEEL, "wheel-turn-left", N_("Wheel Turn Left") }, + { REL_WHEEL, "wheel-turn-right", N_("Wheel Turn Right") }, +}; + + +enum +{ + PROP_0, + PROP_DEVICE, + PROP_DEVICE_STORE +}; + + +#define CONTROLLER_TYPE_LINUX_INPUT (controller_linux_input_get_type ()) +#define CONTROLLER_LINUX_INPUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CONTROLLER_TYPE_LINUX_INPUT, ControllerLinuxInput)) +#define CONTROLLER_LINUX_INPUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CONTROLLER_TYPE_LINUX_INPUT, ControllerLinuxInputClass)) +#define CONTROLLER_IS_LINUX_INPUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CONTROLLER_TYPE_LINUX_INPUT)) +#define CONTROLLER_IS_LINUX_INPUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CONTROLLER_TYPE_LINUX_INPUT)) + + +typedef struct _ControllerLinuxInput ControllerLinuxInput; +typedef struct _ControllerLinuxInputClass ControllerLinuxInputClass; + +struct _ControllerLinuxInput +{ + GimpController parent_instance; + + GimpInputDeviceStore *store; + gchar *device; + GIOChannel *io; + guint io_id; +}; + +struct _ControllerLinuxInputClass +{ + GimpControllerClass parent_class; +}; + + +static GType controller_linux_input_get_type (void); + +static void linux_input_dispose (GObject *object); +static void linux_input_finalize (GObject *object); +static void linux_input_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void linux_input_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint linux_input_get_n_events (GimpController *controller); +static const gchar * linux_input_get_event_name (GimpController *controller, + gint event_id); +static const gchar * linux_input_get_event_blurb (GimpController *controller, + gint event_id); + +static void linux_input_device_changed (ControllerLinuxInput *controller, + const gchar *udi); +static gboolean linux_input_set_device (ControllerLinuxInput *controller, + const gchar *device); +static gboolean linux_input_read_event (GIOChannel *io, + GIOCondition cond, + gpointer data); + + +static const GimpModuleInfo linux_input_info = +{ + GIMP_MODULE_ABI_VERSION, + N_("Linux input event controller"), + "Sven Neumann <sven@gimp.org>, Michael Natterer <mitch@gimp.org>", + "v0.2", + "(c) 2004-2007, released under the GPL", + "2004-2007" +}; + + +G_DEFINE_DYNAMIC_TYPE (ControllerLinuxInput, controller_linux_input, + GIMP_TYPE_CONTROLLER) + + +G_MODULE_EXPORT const GimpModuleInfo * +gimp_module_query (GTypeModule *module) +{ + return &linux_input_info; +} + +G_MODULE_EXPORT gboolean +gimp_module_register (GTypeModule *module) +{ + gimp_input_device_store_register_types (module); + controller_linux_input_register_type (module); + + return TRUE; +} + +static void +controller_linux_input_class_init (ControllerLinuxInputClass *klass) +{ + GimpControllerClass *controller_class = GIMP_CONTROLLER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = linux_input_dispose; + object_class->finalize = linux_input_finalize; + object_class->get_property = linux_input_get_property; + object_class->set_property = linux_input_set_property; + + g_object_class_install_property (object_class, PROP_DEVICE, + g_param_spec_string ("device", + _("Device:"), + _("The name of the device to read Linux Input events from."), + NULL, + GIMP_CONFIG_PARAM_FLAGS)); +#ifdef HAVE_LIBGUDEV + g_object_class_install_property (object_class, PROP_DEVICE_STORE, + g_param_spec_object ("device-values", + NULL, NULL, + GIMP_TYPE_INPUT_DEVICE_STORE, + G_PARAM_READABLE)); +#endif + + controller_class->name = _("Linux Input"); + controller_class->help_id = "gimp-controller-linux-input"; + controller_class->icon_name = GIMP_ICON_CONTROLLER_LINUX_INPUT; + + controller_class->get_n_events = linux_input_get_n_events; + controller_class->get_event_name = linux_input_get_event_name; + controller_class->get_event_blurb = linux_input_get_event_blurb; +} + +static void +controller_linux_input_class_finalize (ControllerLinuxInputClass *klass) +{ +} + +static void +controller_linux_input_init (ControllerLinuxInput *controller) +{ + controller->store = gimp_input_device_store_new (); + + if (controller->store) + { + g_signal_connect_swapped (controller->store, "device-added", + G_CALLBACK (linux_input_device_changed), + controller); + g_signal_connect_swapped (controller->store, "device-removed", + G_CALLBACK (linux_input_device_changed), + controller); + } +} + +static void +linux_input_dispose (GObject *object) +{ + ControllerLinuxInput *controller = CONTROLLER_LINUX_INPUT (object); + + linux_input_set_device (controller, NULL); + + G_OBJECT_CLASS (controller_linux_input_parent_class)->dispose (object); +} + +static void +linux_input_finalize (GObject *object) +{ + ControllerLinuxInput *controller = CONTROLLER_LINUX_INPUT (object); + + if (controller->store) + { + g_object_unref (controller->store); + controller->store = NULL; + } + + G_OBJECT_CLASS (controller_linux_input_parent_class)->finalize (object); +} + +static void +linux_input_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + ControllerLinuxInput *controller = CONTROLLER_LINUX_INPUT (object); + + switch (property_id) + { + case PROP_DEVICE: + linux_input_set_device (controller, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +linux_input_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ControllerLinuxInput *controller = CONTROLLER_LINUX_INPUT (object); + + switch (property_id) + { + case PROP_DEVICE: + g_value_set_string (value, controller->device); + break; + case PROP_DEVICE_STORE: + g_value_set_object (value, controller->store); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint +linux_input_get_n_events (GimpController *controller) +{ + return G_N_ELEMENTS (key_events) + G_N_ELEMENTS (rel_events); +} + +static const gchar * +linux_input_get_event_name (GimpController *controller, + gint event_id) +{ + if (event_id < 0) + { + return NULL; + } + else if (event_id < G_N_ELEMENTS (key_events)) + { + return key_events[event_id].name; + } + else if (event_id < linux_input_get_n_events (controller)) + { + return rel_events[event_id - G_N_ELEMENTS (key_events)].name; + } + else + { + return NULL; + } +} + +static const gchar * +linux_input_get_event_blurb (GimpController *controller, + gint event_id) +{ + if (event_id < 0) + { + return NULL; + } + else if (event_id < G_N_ELEMENTS (key_events)) + { + return gettext (key_events[event_id].blurb); + } + else if (event_id < linux_input_get_n_events (controller)) + { + return gettext (rel_events[event_id - G_N_ELEMENTS (key_events)].blurb); + } + else + { + return NULL; + } +} + +#define BITS_PER_LONG (sizeof(long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define BIT(x) (1UL<<OFF(x)) +#define LONG(x) ((x)/BITS_PER_LONG) +#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) + +static void +linux_input_get_device_info (ControllerLinuxInput *controller, + int fd) +{ + unsigned long evbit[NBITS (EV_MAX)]; + unsigned long keybit[NBITS (KEY_MAX)]; + unsigned long relbit[NBITS (REL_MAX)]; + unsigned long absbit[NBITS (ABS_MAX)]; + + gint num_keys = 0; + gint num_ext_keys = 0; + gint num_buttons = 0; + gint num_rels = 0; + gint num_abs = 0; + + /* get event type bits */ + ioctl (fd, EVIOCGBIT (0, EV_MAX), evbit); + + if (test_bit (EV_KEY, evbit)) + { + gint i; + + /* get keyboard bits */ + ioctl (fd, EVIOCGBIT (EV_KEY, KEY_MAX), keybit); + + /** count typical keyboard keys only */ + for (i = KEY_Q; i < KEY_M; i++) + if (test_bit (i, keybit)) + { + num_keys++; + + g_print ("%s: key 0x%02x present\n", G_STRFUNC, i); + } + + g_print ("%s: #keys = %d\n", G_STRFUNC, num_keys); + + for (i = KEY_OK; i < KEY_MAX; i++) + if (test_bit (i, keybit)) + { + num_ext_keys++; + + g_print ("%s: ext key 0x%02x present\n", G_STRFUNC, i); + } + + g_print ("%s: #ext_keys = %d\n", G_STRFUNC, num_ext_keys); + + for (i = BTN_MISC; i < KEY_OK; i++) + if (test_bit (i, keybit)) + { + num_buttons++; + + g_print ("%s: button 0x%02x present\n", G_STRFUNC, i); + } + + g_print ("%s: #buttons = %d\n", G_STRFUNC, num_buttons); + } + + if (test_bit (EV_REL, evbit)) + { + gint i; + + /* get bits for relative axes */ + ioctl (fd, EVIOCGBIT (EV_REL, REL_MAX), relbit); + + for (i = 0; i < REL_MAX; i++) + if (test_bit (i, relbit)) + { + num_rels++; + + g_print ("%s: rel 0x%02x present\n", G_STRFUNC, i); + } + + g_print ("%s: #rels = %d\n", G_STRFUNC, num_rels); + } + + if (test_bit (EV_ABS, evbit)) + { + gint i; + + /* get bits for absolute axes */ + ioctl (fd, EVIOCGBIT (EV_ABS, ABS_MAX), absbit); + + for (i = 0; i < ABS_MAX; i++) + if (test_bit (i, absbit)) + { + struct input_absinfo absinfo; + + num_abs++; + + /* get info for the absolute axis */ + ioctl (fd, EVIOCGABS (i), &absinfo); + + g_print ("%s: abs 0x%02x present [%d..%d]\n", G_STRFUNC, i, + absinfo.minimum, absinfo.maximum); + } + + g_print ("%s: #abs = %d\n", G_STRFUNC, num_abs); + } +} + +static void +linux_input_device_changed (ControllerLinuxInput *controller, + const gchar *identifier) +{ + if (controller->device && strcmp (identifier, controller->device) == 0) + { + linux_input_set_device (controller, identifier); + g_object_notify (G_OBJECT (controller), "device"); + } +} + +static gboolean +linux_input_set_device (ControllerLinuxInput *controller, + const gchar *device) +{ + gchar *filename; + + if (controller->io) + { + g_source_remove (controller->io_id); + controller->io_id = 0; + + g_io_channel_unref (controller->io); + controller->io = NULL; + } + + if (controller->device) + g_free (controller->device); + + controller->device = g_strdup (device); + + g_object_set (controller, "name", _("Linux Input Events"), NULL); + + if (controller->device && strlen (controller->device)) + { + if (controller->store) + filename = gimp_input_device_store_get_device_file (controller->store, + controller->device); + else + filename = g_strdup (controller->device); + } + else + { + g_object_set (controller, "state", _("No device configured"), NULL); + + return FALSE; + } + + if (filename) + { + gchar *state; + gint fd; + + fd = g_open (filename, O_RDONLY, 0); + + if (fd >= 0) + { + gchar name[256]; + + name[0] = '\0'; + if (ioctl (fd, EVIOCGNAME (sizeof (name)), name) >= 0 && + strlen (name) > 0 && + g_utf8_validate (name, -1, NULL)) + { + g_object_set (controller, "name", name, NULL); + } + + linux_input_get_device_info (controller, fd); + + state = g_strdup_printf (_("Reading from %s"), filename); + g_object_set (controller, "state", state, NULL); + g_free (state); + + g_free (filename); + + controller->io = g_io_channel_unix_new (fd); + g_io_channel_set_close_on_unref (controller->io, TRUE); + g_io_channel_set_encoding (controller->io, NULL, NULL); + + controller->io_id = g_io_add_watch (controller->io, + G_IO_IN, + linux_input_read_event, + controller); + return TRUE; + } + else + { + state = g_strdup_printf (_("Device not available: %s"), + g_strerror (errno)); + g_object_set (controller, "state", state, NULL); + g_free (state); + } + + g_free (filename); + } + else if (controller->store) + { + GError *error = gimp_input_device_store_get_error (controller->store); + + if (error) + { + g_object_set (controller, "state", error->message, NULL); + g_error_free (error); + } + else + { + g_object_set (controller, "state", _("Device not available"), NULL); + } + } + + return FALSE; +} + +static gboolean +linux_input_read_event (GIOChannel *io, + GIOCondition cond, + gpointer data) +{ + ControllerLinuxInput *input = CONTROLLER_LINUX_INPUT (data); + GIOStatus status; + GError *error = NULL; + struct input_event ev; + gsize n_bytes; + + status = g_io_channel_read_chars (io, + (gchar *) &ev, + sizeof (struct input_event), &n_bytes, + &error); + + switch (status) + { + case G_IO_STATUS_ERROR: + case G_IO_STATUS_EOF: + g_source_remove (input->io_id); + input->io_id = 0; + + g_io_channel_unref (input->io); + input->io = NULL; + + if (error) + { + gchar *state = g_strdup_printf (_("Device not available: %s"), + error->message); + g_object_set (input, "state", state, NULL); + g_free (state); + + g_clear_error (&error); + } + else + { + g_object_set (input, "state", _("End of file"), NULL); + } + return FALSE; + break; + + case G_IO_STATUS_AGAIN: + return TRUE; + + default: + break; + } + + if (n_bytes == sizeof (struct input_event)) + { + GimpController *controller = GIMP_CONTROLLER (data); + GimpControllerEvent cevent = { 0, }; + gint i; + + switch (ev.type) + { + case EV_KEY: + g_print ("%s: EV_KEY code = 0x%02x\n", G_STRFUNC, ev.code); + + for (i = 0; i < G_N_ELEMENTS (key_events); i++) + if (ev.code == key_events[i].code) + { + cevent.any.type = GIMP_CONTROLLER_EVENT_TRIGGER; + cevent.any.source = controller; + cevent.any.event_id = i; + + gimp_controller_event (controller, &cevent); + + break; + } + break; + + case EV_REL: + g_print ("%s: EV_REL code = 0x%02x (value = %d)\n", G_STRFUNC, + ev.code, ev.value); + + for (i = 0; i < G_N_ELEMENTS (rel_events); i++) + if (ev.code == rel_events[i].code) + { + cevent.any.type = GIMP_CONTROLLER_EVENT_VALUE; + cevent.any.source = controller; + cevent.any.event_id = G_N_ELEMENTS (key_events) + i; + + g_value_init (&cevent.value.value, G_TYPE_DOUBLE); + + if (ev.value < 0) + { + g_value_set_double (&cevent.value.value, -ev.value); + } + else + { + cevent.any.event_id++; + + g_value_set_double (&cevent.value.value, ev.value); + } + + gimp_controller_event (controller, &cevent); + + g_value_unset (&cevent.value.value); + + break; + } + break; + + case EV_ABS: + g_print ("%s: EV_ABS code = 0x%02x (value = %d)\n", G_STRFUNC, + ev.code, ev.value); + break; + + default: + break; + } + } + + return TRUE; +} |