/*
 * 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;

	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 (WacomEventData, wacom_event_data, wacom_event_data_copy, wacom_event_data_free)
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 append_slist(gpointer key, gpointer value, gpointer user_data)
{
	GSList **list = user_data;
	*list = g_slist_append(*list, g_strdup(key));
}

/**
 * wacom_options_list_keys:
 *
 * Returns: (element-type utf8) (transfer full):
 */
GSList *wacom_options_list_keys(WacomOptions *opts)
{
	GSList *list = NULL;

	g_hash_table_foreach(opts->ht, append_slist, &list);

	return list;
}

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,
	SIGNAL_EVDEV,

	LAST_SIGNAL,
};

static guint signals[LAST_SIGNAL] = { 0 };

ValuatorMask *
valuator_mask_new(int num_valuators)
{
	return NULL;
}

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 = (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;
}

WacomOptions *wacom_device_get_options(WacomDevice *device)
{
	return device->options;
}

void wacom_device_set_runtime_option(WacomDevice *device, const char *name, const char *value)
{
	WacomDevicePtr priv = device->priv;

	wcmLog(priv, W_ERROR, "Setting special option %s=%s\n", name, value);

	if (g_str_equal(name, "PanButton")) {
		guint btn = atoi(value) - 1; /* array is zero-indexed, config options use 1-indexed ones */
		assert(btn < sizeof(priv->key_actions));
		wcmActionSet(&priv->key_actions[btn], 0, AC_PANSCROLL);
	} else {
		wcmLog(priv, W_ERROR, ":::::::::::::::: Unsupported runtime option %s ::::::::::::::::\n", name);
	}
}

/****************** 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. Unless there are no devices (setup error on
	 * first device), then we silently discard the message */

	WacomDevice *device = NULL;
	g_autofree char *str = NULL;
	va_list args;

	if (!common->wcmDevices)
		return;

	device = common->wcmDevices->frontend;
	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;
	WacomTouchState state;

	switch (type) {
	case XI_TouchBegin: state = WTOUCH_BEGIN; break;
	case XI_TouchUpdate: state = WTOUCH_UPDATE; break;
	case XI_TouchEnd: state = WTOUCH_END; break;
	default:
			  abort();
	}
	g_signal_emit(device, signals[SIGNAL_TOUCH], 0, state, touchid, x, y);
}

void wcmNotifyEvdev(WacomDevicePtr priv, const struct input_event *event)
{
	WacomDevice *device = priv->frontend;
	g_signal_emit(device, signals[SIGNAL_EVDEV], 0, event);
}

void wcmInitAxis(WacomDevicePtr priv, enum WacomAxisType type,
			int min, int max, int res)
{
	WacomDevice *device = priv->frontend;
	WacomAxis ax = {
		.type = (WacomEventAxis)type,
		.min = min,
		.max = max,
		.res = res,
	};
	device->axes[ffs(type)] = ax;
	device->axis_mask |= (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;
}

WacomToolType wacom_device_get_tool_type(WacomDevice *device)
{
	return device->type;
}

const WacomAxis *wacom_device_get_axis(WacomDevice *device,
				       WacomEventAxis which)
{
	g_return_val_if_fail(which <= _WAXIS_LAST, 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, unsigned 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 != UINT_MAX) {
		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, WACOM_TYPE_EVENT_DATA);

	/**
	 * 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, WACOM_TYPE_EVENT_DATA);

	/**
	 * 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, WACOM_TYPE_EVENT_DATA);

	/**
	 * 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);
	/**
	 * WacomDevice::evdev-event:
	 * @device: the device that sent the event
	 * @event: the struct input_event
	 *
	 * The evdev signal is emitted whenever an evdev event is about to be
	 * processed by the driver
	 */
	signals[SIGNAL_EVDEV] =
		g_signal_new("evdev-event",
			     G_TYPE_FROM_CLASS(klass),
			     G_SIGNAL_RUN_FIRST,
			     0, NULL, NULL, NULL, G_TYPE_NONE,
			     /* struct input_event */
			     1, G_TYPE_POINTER);

}

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);
}

WacomEventData* wacom_event_data_copy(const WacomEventData *event_data)
{
	WacomEventData *new_event_data = malloc(sizeof(*event_data));
	memcpy(new_event_data, event_data, sizeof(*event_data));
	return new_event_data;
}

void wacom_event_data_free(WacomEventData *event_data)
{
	free(event_data);
}
