summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--meson.build91
-rw-r--r--meson_options.txt5
-rw-r--r--src/gwacom/wacom-device.c936
-rw-r--r--src/gwacom/wacom-device.h198
-rw-r--r--src/gwacom/wacom-driver.c149
-rw-r--r--src/gwacom/wacom-driver.h48
-rw-r--r--src/gwacom/wacom-private.h29
-rw-r--r--src/x11/xf86Wacom.c1
-rw-r--r--tools/wacom-record.c367
9 files changed, 1822 insertions, 2 deletions
diff --git a/meson.build b/meson.build
index 74b867e..ea0189c 100644
--- a/meson.build
+++ b/meson.build
@@ -98,10 +98,16 @@ config_ver_h = vcs_tag(command : ['git', 'describe'],
# config.h
config_h = configuration_data()
config_h.set('_GNU_SOURCE', 1)
+config_h.set_quoted('PACKAGE_VERSION', meson.project_version())
config_h.set('PACKAGE_VERSION_MAJOR', wacom_version[0])
config_h.set('PACKAGE_VERSION_MINOR', wacom_version[1])
config_h.set('PACKAGE_VERSION_PATCHLEVEL', wacom_version[2])
+# shortcut to avoid linking to _Xasprintf if xorg-server isn't included
+if cc.has_function('asprintf', prefix: '#define _GNU_SOURCE')
+ config_h.set10('HAVE_ASPRINTF', true)
+endif
+
if get_option('debug-messages')
config_h.set10('DEBUG', true)
endif
@@ -112,7 +118,7 @@ endif
# Driver
-src_wacom = [
+src_wacom_core = [
config_ver_h,
'src/WacomInterface.h',
'src/wcmCommon.c',
@@ -126,6 +132,9 @@ src_wacom = [
'src/xf86WacomDefs.h',
'src/xf86Wacom.h',
'src/wcmUSB.c',
+]
+
+src_wacom = src_wacom_core + [
'src/x11/xf86WacomProperties.c',
'src/x11/xf86Wacom.c',
]
@@ -162,6 +171,86 @@ configure_file(
install_dir: dir_pkgconf,
)
+# GWacom library
+# This is not a stable library interface
+
+dep_glib = dependency('glib-2.0', required: get_option('wacom-gobject'))
+dep_gobject = dependency('gobject-2.0', required: get_option('wacom-gobject'))
+dep_gio = dependency('gio-2.0', required: get_option('wacom-gobject'))
+dep_gio_unix = dependency('gio-unix-2.0', required: get_option('wacom-gobject'))
+dep_object_ir = dependency('gobject-introspection-1.0', required: get_option('wacom-gobject'))
+
+build_gwacom = dep_glib.found() and dep_gobject.found() and dep_gio.found() and dep_gio_unix.found() and dep_object_ir.found()
+
+if build_gwacom
+ gnome = import('gnome')
+
+ src_gwacom = [
+ 'src/gwacom/wacom-driver.h',
+ 'src/gwacom/wacom-driver.c',
+ 'src/gwacom/wacom-device.h',
+ 'src/gwacom/wacom-device.c',
+ ]
+
+ src_libgwacom = src_wacom_core + src_gwacom + [
+ 'src/wcmISDV4-stub.c',
+ ]
+
+ deps_gwacom = [
+ dep_xserver,
+ dep_m,
+ dep_glib,
+ dep_gobject,
+ dep_gio,
+ dep_gio_unix,
+ ]
+
+ lib_gwacom = both_libraries(
+ 'gwacom',
+ src_libgwacom + src_wacom_core,
+ include_directories: [dir_src, dir_include],
+ dependencies: deps_gwacom,
+ install: false,
+ c_args: ['-fvisibility=default'],
+ )
+
+ dep_gwacom = declare_dependency(
+ link_with: lib_gwacom,
+ dependencies: deps_gwacom,
+ include_directories: ['src/gwacom/'],
+ )
+
+ # GIR bindings are not installed, use
+ # export GI_TYPELIB_PATH="$PWD/builddir:$LD_LIBRARY_PATH"
+ # export LD_LIBRARY_PATH="$PWD/builddir:$LD_LIBRARY_PATH"
+ dep_gir = gnome.generate_gir(lib_gwacom,
+ sources: src_gwacom,
+ dependencies: [deps_gwacom],
+ namespace: 'wacom',
+ nsversion: '@0@.@1@'.format(wacom_version[0], wacom_version[1]),
+ identifier_prefix: 'Wacom',
+ symbol_prefix: 'wacom',
+ includes: 'GObject-2.0',
+ install: false,
+ )
+
+ # A custom target that forces the GIR bindings to be built. Usually
+ # those get built on install but we don't install ours.
+ custom_target('force GIR build',
+ build_by_default: true,
+ command: ['echo'],
+ output: 'force-gir-build.dummy',
+ capture: true,
+ depends: [dep_gir])
+
+ executable('wacom-record',
+ 'tools/wacom-record.c',
+ config_ver_h,
+ dependencies: [dep_libudev, dep_glib, dep_gwacom],
+ install: false,
+ )
+endif
+
# Tools
if get_option('serial-device-support')
src_shared = [
diff --git a/meson_options.txt b/meson_options.txt
index 5a503cc..c552b45 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -43,3 +43,8 @@ option('serial-device-support',
value: true,
description: 'Build with ISDV4 serial support'
)
+option('wacom-gobject',
+ type: 'feature',
+ value: 'auto',
+ description: 'Build the Wacom GObject library and associated tools [default: auto]'
+)
diff --git a/src/gwacom/wacom-device.c b/src/gwacom/wacom-device.c
new file mode 100644
index 0000000..2c61b99
--- /dev/null
+++ b/src/gwacom/wacom-device.c
@@ -0,0 +1,936 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* THIS IS NOT STABLE API, USE WITHIN THIS REPO ONLY */
+
+#include "config.h"
+#include "wacom-device.h"
+#include "wacom-private.h"
+#include <stdarg.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <gio/gio.h>
+
+
+#include "xf86Wacom.h"
+
+struct _WacomOptions {
+ GObject parent_instance;
+ GHashTable *ht;
+};
+
+struct _WacomDevice {
+ GObject parent_instance;
+ WacomDriver *driver;
+
+ guint id; /* incrementing ID */
+
+ WacomOptions *options;
+ int fd;
+ char *name;
+ char *path;
+
+ enum WacomToolType type;
+
+ gboolean enabled;
+
+ WacomDevicePtr priv;
+
+ int nbuttons;
+ int naxes;
+ int ntouches;
+ gboolean has_keys;
+ gboolean is_absolute;
+ gboolean is_direct_touch;
+
+ guint axis_mask;
+ WacomAxis axes[32]; /* ffs of the type is the axis index */
+
+ GIOChannel *channel;
+ guint watch;
+};
+
+G_DEFINE_TYPE (WacomOptions, wacom_options, G_TYPE_OBJECT)
+G_DEFINE_TYPE (WacomDevice, wacom_device, G_TYPE_OBJECT)
+G_DEFINE_BOXED_TYPE (WacomAxis, wacom_axis, wacom_axis_copy, wacom_axis_free)
+
+WacomOptions *wacom_options_new(const char *key, ...)
+{
+ g_autoptr(WacomOptions) opts = g_object_new(WACOM_TYPE_OPTIONS, NULL);
+ GHashTable *ht = opts->ht;
+ va_list args;
+ const char *k, *v;
+
+ va_start(args, key);
+ k = key;
+ while (k) {
+ v = va_arg(args, const char *);
+ g_return_val_if_fail(v != NULL, NULL);
+ g_hash_table_replace(ht, g_strdup(k), g_strdup(v));
+ k = va_arg(args, const char *);
+ }
+ va_end(args);
+
+ return g_steal_pointer(&opts);
+}
+
+static void copy_options(gpointer key, gpointer value, gpointer user_data)
+{
+ WacomOptions *dup = user_data;
+ wacom_options_set(dup, key, value);
+}
+
+WacomOptions *wacom_options_duplicate(WacomOptions *opts)
+{
+ WacomOptions *dup = wacom_options_new(NULL, NULL);
+ g_hash_table_foreach(opts->ht, copy_options, dup);
+ return dup;
+}
+
+const char *wacom_options_get(WacomOptions *opts, const char *key)
+{
+ g_return_val_if_fail(key != NULL, NULL);
+
+ g_autofree char *lower = g_ascii_strdown(key, -1);
+ return g_hash_table_lookup(opts->ht, lower);
+}
+
+void wacom_options_set(WacomOptions *opts, const char *key, const char *value)
+{
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(value != NULL);
+ g_hash_table_replace(opts->ht, g_ascii_strdown(key, -1), g_strdup(value));
+}
+
+static void
+wacom_options_finalize(GObject *gobject)
+{
+ WacomOptions *opts = WACOM_OPTIONS(gobject);
+ g_hash_table_destroy(opts->ht);
+ G_OBJECT_CLASS (wacom_options_parent_class)->finalize (gobject);
+}
+
+static void
+wacom_options_class_init(WacomOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = wacom_options_finalize;
+}
+
+static void
+wacom_options_init(WacomOptions *self)
+{
+ g_autoptr(GHashTable) ht = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+
+ /* Pretend this is a udev device */
+ g_hash_table_replace(ht, g_strdup("_source"), g_strdup("server/udev"));
+
+ self->ht = g_steal_pointer(&ht);
+}
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_ENABLED,
+ PROP_PATH,
+
+ N_PROPERTIES
+};
+
+static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
+
+enum {
+ SIGNAL_KEY,
+ SIGNAL_BUTTON,
+ SIGNAL_MOTION,
+ SIGNAL_TOUCH,
+ SIGNAL_PROXIMITY,
+
+ SIGNAL_LOGMSG, /* A log message from the driver */
+ SIGNAL_DBGMSG, /* A debug message from the driver */
+
+ SIGNAL_READ_ERROR,
+
+ LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+WacomDevice*
+wacom_device_new(WacomDriver *driver,
+ const char *name,
+ WacomOptions *options)
+{
+ static guint deviceid = 0;
+ WacomDevice *device = g_object_new(WACOM_TYPE_DEVICE, NULL);
+ WacomDevicePtr priv = wcmAllocate(device, name);
+
+ device->id = deviceid++;
+ device->driver = g_object_ref(driver);
+ device->priv = priv;
+ device->name = g_strdup(name);
+ device->options = wacom_options_duplicate(options);
+
+ wacom_driver_add_device(driver, device);
+
+ return device;
+}
+
+void
+wacom_device_remove(WacomDevice *device)
+{
+ wcmUnInit(device->priv);
+ wacom_driver_remove_device(device->driver, device);
+}
+
+gboolean
+wacom_device_preinit(WacomDevice *device)
+{
+ gboolean rc = wcmPreInit(device->priv) == Success;
+
+ device->type = (enum WacomToolType)device->priv->type;
+
+ return rc;
+}
+
+gboolean
+wacom_device_setup(WacomDevice *device)
+{
+ return wcmDevInit(device->priv);
+}
+
+static gboolean read_device(GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ WacomDevice *device = data;
+ int rc;
+
+ do {
+ rc = wcmReadPacket(device->priv);
+ } while (rc > 0);
+
+ return rc == 0;
+}
+
+gboolean
+wacom_device_enable(WacomDevice *device)
+{
+
+ g_return_val_if_fail(!device->enabled, true);
+
+ if (!wcmDevOpen(device->priv) || ! wcmDevStart(device->priv))
+ return false;
+
+ device->channel = g_io_channel_unix_new(device->fd);
+ device->watch = g_io_add_watch(device->channel, G_IO_IN,
+ read_device, device);
+
+ device->enabled = true;
+ g_object_notify_by_pspec(G_OBJECT(device), obj_properties[PROP_ENABLED]);
+
+ return true;
+}
+
+void
+wacom_device_disable(WacomDevice *device)
+{
+ g_return_if_fail(device->enabled);
+
+ wcmDevStop(device->priv);
+ wcmDevClose(device->priv);
+
+ device->enabled = false;
+ g_object_notify_by_pspec(G_OBJECT(device), obj_properties[PROP_ENABLED]);
+}
+
+WacomDriver *wacom_device_get_driver(WacomDevice *device)
+{
+ return device->driver;
+}
+void *wacom_device_get_impl(WacomDevice *device)
+{
+ return device->priv;
+}
+
+/****************** Driver layer *****************/
+
+int
+wcmGetFd(WacomDevicePtr priv)
+{
+ WacomDevice *device = priv->frontend;
+ return device->fd;
+}
+
+void
+wcmSetFd(WacomDevicePtr priv, int fd)
+{
+ WacomDevice *device = priv->frontend;
+ device->fd = fd;
+}
+
+void
+wcmSetName(WacomDevicePtr priv, const char *name)
+{
+ WacomDevice *device = priv->frontend;
+ g_free(device->name);
+ device->name = g_strdup(name);
+}
+
+__attribute__((__format__(__printf__ , 3, 0)))
+static void wcmLogDevice(WacomDevicePtr priv, WacomLogType type,
+ const char *format, va_list args)
+{
+ WacomDevice *device = priv->frontend;
+ const char *prefix = ".";
+ g_autofree char *str = NULL;
+
+ switch (type) {
+ case W_PROBED: prefix = "p"; break;
+ case W_CONFIG: prefix = "c"; break;
+ case W_DEFAULT: prefix = "d"; break;
+ case W_CMDLINE: prefix = "c"; break;
+ case W_NOTICE: prefix = "N"; break;
+ case W_ERROR: prefix = "E"; break;
+ case W_WARNING: prefix = "W"; break;
+ case W_INFO: prefix = "I"; break;
+ case W_NONE: prefix = "-"; break;
+ case W_NOT_IMPLEMENTED: prefix = "N/I"; break;
+ case W_DEBUG: prefix = "D"; break;
+ case W_UNKNOWN: prefix = "?"; break;
+ }
+
+ str = g_strdup_vprintf(format, args);
+ g_signal_emit(device, signals[SIGNAL_LOGMSG], 0, prefix, str);
+}
+
+void wcmLog(WacomDevicePtr priv, WacomLogType type, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ wcmLogDevice(priv, type, format, args);
+ va_end(args);
+}
+
+/* identical to wcmLog, we don't need sigsafe logging */
+void wcmLogSafe(WacomDevicePtr priv, WacomLogType type, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ wcmLogDevice(priv, type, format, args);
+ va_end(args);
+}
+
+void
+wcmLogDebugDevice(WacomDevicePtr priv, int debug_level, const char *func, const char *format, ...)
+{
+ WacomDevice *device = priv->frontend;
+ g_autofree char *str = NULL;
+ va_list args;
+
+ va_start(args, format);
+ str = g_strdup_vprintf(format, args);
+ va_end(args);
+
+ g_signal_emit(device, signals[SIGNAL_DBGMSG], 0, debug_level, func, str);
+}
+
+void wcmLogCommon(WacomCommonPtr common, WacomLogType type, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ /* We just log the common ones through the first device in the list,
+ * probably good enough */
+ wcmLogDevice(common->wcmDevices, type, format, args);
+ va_end(args);
+}
+
+/* identical to wcmLogCommon, we don't need sigsafe logging */
+void wcmLogCommonSafe(WacomCommonPtr common, WacomLogType type, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ /* We just log the common ones through the first device in the list,
+ * probably good enough */
+ wcmLogDevice(common->wcmDevices, type, format, args);
+ va_end(args);
+}
+
+void
+wcmLogDebugCommon(WacomCommonPtr common, int debug_level, const char *func, const char *format, ...)
+{
+ /* We just log the common ones through the first device in the list,
+ * probably good enough */
+ WacomDevice *device = common->wcmDevices->frontend;
+ g_autofree char *str = NULL;
+ va_list args;
+
+ va_start(args, format);
+ str = g_strdup_vprintf(format, args);
+ va_end(args);
+
+ g_signal_emit(device, signals[SIGNAL_DBGMSG], 0, debug_level, func, str);
+}
+
+char *wcmOptGetStr(WacomDevicePtr priv, const char *key, const char *default_value)
+{
+ WacomDevice *dev = priv->frontend;
+ WacomOptions *opts = dev->options;
+ const char *value = wacom_options_get(opts, key);
+
+ if (!value)
+ value = default_value;
+
+ return g_strdup(value);
+}
+
+int wcmOptGetInt(WacomDevicePtr priv, const char *key, int default_value)
+{
+ char buf[16];
+ snprintf(buf, sizeof(buf), "%d", default_value);
+ char *str = wcmOptGetStr(priv, key, buf);
+ int value = default_value;
+ if (str)
+ value = atoi(str);
+ g_free(str);
+ return value;
+}
+
+bool wcmOptGetBool(WacomDevicePtr priv, const char *key, bool default_value)
+{
+ char *str = wcmOptGetStr(priv, key, default_value ? "true" : "false");
+ bool is_true = false;
+
+ if (g_str_equal(str, "true") ||
+ g_str_equal(str, "yes") ||
+ g_str_equal(str, "on") ||
+ g_str_equal(str, "1"))
+ is_true = true;
+ g_free(str);
+ return is_true;
+}
+
+/* Get the option of the given type, quietly (without logging) */
+char *wcmOptCheckStr(WacomDevicePtr priv, const char *key, const char *default_value)
+{
+ return wcmOptGetStr(priv, key, default_value);
+}
+
+int wcmOptCheckInt(WacomDevicePtr priv, const char *key, int default_value)
+{
+ return wcmOptGetInt(priv, key, default_value);
+}
+
+bool wcmOptCheckBool(WacomDevicePtr priv, const char *key, bool default_value)
+{
+ return wcmOptGetBool(priv, key, default_value);
+}
+
+/* Change the option to the new value */
+void wcmOptSetStr(WacomDevicePtr priv, const char *key, const char *value)
+{
+ WacomDevice *device = priv->frontend;
+ WacomOptions *opts = device->options;
+
+ wacom_options_set(opts, key, value);
+}
+
+void wcmOptSetInt(WacomDevicePtr priv, const char *key, int value)
+{
+ char buf[64];
+ snprintf(buf, sizeof(buf), "%d", value);
+ wcmOptSetStr(priv, key, buf);
+}
+
+void wcmOptSetBool(WacomDevicePtr priv, const char *key, bool value)
+{
+ wcmOptSetStr(priv, key, value ? "true" : "false");
+}
+
+void wcmEmitKeycode(WacomDevicePtr priv, int keycode, int state)
+{
+ WacomDevice *device = priv->frontend;
+ g_signal_emit(device, signals[SIGNAL_KEY], 0, keycode, state);
+}
+
+void wcmEmitProximity(WacomDevicePtr priv, bool is_proximity_in,
+ const WacomAxisData *axes)
+{
+ WacomDevice *device = priv->frontend;
+ g_signal_emit(device, signals[SIGNAL_PROXIMITY], 0, is_proximity_in, axes);
+}
+
+void wcmEmitMotion(WacomDevicePtr priv, bool is_absolute, const WacomAxisData *axes)
+{
+ WacomDevice *device = priv->frontend;
+ g_signal_emit(device, signals[SIGNAL_MOTION], 0, is_absolute, axes);
+}
+
+void wcmEmitButton(WacomDevicePtr priv, bool is_absolute, int button, bool is_press, const WacomAxisData *axes)
+{
+ WacomDevice *device = priv->frontend;
+ g_signal_emit(device, signals[SIGNAL_BUTTON], 0, is_absolute, button, is_press, axes);
+}
+
+void wcmEmitTouch(WacomDevicePtr priv, int type, unsigned int touchid, int x, int y)
+{
+ WacomDevice *device = priv->frontend;
+ enum WacomTouchState state;
+
+ switch (type) {
+ case XI_TouchBegin: state = WACOM_TOUCH_BEGIN; break;
+ case XI_TouchUpdate: state = WACOM_TOUCH_UPDATE; break;
+ case XI_TouchEnd: state = WACOM_TOUCH_END; break;
+ default:
+ abort();
+ }
+ g_signal_emit(device, signals[SIGNAL_TOUCH], 0, state, touchid, x, y);
+}
+
+void wcmInitAxis(WacomDevicePtr priv, enum WacomAxisType type,
+ int min, int max, int res)
+{
+ WacomDevice *device = priv->frontend;
+ WacomAxis ax = {
+ .type = (enum WacomEventAxis)type,
+ .min = min,
+ .max = max,
+ .res = res,
+ };
+ device->axes[ffs(type)] = ax;
+ device->axis_mask |= (enum WacomEventAxis)type;
+}
+
+bool wcmInitButtons(WacomDevicePtr priv, unsigned int nbuttons)
+{
+ WacomDevice *device = priv->frontend;
+ device->nbuttons = nbuttons;
+ return true;
+}
+
+bool wcmInitKeyboard(WacomDevicePtr priv)
+{
+ WacomDevice *device = priv->frontend;
+ device->has_keys = true;
+ return true;
+}
+
+bool wcmInitPointer(WacomDevicePtr priv, int naxes, bool is_absolute)
+{
+ WacomDevice *device = priv->frontend;
+ device->naxes = naxes;
+ device->is_absolute = is_absolute;
+ return true;
+}
+
+bool wcmInitTouch(WacomDevicePtr priv, int ntouches, bool is_direct_touch)
+{
+ WacomDevice *device = priv->frontend;
+ device->ntouches = ntouches;
+ device->is_direct_touch = is_direct_touch;
+ return true;
+}
+
+guint wacom_device_get_id(WacomDevice *device)
+{
+ return device->id;
+}
+
+const char *wacom_device_get_name(WacomDevice *device)
+{
+ return device->name;
+}
+
+enum WacomToolType wacom_device_get_tool_type(WacomDevice *device)
+{
+ return device->type;
+}
+
+const WacomAxis *wacom_device_get_axis(WacomDevice *device,
+ enum WacomEventAxis which)
+{
+ g_return_val_if_fail(which <= WACOM_RING2, NULL);
+
+ return (device->axis_mask & which) ? &device->axes[ffs(which)] : NULL;
+}
+
+int wacom_device_get_num_buttons(WacomDevice *device)
+{
+ return device->nbuttons;
+}
+gboolean wacom_device_has_keys(WacomDevice *device)
+{
+ return device->has_keys;
+}
+int wacom_device_get_num_touches(WacomDevice *device)
+{
+ return device->ntouches;
+}
+gboolean wacom_device_is_direct_touch(WacomDevice *device)
+{
+ return device->is_direct_touch;
+}
+gboolean wacom_device_is_absolute(WacomDevice *device)
+{
+ return device->is_absolute;
+}
+int wacom_device_get_num_axes(WacomDevice *device)
+{
+ return device->naxes;
+}
+
+int wcmOpen(WacomDevicePtr priv)
+{
+ WacomDevice *device = priv->frontend;
+ const char *path = wacom_options_get(device->options, "device");
+ int fd;
+
+ if (!path) {
+ wcmLog(priv, W_ERROR, "Error opending device, no option 'device'\n");
+ return -ENODEV;
+ }
+
+ fd = open(path, O_RDONLY|O_NONBLOCK);
+
+ g_free(device->path);
+ device->path = strdup(path);
+ g_object_notify_by_pspec(G_OBJECT(device), obj_properties[PROP_PATH]);
+
+ return fd != -1 ? fd : -errno;
+}
+
+
+void wcmClose(WacomDevicePtr priv)
+{
+ WacomDevice *device = priv->frontend;
+
+ close(device->fd);
+ device->fd = -1;
+}
+
+/* FIXME: the bits we emulate in the driver don't depend on timers, so we just
+ * have a stub timer implementation that does nothing */
+WacomTimerPtr wcmTimerNew(void)
+{
+ return calloc(1, 1);
+}
+
+void wcmTimerFree(WacomTimerPtr timer)
+{
+ g_free(timer);
+}
+
+void wcmTimerCancel(WacomTimerPtr timer)
+{
+}
+
+void wcmTimerSet(WacomTimerPtr timer, uint32_t millis, WacomTimerCallback func, void *userdata)
+{
+}
+
+void wcmUpdateSerialProperty(WacomDevicePtr priv) {}
+void wcmUpdateHWTouchProperty(WacomDevicePtr priv) {}
+void wcmUpdateRotationProperty(WacomDevicePtr priv) {}
+
+struct hotplug {
+ WacomDriver *driver;
+ char *name;
+ WacomOptions *opts;
+};
+
+static gboolean hotplugDevice(gpointer data)
+{
+ struct hotplug *hotplug = data;
+ g_autoptr(WacomDevice) new_device = wacom_device_new(hotplug->driver, hotplug->name, hotplug->opts);
+
+ g_object_unref(hotplug->driver);
+ g_object_unref(hotplug->opts);
+ g_free(hotplug->name);
+ g_free(hotplug);
+
+ g_return_val_if_fail(new_device != NULL, FALSE); /* silence 'unused variable device' compiler warning */
+
+ /* WacomDriver holds the device ref */
+ return FALSE;
+}
+
+void wcmQueueHotplug(WacomDevicePtr priv, const char* name, const char *type, int serial)
+{
+ WacomDevice *device = priv->frontend;
+ struct hotplug *hotplug = g_new0(struct hotplug, 1);
+ WacomOptions *new_opts = wacom_options_duplicate(device->options);
+ char buf[64] = {0};
+
+ wacom_options_set(new_opts, "Type", type);
+ if (serial > -1) {
+ snprintf(buf, sizeof(buf), "0x%x", serial);
+ wacom_options_set(new_opts, "Serial", buf);
+ }
+
+ hotplug->name = g_strdup(name);
+ hotplug->opts = g_steal_pointer(&new_opts);
+ hotplug->driver = g_object_ref(device->driver);
+
+ g_idle_add(hotplugDevice, hotplug);
+}
+
+uint32_t wcmTimeInMillis(void)
+{
+ return (uint32_t)g_get_monotonic_time();
+}
+
+/****************** GObject boilerplate *****************/
+
+static void
+wacom_device_dispose(GObject *gobject)
+{
+ G_OBJECT_CLASS (wacom_device_parent_class)->dispose (gobject);
+}
+
+static void
+wacom_device_finalize(GObject *gobject)
+{
+ WacomDevice *device = WACOM_DEVICE(gobject);
+ g_free(device->path);
+ g_object_unref(device->driver);
+ G_OBJECT_CLASS (wacom_device_parent_class)->finalize (gobject);
+}
+
+static void
+wacom_device_get_property(GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ WacomDevice *self = WACOM_DEVICE(object);
+
+ switch (property_id) {
+ case PROP_NAME:
+ g_value_set_string(value, self->name);
+ break;
+ case PROP_ENABLED:
+ g_value_set_boolean(value, self->enabled);
+ break;
+ case PROP_PATH:
+ g_value_set_string(value, self->path);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wacom_device_class_init(WacomDeviceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->dispose = wacom_device_dispose;
+ object_class->finalize = wacom_device_finalize;
+ object_class->get_property = wacom_device_get_property;
+
+ /**
+ * WacomDevice:name
+ */
+ obj_properties[PROP_NAME] =
+ g_param_spec_string("name",
+ "Device name",
+ "The device's name",
+ NULL, /* default value */
+ G_PARAM_READABLE);
+ g_object_class_install_property(object_class, PROP_NAME,
+ obj_properties[PROP_NAME]);
+
+ /**
+ * WacomDevice:enabled
+ */
+ obj_properties[PROP_ENABLED] =
+ g_param_spec_boolean("enabled",
+ "Enabled indicator",
+ "Indicates if the device is enabled",
+ FALSE,
+ G_PARAM_READABLE);
+ g_object_class_install_property(object_class, PROP_ENABLED,
+ obj_properties[PROP_ENABLED]);
+
+ /**
+ * WacomDevice:path
+ */
+ obj_properties[PROP_PATH] =
+ g_param_spec_string("path",
+ "Device path",
+ "The device's event node",
+ NULL /* default value */,
+ G_PARAM_READABLE);
+ g_object_class_install_property(object_class, PROP_PATH,
+ obj_properties[PROP_PATH]);
+
+ /**
+ * WacomDevice::keycode:
+ * @device: the device that sent the event
+ * @keycode: the keycode
+ * @is_press: true for down, false for up
+ *
+ * The keycode signal is emitted whenever the device sends a key event
+ */
+ signals[SIGNAL_KEY] =
+ g_signal_new("keycode",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ /* key, is_press */
+ 2, G_TYPE_UINT, G_TYPE_BOOLEAN);
+
+ /**
+ * WacomDevice::button:
+ * @device: the device that sent the event
+ * @is_absolute: true if the axis values are absolute
+ * @button: the 1-indexed button number
+ * @is_press: true for down, false for up
+ * @axes: a WacomEventData pointer
+ *
+ * The button signal is emitted whenever the device sends a button
+ * event
+ */
+ signals[SIGNAL_BUTTON] =
+ g_signal_new("button",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ /* is_absolute, button, is_press, axes */
+ 4, G_TYPE_BOOLEAN, G_TYPE_UINT,
+ G_TYPE_BOOLEAN, G_TYPE_POINTER);
+
+ /**
+ * WacomDevice::motion:
+ * @device: the device that sent the event
+ * @is_absolute: true if the axis values are absolute
+ * @axes: a WacomEventData pointer
+ *
+ * The motion signal is emitted whenever the device changes position
+ */
+ signals[SIGNAL_MOTION] =
+ g_signal_new("motion",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ /* is_absolute, axes */
+ 2, G_TYPE_BOOLEAN, G_TYPE_POINTER);
+
+ /**
+ * WacomDevice::touch:
+ * @device: the device that sent the event
+ * @type: one of WacomTouchState
+ * @touchid: an unsigned integer with the touch ID of this touch
+ * @x: x position
+ * @y: y position
+ *
+ * The touch signal is emitted whenever a touch starts/moves/stops
+ */
+ signals[SIGNAL_TOUCH] =
+ g_signal_new("touch",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ /* type, touchid, x, y */
+ 3, G_TYPE_INT, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INT);
+
+ /**
+ * WacomDevice::proximity:
+ * @device: the device that sent the event
+ * @is_prox_in: true for proximity in, false for proxiity out
+ * @axes: a WacomEventData pointer
+ *
+ * The proximity signal is emitted whenever the device enters of
+ * leaves proximity
+ */
+ signals[SIGNAL_PROXIMITY] =
+ g_signal_new("proximity",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ /* is_prox_in, axes */
+ 2, G_TYPE_BOOLEAN, G_TYPE_POINTER);
+
+ /**
+ * WacomDevice::log-message:
+ * @device: the device that sent the event
+ * @type: a string to use as prefix
+ * @msg: the log message
+ *
+ * The log-message signal is emitted whenever the device is trying to
+ * write a message to the regular log
+ */
+ signals[SIGNAL_LOGMSG] =
+ g_signal_new("log-message",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ /* type, string */
+ 2, G_TYPE_STRING, G_TYPE_STRING);
+
+
+ /**
+ * WacomDevice::debug-message:
+ * @device: the device that sent the event
+ * @level: the numerical debug level for this message
+ * @func: function where the debug message was triggered
+ * @msg: the debug message
+ *
+ * The debug-message signal is emitted whenever the device is trying to
+ * write a debug messag
+ */
+ signals[SIGNAL_DBGMSG] =
+ g_signal_new("debug-message",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ /* level, func, string */
+ 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING);
+
+ /**
+ * WacomDevice::read-error:
+ * @device: the device that sent the event
+ * @errno: the errno that was detected
+ *
+ * The read-error signal is emitted whenever that was an error reading
+ * the device.
+ */
+ signals[SIGNAL_READ_ERROR] =
+ g_signal_new("read-error",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ /* errno */
+ 1, G_TYPE_INT);
+}
+
+static void
+wacom_device_init(WacomDevice *self)
+{
+}
+
+WacomAxis* wacom_axis_copy(const WacomAxis *axis)
+{
+ WacomAxis *new_axis = malloc(sizeof(*axis));
+ memcpy(new_axis, axis, sizeof(*axis));
+ return new_axis;
+}
+
+void wacom_axis_free(WacomAxis *axis)
+{
+ free(axis);
+}
diff --git a/src/gwacom/wacom-device.h b/src/gwacom/wacom-device.h
new file mode 100644
index 0000000..2108661
--- /dev/null
+++ b/src/gwacom/wacom-device.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* THIS IS NOT STABLE API, USE WITHIN THIS REPO ONLY */
+
+#pragma once
+
+#include <stdint.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include "wacom-driver.h"
+
+G_BEGIN_DECLS
+
+#define WACOM_TYPE_OPTIONS (wacom_options_get_type())
+G_DECLARE_FINAL_TYPE (WacomOptions, wacom_options, WACOM, OPTIONS, GObject);
+
+/**
+ * wacom_options_new: (skip)
+ * @key: (nullable): The first key, followed by a const char * value, followed
+ * by other key/valuy pairs, terminated with a NULL key.
+ *
+ * Create a new set of configuration options in key-value format, matching the
+ * xorg.conf options the driver supports. Parsing of the varargs stops when
+ * the next key is NULL.
+ *
+ * Use g_object_unref() to free the returned object.
+ *
+ * Returns: (transfer full): a new WacomOptions object
+ */
+__attribute__((sentinel))
+WacomOptions *wacom_options_new(const char *key, ...);
+
+/**
+ * wacom_options_duplicate:
+ *
+ * Returns: (transfer full): a deep copy of the options given as argument.
+ */
+WacomOptions *wacom_options_duplicate(WacomOptions *opts);
+
+/**
+ * wacom_options_get:
+ *
+ * Return the value for the given key or NULL if the key does not exist.
+ */
+const char *wacom_options_get(WacomOptions *opts, const char *key);
+
+/**
+ * wacom_options_set:
+ *
+ * Add or replace the option with the given string value.
+ */
+void wacom_options_set(WacomOptions *opts, const char *key, const char *value);
+
+
+#define WACOM_TYPE_DEVICE (wacom_device_get_type())
+G_DECLARE_FINAL_TYPE (WacomDevice, wacom_device, WACOM, DEVICE, GObject)
+
+enum WacomToolType {
+ WACOM_TOOL_INVALID = 0,
+ WACOM_TOOL_STYLUS,
+ WACOM_TOOL_ERASER,
+ WACOM_TOOL_CURSOR,
+ WACOM_TOOL_PAD,
+ WACOM_TOOL_TOUCH,
+};
+
+enum WacomTouchState {
+ WACOM_TOUCH_BEGIN,
+ WACOM_TOUCH_UPDATE,
+ WACOM_TOUCH_END,
+};
+
+enum WacomEventAxis {
+ WACOM_X = (1 << 0),
+ WACOM_Y = (1 << 1),
+ WACOM_PRESSURE = (1 << 2),
+ WACOM_TILT_X = (1 << 3),
+ WACOM_TILT_Y = (1 << 4),
+ WACOM_STRIP_X = (1 << 5),
+ WACOM_STRIP_Y = (1 << 6),
+ WACOM_ROTATION = (1 << 7),
+ WACOM_THROTTLE = (1 << 8),
+ WACOM_WHEEL = (1 << 9),
+ WACOM_RING = (1 << 10),
+ WACOM_RING2 = (1 << 11),
+};
+
+/* The pointer argument to all the event signals. If the mask is set for
+ * a given axis, that value contains the current state of the axis */
+typedef struct {
+ uint32_t mask; /* bitmask of WacomEventAxis */
+ int x, y;
+ int pressure;
+ int tilt_x, tilt_y;
+ int strip_x, strip_y;
+ int rotation;
+ int throttle;
+ int wheel;
+ int ring, ring2;
+} WacomEventData;
+
+#define WACOM_TYPE_AXIS (wacom_axis_get_type())
+
+typedef struct {
+ enum WacomEventAxis type;
+ int min, max;
+ int res;
+} WacomAxis;
+
+GType wacom_axis_get_type(void);
+WacomAxis* wacom_axis_copy(const WacomAxis *axis);
+void wacom_axis_free(WacomAxis *axis);
+
+/**
+ * wacom_device_new:
+ *
+ * Note that the driver may rename the device, the name is not guaranteed to
+ * be the device's name.
+ *
+ * If options does not contain a key "Device" with the path to the event node,
+ * the driver will auto-detect the event node during PreInit.
+ *
+ * Use wacom_device_preinit() to pre-init the device and verify it can be
+ * used. Then call wacom_device_setup() to initialize the device with the
+ * right axes, followed by wacom_device_enable() to have it process events.
+ *
+ * You should connect to the log-message and debug-message signals before
+ * calling wacom_device_preinit().
+ *
+ * Use g_object_unref() to free the returned object.
+ *
+ * @driver The driver to assign to
+ * @param name (trqnsfer none) The device name
+ * @param options (transfer none) The device name
+ *
+ * Returns: (transfer full): a new allocated device witha refcount of at
+ * least 1
+ */
+WacomDevice *wacom_device_new(WacomDriver *driver,
+ const char *name,
+ WacomOptions *options);
+
+void wacom_device_remove(WacomDevice *device);
+
+gboolean wacom_device_preinit(WacomDevice *device);
+gboolean wacom_device_setup(WacomDevice *device);
+gboolean wacom_device_enable(WacomDevice *device);
+void wacom_device_disable(WacomDevice *device);
+
+/**
+ * wacom_device_get_id:
+ *
+ * A numeric value assigned to the device by this wrapper library during
+ * wacom_device_new(). This value serves the same purpose as the X11 device
+ * ID.
+ */
+guint wacom_device_get_id(WacomDevice *device);
+const char *wacom_device_get_name(WacomDevice *device);
+enum WacomToolType wacom_device_get_tool_type(WacomDevice *device);
+
+/* The following getters are only available after wacom_device_setup() */
+
+int wacom_device_get_num_buttons(WacomDevice *device);
+gboolean wacom_device_has_keys(WacomDevice *device);
+int wacom_device_get_num_touches(WacomDevice *device);
+gboolean wacom_device_is_direct_touch(WacomDevice *device);
+gboolean wacom_device_is_absolute(WacomDevice *device);
+int wacom_device_get_num_axes(WacomDevice *device);
+
+/**
+ * wacom_device_get_axis:
+ *
+ * This function is only available after wacom_device_setup()
+ *
+ * Returns: (transfer none): the axis of this device or NULL for an invalid
+ * index.
+ */
+const WacomAxis* wacom_device_get_axis(WacomDevice *device,
+ enum WacomEventAxis which);
+
+G_END_DECLS
diff --git a/src/gwacom/wacom-driver.c b/src/gwacom/wacom-driver.c
new file mode 100644
index 0000000..bc9d6bf
--- /dev/null
+++ b/src/gwacom/wacom-driver.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "wacom-driver.h"
+#include "wacom-private.h"
+
+#include "xf86Wacom.h"
+
+struct _WacomDriver {
+ GObject parent_instance;
+
+ GList *devices;
+};
+
+enum {
+ SIGNAL_DEVICE_ADDED,
+ SIGNAL_DEVICE_REMOVED,
+ LAST_SIGNAL,
+};
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (WacomDriver, wacom_driver, G_TYPE_OBJECT)
+
+int wcmForeachDevice(WacomDevicePtr priv, WacomDeviceCallback func, void *data)
+{
+ WacomDevice *device = priv->frontend;
+ WacomDriver *driver = wacom_device_get_driver(device);
+ GList *elem = driver->devices;
+ int nmatch = 0;
+
+ while (elem)
+ {
+ WacomDevice *d = elem->data;
+ int rc = func(wacom_device_get_impl(d), data);
+ elem = elem->next;
+ if (rc == -ENODEV)
+ continue;
+
+ if (rc < 0)
+ return -rc;
+ nmatch += 1;
+ if (rc == 0)
+ break;
+ }
+
+ return nmatch;
+}
+
+WacomDriver*
+wacom_driver_new(void)
+{
+ return g_object_new(WACOM_TYPE_DRIVER, NULL);
+}
+
+GList *
+wacom_driver_get_devices(WacomDriver *driver)
+{
+ return g_list_copy(driver->devices);
+}
+
+void
+wacom_driver_add_device(WacomDriver *driver, WacomDevice *device)
+{
+ driver->devices = g_list_append(driver->devices, g_object_ref(device));
+ g_signal_emit(driver, signals[SIGNAL_DEVICE_ADDED], 0, device);
+}
+
+void
+wacom_driver_remove_device(WacomDriver *driver, WacomDevice *device)
+{
+ if (g_list_find(driver->devices, device)) {
+ driver->devices = g_list_remove(driver->devices, device);
+ g_signal_emit(driver, signals[SIGNAL_DEVICE_REMOVED], 0, device);
+ g_object_unref(device);
+ }
+}
+
+static void
+wacom_driver_dispose(GObject *gobject)
+{
+ G_OBJECT_CLASS (wacom_driver_parent_class)->dispose (gobject);
+}
+
+static void
+wacom_driver_finalize(GObject *gobject)
+{
+ G_OBJECT_CLASS (wacom_driver_parent_class)->finalize (gobject);
+}
+
+
+static void
+wacom_driver_class_init(WacomDriverClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->dispose = wacom_driver_dispose;
+ object_class->finalize = wacom_driver_finalize;
+
+ /**
+ * WacomDriver::device-added:
+ * @driver: the driver instance
+ * @device: the device that was added
+ *
+ * The device-added signal is emitted whenever a device is added. A
+ * caller should connect to the device's signals as required and
+ * initialize and enable the device.
+ */
+ signals[SIGNAL_DEVICE_ADDED] =
+ g_signal_new("device-added",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ 1, WACOM_TYPE_DEVICE);
+
+ /**
+ * WacomDriver::device-removed:
+ * @driver: the driver instance
+ * @device: the device that was removed
+ */
+ signals[SIGNAL_DEVICE_REMOVED] =
+ g_signal_new("device-removed",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE,
+ 1, WACOM_TYPE_DEVICE);
+}
+
+static void
+wacom_driver_init(WacomDriver *self)
+{
+}
+
diff --git a/src/gwacom/wacom-driver.h b/src/gwacom/wacom-driver.h
new file mode 100644
index 0000000..a6a52f8
--- /dev/null
+++ b/src/gwacom/wacom-driver.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define WACOM_TYPE_DRIVER (wacom_driver_get_type())
+G_DECLARE_FINAL_TYPE (WacomDriver, wacom_driver, WACOM, DRIVER, GObject)
+
+
+/**
+ * wacom_driver_new:
+ *
+ * Use g_object_unref() to release the driver object
+ *
+ * Returns: (transfer full): a new WacomDriver object
+ */
+WacomDriver *wacom_driver_new(void);
+
+/**
+ * wacom_driver_get_devices:
+ *
+ * Returns: (element-type WacomDevice) (transfer container):
+ */
+GList *wacom_driver_get_devices(WacomDriver *driver);
+
+G_END_DECLS
+
+
diff --git a/src/gwacom/wacom-private.h b/src/gwacom/wacom-private.h
new file mode 100644
index 0000000..f8c7fa1
--- /dev/null
+++ b/src/gwacom/wacom-private.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include "wacom-driver.h"
+#include "wacom-device.h"
+
+
+WacomDriver *wacom_device_get_driver(WacomDevice *device);
+void *wacom_device_get_impl(WacomDevice *device);
+
+void wacom_driver_add_device(WacomDriver *driver, WacomDevice *device);
+void wacom_driver_remove_device(WacomDriver *driver, WacomDevice *device);
diff --git a/src/x11/xf86Wacom.c b/src/x11/xf86Wacom.c
index 5babf82..df74d5c 100644
--- a/src/x11/xf86Wacom.c
+++ b/src/x11/xf86Wacom.c
@@ -246,7 +246,6 @@ static InputOption *wcmOptionDupConvert(WacomDevicePtr priv, const char* name, c
WacomToolPtr ser = common->serials;
InputOption *iopts = NULL;
pointer options, o;
- int rc;
options = xf86OptionListDuplicate(original);
options = xf86ReplaceStrOption(options, "Type", type);
diff --git a/tools/wacom-record.c b/tools/wacom-record.c
new file mode 100644
index 0000000..63e86fa
--- /dev/null
+++ b/tools/wacom-record.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "config-ver.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <glib.h>
+#include <glib-unix.h>
+#include <libudev.h>
+#include "wacom-driver.h"
+#include "wacom-device.h"
+
+#define strbool(x_) (x_) ? "true" : "false"
+
+static guint debug_level = 0;
+static gboolean print_version = false;
+static gboolean grab_device = false;
+static const char *driver_options = NULL;
+
+static GOptionEntry opts[] =
+{
+ { "version", 0, 0, G_OPTION_ARG_NONE, &print_version, "Print version and exit", NULL },
+ { "debug-level", 'v', 0, G_OPTION_ARG_INT, &debug_level, "Set the debug level", NULL },
+ { "options", 0, 0, G_OPTION_ARG_STRING, &driver_options, "Driver options in the form \"Foo=bar,Baz=bat\"", NULL },
+ { "grab", 0, 0, G_OPTION_ARG_NONE, &grab_device, "Grab the device while recording", NULL },
+ { NULL },
+};
+
+static void log_message(WacomDevice *device, const char *type, const char *message)
+{
+ printf("# [%s] %s: %s", type, wacom_device_get_name(device), message);
+}
+
+static void debug_message(WacomDevice *device, int debug_level, const char *func, const char *message)
+{
+ printf("# DBG%02d %-35s| %s: %s", debug_level, func, wacom_device_get_name(device), message);
+}
+
+static inline void print_axes(const WacomEventData *data)
+{
+ printf(" axes: { x: %5d, y: %5d, pressure: %4d, tilt: [%3d,%3d], rotation: %3d, throttle: %3d, wheel: %3d, rings: [%3d, %3d] }\n",
+ data->x, data->y,
+ (data->mask & WACOM_PRESSURE) ? data->pressure : 0,
+ (data->mask & WACOM_TILT_X) ? data->tilt_x : 0,
+ (data->mask & WACOM_TILT_Y) ? data->tilt_y : 0,
+ (data->mask & WACOM_ROTATION) ? data->rotation : 0,
+ (data->mask & WACOM_THROTTLE) ? data->throttle : 0,
+ (data->mask & WACOM_WHEEL) ? data->wheel : 0,
+ (data->mask & WACOM_RING) ? data->ring : 0,
+ (data->mask & WACOM_RING2) ? data->ring2 : 0);
+}
+
+static void proximity(WacomDevice *device, gboolean is_prox_in, WacomEventData *data)
+{
+ printf(" - source: %d\n"
+ " event: proximity\n"
+ " proximity-in: %s\n",
+ wacom_device_get_id(device), strbool(is_prox_in));
+ print_axes(data);
+}
+
+static void motion(WacomDevice *device, gboolean is_absolute, WacomEventData *data)
+{
+ printf(" - source: %d\n"
+ " event: motion\n", wacom_device_get_id(device));
+ print_axes(data);
+}
+
+static void button(WacomDevice *device, gboolean is_absolute, int button,
+ gboolean is_press, WacomEventData *data)
+{
+ printf(" - source: %d\n"
+ " event: button\n"
+ " button: %d\n"
+ " is-press: %s\n",
+ wacom_device_get_id(device), button, strbool(is_press));
+ print_axes(data);
+}
+
+static void key(WacomDevice *device, gboolean keycode, gboolean is_press)
+{
+ printf(" - source: %d\n"
+ " event: key\n"
+ " key: %d\n"
+ " is-press: %s\n", wacom_device_get_id(device),
+ keycode, strbool(is_press));
+}
+
+static void device_added(WacomDriver *driver, WacomDevice *device)
+{
+ printf(" - source: %d\n"
+ " event: new-device\n"
+ " name: \"%s\"\n",
+ wacom_device_get_id(device), wacom_device_get_name(device));
+
+ g_signal_connect(device, "log-message", G_CALLBACK(log_message), NULL);
+ g_signal_connect(device, "debug-message", G_CALLBACK(debug_message), NULL);
+ g_signal_connect(device, "motion", G_CALLBACK(motion), NULL);
+ g_signal_connect(device, "proximity", G_CALLBACK(proximity), NULL);
+ g_signal_connect(device, "button", G_CALLBACK(button), NULL);
+ g_signal_connect(device, "keycode", G_CALLBACK(key), NULL);
+
+ if (!wacom_device_preinit(device))
+ fprintf(stderr, "Failed to preinit device %s\n", wacom_device_get_name(device));
+ else if (!wacom_device_setup(device))
+ fprintf(stderr, "Failed to setup device %s\n", wacom_device_get_name(device));
+ else if (!wacom_device_enable(device))
+ fprintf(stderr, "Failed to enable device %s\n", wacom_device_get_name(device));
+ else {
+ const char *typestr = NULL;
+
+ switch(wacom_device_get_tool_type(device)) {
+ case WACOM_TOOL_INVALID: typestr = "invalid"; break;
+ case WACOM_TOOL_STYLUS: typestr = "stylus"; break;
+ case WACOM_TOOL_ERASER: typestr = "eraser"; break;
+ case WACOM_TOOL_CURSOR: typestr = "cursor"; break;
+ case WACOM_TOOL_PAD: typestr = "pad"; break;
+ case WACOM_TOOL_TOUCH: typestr = "touch"; break;
+
+ }
+
+ printf(" type: %s\n", typestr);
+ printf(" capabilities:\n"
+ " keys: %s\n"
+ " is-absolute: %s\n"
+ " is-direct-touch: %s\n"
+ " ntouches: %d\n"
+ " naxes: %d\n",
+ strbool(wacom_device_has_keys(device)),
+ strbool(wacom_device_is_absolute(device)),
+ strbool(wacom_device_is_direct_touch(device)),
+ wacom_device_get_num_touches(device),
+ wacom_device_get_num_axes(device));
+ printf(" axes:\n");
+ for (enum WacomEventAxis which = WACOM_X; which <= WACOM_RING2; which <<= 1) {
+ const WacomAxis *axis = wacom_device_get_axis(device, which);
+ const char *typestr = NULL;
+
+ if (!axis)
+ continue;
+
+ switch (axis->type) {
+ case WACOM_X: typestr = "x"; break;
+ case WACOM_Y: typestr = "y"; break;
+ case WACOM_PRESSURE: typestr = "pressure"; break;
+ case WACOM_TILT_X: typestr = "tilt_x"; break;
+ case WACOM_TILT_Y: typestr = "tilt_y"; break;
+ case WACOM_STRIP_X: typestr = "strip_x"; break;
+ case WACOM_STRIP_Y: typestr = "strip_y"; break;
+ case WACOM_ROTATION: typestr = "rotation"; break;
+ case WACOM_THROTTLE: typestr = "throttle"; break;
+ case WACOM_WHEEL: typestr = "wheel"; break;
+ case WACOM_RING: typestr = "ring"; break;
+ case WACOM_RING2: typestr = "ring2"; break;
+ }
+
+ printf(" - {type: %-12s, range: [%5d, %5d], resolution: %5d}\n",
+ typestr, axis->min, axis->max, axis->res);
+
+ }
+ return;
+ }
+
+ wacom_device_remove(device);
+}
+
+
+static void device_removed(WacomDriver *driver, WacomDevice *device)
+{
+ printf(" - source: %d\n"
+ " event: removed-device\n"
+ " name: \"%s\"\n",
+ wacom_device_get_id(device), wacom_device_get_name(device));
+}
+
+static char *find_device(void)
+{
+ const char *entry;
+ GDir *dir = g_dir_open("/dev/input/", 0, NULL);
+ struct udev *udev;
+ const char *prefix = isatty(STDERR_FILENO) ? "" : "#";
+ int selected_device;
+
+ if (!dir)
+ return NULL;
+
+ udev = udev_new();
+ fprintf(stderr, "%sAvailable devices:\n", prefix);
+
+ while ((entry = g_dir_read_name(dir))) {
+ struct udev_device *dev, *parent;
+ const char *name;
+ g_autofree char *syspath = NULL;
+
+ if (!g_str_has_prefix(entry, "event"))
+ continue;
+
+ syspath = g_strdup_printf("/sys/class/input/%s", entry);
+ dev = udev_device_new_from_syspath(udev, syspath);
+ if (!dev)
+ continue;
+
+ parent = udev_device_get_parent(dev);
+ name = udev_device_get_sysattr_value(parent, "name");
+ fprintf(stderr, "%s/dev/input/%s: %s\n", prefix, entry, name);
+
+ udev_device_unref(dev);
+ }
+ fprintf(stderr, "%sSelect the device event number: ", prefix);
+ if (scanf("%d", &selected_device) != 1 || selected_device < 0)
+ return NULL;
+
+ udev_unref(udev);
+ g_dir_close(dir);
+
+ return g_strdup_printf("/dev/input/event%d", selected_device);
+}
+
+static void print_device_info(const char *path, char *name)
+{
+ printf(" device:\n");
+ printf(" path: %s\n", path);
+ printf(" name: \"%s\"\n", name);
+}
+
+static char *get_device_name(const char *path)
+{
+ struct udev *udev;
+ struct udev_device *dev, *parent;
+ g_autofree char *basename = g_path_get_basename(path);
+ g_autofree char *syspath;
+ char *name = NULL;
+
+ udev = udev_new();
+
+ /* This won't work for symlinks. Oh well */
+ syspath = g_strdup_printf("/sys/class/input/%s", basename);
+ dev = udev_device_new_from_syspath(udev, syspath);
+ if (dev) {
+ const char *udev_name;
+ parent = udev_device_get_parent(dev);
+ udev_name = udev_device_get_sysattr_value(parent, "name");
+ name = g_strdup(udev_name);
+ }
+
+ udev_device_unref(dev);
+ udev_unref(udev);
+ return name;
+}
+
+static gboolean cb_sigint(gpointer loop)
+{
+ fprintf(stderr, "Exiting\n");
+ g_main_loop_quit(loop);
+ return FALSE;
+}
+
+
+int main(int argc, char **argv)
+{
+ g_autoptr(WacomDriver) driver = NULL;
+ g_autoptr(WacomDevice) device = NULL;
+ g_autoptr(WacomOptions) options = NULL;
+ g_autoptr(GMainLoop) loop = NULL;
+ g_autoptr(GOptionContext) context = g_option_context_new("- record events from a Wacom device");
+ GError *error = NULL;
+ g_autofree char *path = NULL;
+ g_autofree char *name = NULL;
+
+ g_option_context_add_main_entries(context, opts, NULL);
+ if (!g_option_context_parse (context, &argc, &argv, &error)) {
+ fprintf(stderr, "option parsing failed: %s\n", error->message);
+ return 2;
+ }
+
+ if (print_version) {
+ printf("%s\n", PACKAGE_VERSION);
+ return 0;
+ }
+
+ if (argc <= 1)
+ path = find_device();
+ else
+ path = strdup(argv[1]);
+
+ if (!path) {
+ fprintf(stderr, "Invalid device path or unable to find device");
+ return 1;
+ }
+
+ printf("wacom-record:\n");
+ printf(" version: %s\n", PACKAGE_VERSION);
+ printf(" git: %s\n", BUILD_VERSION);
+
+ name = get_device_name(path);
+ print_device_info(path, name);
+
+ driver = wacom_driver_new();
+ options = wacom_options_new("device", path, NULL);
+ if (grab_device)
+ wacom_options_set(options, "Grab", "true");
+ if (debug_level) {
+ g_autofree char *lvl = g_strdup_printf("%d", debug_level);
+ wacom_options_set(options, "DebugLevel", lvl);
+ wacom_options_set(options, "CommonDBG", lvl);
+ }
+ if (driver_options) {
+ g_auto(GStrv) strv = g_strsplit(driver_options, ",", -1);
+ char **opt = strv;
+
+ printf(" options:\n");
+
+ while (*opt) {
+ g_auto(GStrv) kv = g_strsplit(*opt, "=", 2);
+ g_return_val_if_fail(kv[0] != NULL, 1);
+ g_return_val_if_fail(kv[1] != NULL, 1);
+ printf(" - %s: \"%s\"\n", kv[0], kv[1]);
+ wacom_options_set(options, kv[0], kv[1]);
+ opt++;
+ }
+ }
+
+ printf(" events:\n");
+
+ g_signal_connect(driver, "device-added", G_CALLBACK(device_added), NULL);
+ g_signal_connect(driver, "device-removed", G_CALLBACK(device_removed), NULL);
+
+ ++argv; --argc; /* first arg is already in path */
+ while (true) {
+ device = wacom_device_new(driver, name, options);
+ if (!device) {
+ fprintf(stderr, "Unable to record device %s - is this a Wacom tablet?\n", path);
+ return 1;
+ }
+
+ if (--argc == 0)
+ break;
+
+ g_free(path);
+ path = strdup(*(++argv));
+ wacom_options_set(options, "device", path);
+ }
+
+
+ loop = g_main_loop_new(NULL, FALSE);
+ g_unix_signal_add(SIGINT, cb_sigint, loop);
+ g_main_loop_run(loop);
+
+ return 0;
+}