libtwin: Add touch support

In a similar fashion as the Linux mouse and joystick, support absolute
pointing devices using /dev/input/eventX. Currently it only handles
ABS_X, ABS_Y, and BTN_TOUCH events.

The library interface will open the specified device or, if unspecified,
find the first device in /dev/input that supports absolute and touch
events.

Signed-off-by: Tyler Hall <tylerwhall@gmail.com>
Signed-off-by: Geoff Levand <geoff@infradead.org>
diff --git a/Makefile.am b/Makefile.am
index 2d26ab0..6bd21a4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -73,6 +73,11 @@
 libtwin_libtwin_la_SOURCES += libtwin/twin_linux_js.c
 endif
 
+if TWIN_TOUCHSCREEN
+pkginclude_HEADERS += libtwin/twin_linux_touch.h
+libtwin_libtwin_la_SOURCES += libtwin/twin_linux_touch.c
+endif
+
 if TWIN_PNG
 pkginclude_HEADERS += libtwin/twin_png.h
 libtwin_libtwin_la_SOURCES += libtwin/twin_png.c
diff --git a/configure.ac b/configure.ac
index 0695ead..3cf1496 100644
--- a/configure.ac
+++ b/configure.ac
@@ -87,6 +87,12 @@
 		[Disable linux joystick support (default=enabled)]),
 	twin_joystick="$enableval", twin_joystick="yes")
 
+# linux touchscreen
+AC_ARG_ENABLE(linux-touchscreen,
+	AC_HELP_STRING([--disable-linux-touchscreen],
+		[Disable linux touchscreen support (default=enabled)]),
+	twin_touchscreen="$enableval", twin_touchscreen="yes")
+
 # zlib
 AC_ARG_ENABLE(zlib,
 	AC_HELP_STRING([--disable-zlib],
@@ -193,6 +199,7 @@
 AM_CONDITIONAL(TWIN_TTF, test x$twin_ttf = xyes)
 AM_CONDITIONAL(TWIN_MOUSE, test x$twin_mouse = xyes)
 AM_CONDITIONAL(TWIN_JOYSTICK, test x$twin_joystick = xyes)
+AM_CONDITIONAL(TWIN_TOUCHSCREEN, test x$twin_touchscreen = xyes)
 
 AC_MSG_NOTICE([x11 support:    $twin_x11])
 AC_MSG_NOTICE([fbdev support:  $twin_fb])
@@ -202,6 +209,7 @@
 AC_MSG_NOTICE([twin_ttf tool:  $twin_ttf])
 AC_MSG_NOTICE([linux mouse:    $twin_mouse])
 AC_MSG_NOTICE([linux joystick: $twin_joystick])
+AC_MSG_NOTICE([linux touch:    $twin_touchscreen])
 AC_MSG_NOTICE([altivec:        $twin_altivec])
 
 AC_OUTPUT([Makefile
diff --git a/libtwin/twin_linux_touch.c b/libtwin/twin_linux_touch.c
new file mode 100644
index 0000000..56d7595
--- /dev/null
+++ b/libtwin/twin_linux_touch.c
@@ -0,0 +1,226 @@
+/*
+ * Linux touchscreen driver for Twin
+ *
+ * Copyright 2013 Tyler Hall <tylerwhall@gmail.com>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Twin Library; see the file COPYING.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <errno.h>
+
+#include "twinint.h"
+#include "twin_linux_touch.h"
+
+#define BITS_PER_LONG (sizeof(long) * 8)
+#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
+#define OFF(x)  ((x)%BITS_PER_LONG)
+#define LONG(x) ((x)/BITS_PER_LONG)
+#define test_bit(bit, array)	((array[LONG(bit)] >> OFF(bit)) & 1)
+
+#if 0
+#define DEBUG(fmt...)	printf(fmt)
+#else
+#define DEBUG(fmt...)
+#endif
+
+static int scale_touch_range(int val, int min, int max, int scale_range)
+{
+	twin_fixed_t ret;
+
+	max -= min;
+	val -= min;
+	ret = twin_fixed_div(twin_int_to_fixed(val), twin_int_to_fixed(max));
+	ret = twin_fixed_mul(ret, twin_int_to_fixed(scale_range));
+
+	return twin_fixed_to_int(ret);
+}
+
+static twin_bool_t twin_linux_touch_events(int maybe_unused file,
+					   twin_file_op_t maybe_unused ops,
+					   void *closure)
+{
+	twin_linux_touch_t *touch = closure;
+	twin_event_t tev;
+	struct input_event event;
+	int rc;
+
+	rc = read(touch->fd, &event, sizeof(event));
+	if (rc < 0 && errno != EAGAIN) {
+		perror("error reading input event");
+		return TWIN_FALSE;
+	} else if (rc != sizeof(event)) {
+		/* EAGAIN or rc < sizeof(event)
+		 * Linux guarantees reads of at least one event, so ignore */
+		return TWIN_TRUE;
+	}
+
+	switch (event.type) {
+	case EV_ABS:
+		switch (event.code) {
+		case ABS_X:
+			touch->x = scale_touch_range(event.value,
+						     touch->min_x, touch->max_x,
+						     touch->screen->width);
+			break;
+		case ABS_Y:
+			touch->y = scale_touch_range(event.value,
+						     touch->min_y, touch->max_y,
+						     touch->screen->height);
+			break;
+		}
+		break;
+	case EV_KEY:
+		switch (event.code) {
+		case BTN_TOUCH:
+			touch->btn_state = event.value;
+			touch->btn_pending = 1;
+			break;
+		}
+		break;
+	case EV_SYN:
+		tev.u.pointer.screen_x = touch->x;
+		tev.u.pointer.screen_y = touch->y;
+		tev.u.pointer.button = touch->btn_state;
+		if (touch->btn_pending) {
+			tev.kind = touch->btn_state ?
+				TwinEventButtonDown : TwinEventButtonUp;
+			touch->btn_pending = 0;
+		} else {
+			tev.kind = TwinEventMotion;
+		}
+		twin_screen_dispatch(touch->screen, &tev);
+		break;
+	}
+
+	return TWIN_TRUE;
+}
+
+static int get_abs_range(twin_linux_touch_t *touch)
+{
+	struct input_absinfo absinfo;
+	int rc;
+
+	rc = ioctl(touch->fd, EVIOCGABS(ABS_X), &absinfo);
+	if (rc < 0) {
+		perror("EVIOCGABS X");
+		return rc;
+	}
+	DEBUG("x min %d, max %d\n", absinfo.minimum, absinfo.maximum);
+
+	rc = ioctl(touch->fd, EVIOCGABS(ABS_Y), &absinfo);
+	if (rc < 0) {
+		perror("EVIOCGABS Y");
+		return rc;
+	}
+	DEBUG("y min %d, max %d\n", absinfo.minimum, absinfo.maximum);
+
+	return 0;
+}
+
+static twin_linux_touch_t *twin_linux_touch_probe(twin_screen_t *screen)
+{
+	twin_linux_touch_t *touch = NULL;
+	struct dirent *dirent;
+	DIR *dir;
+
+	dir = opendir("/dev/input");
+	if (dir == NULL) {
+		perror("opendir(/dev/input)");
+		return NULL;
+	}
+
+	while ((dirent = readdir(dir))) {
+		char dev_name[sizeof(dirent->d_name) + sizeof("/dev/input")];
+
+		if (strncmp(dirent->d_name, "event", 5))
+			continue;
+		snprintf(dev_name, sizeof(dev_name),
+			 "/dev/input/%s", dirent->d_name);
+
+		touch = twin_linux_touch_create(dev_name, screen);
+		if (touch)
+			break;
+	}
+	closedir(dir);
+
+	return touch;
+}
+
+twin_linux_touch_t *twin_linux_touch_create(const char *file, twin_screen_t *screen)
+{
+	twin_linux_touch_t *touch = NULL;
+	unsigned long bits[NBITS(KEY_MAX)];
+	int dev_fd;
+	int rc;
+
+	/* If file is unspecified, probe all event devices */
+	if (!file)
+		return twin_linux_touch_probe(screen);
+
+	dev_fd = open(file, O_RDONLY | O_NONBLOCK);
+	if (dev_fd < 0) {
+		perror("open");
+		return NULL;
+	}
+	DEBUG("Probing event device %s\n", file);
+	rc = ioctl(dev_fd, EVIOCGBIT(EV_KEY, KEY_MAX), &bits);
+	if (rc < 0) {
+		perror("key ioctl");
+		goto err_close;
+	}
+	if (!test_bit(BTN_TOUCH, bits))
+		goto err_close;
+
+	rc = ioctl(dev_fd, EVIOCGBIT(EV_ABS, KEY_MAX), &bits);
+	if (rc < 0) {
+		perror("EVIOCGBIT");
+		goto err_close;
+	}
+	if (!test_bit(ABS_X, bits) || !test_bit(ABS_Y, bits))
+		goto err_close;
+
+	touch = calloc(1, sizeof(twin_linux_touch_t));
+	if (!touch) {
+		rc = -ENOMEM;
+		goto err_close;
+	}
+	touch->fd = dev_fd;
+	touch->screen = screen;
+
+	if (get_abs_range(touch))
+		goto err_free;
+	touch->file = twin_set_file(twin_linux_touch_events, dev_fd, TWIN_READ, touch);
+	DEBUG("%s attached as touchscreen\n", file);
+	return touch;
+err_free:
+	free(touch);
+err_close:
+	close(dev_fd);
+	return NULL;
+}
+
+void twin_linux_touch_destroy(twin_linux_touch_t *touch)
+{
+	twin_clear_file(touch->file);
+	close(touch->fd);
+	free(touch);
+}
diff --git a/libtwin/twin_linux_touch.h b/libtwin/twin_linux_touch.h
new file mode 100644
index 0000000..8d052db
--- /dev/null
+++ b/libtwin/twin_linux_touch.h
@@ -0,0 +1,49 @@
+/*
+ * Linux touchscreen driver for Twin
+ *
+ * Copyright 2013 Tyler Hall <tylerwhall@gmail.com>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Twin Library; see the file COPYING.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef _TWIN_LINUX_TOUCH_H_
+#define _TWIN_LINUX_TOUCH_H_
+
+#include <libtwin/twin.h>
+
+typedef struct _twin_linux_touch {
+	twin_screen_t *screen;
+	twin_file_t *file;
+
+	int fd;
+	int x, y;
+	int min_x, max_x;
+	int min_y, max_y;
+	int btn_state;
+	int btn_pending;
+} twin_linux_touch_t;
+
+/**
+ * twin_linux_touch_create - create the linux touchscreen driver
+ * @file: device file to open. Pass NULL to use first available
+ */
+twin_linux_touch_t *twin_linux_touch_create(const char *file, twin_screen_t *screen);
+
+/**
+ * twin_linux_touch_destroy - destroy the linux touchscreen driver
+ */
+void twin_linux_touch_destroy(twin_linux_touch_t *touch);
+
+#endif /* _TWIN_LINUX_TOUCH_H_ */
diff --git a/twin_demos/ftwin.c b/twin_demos/ftwin.c
index d6a655c..3e0b4d6 100644
--- a/twin_demos/ftwin.c
+++ b/twin_demos/ftwin.c
@@ -39,6 +39,7 @@
 
 #include "twin_fbdev.h"
 #include "twin_linux_mouse.h"
+#include "twin_linux_touch.h"
 
 #define maybe_unused __attribute__((unused))
 
@@ -72,6 +73,7 @@
 	signal(SIGINT, sigint);
 
 	twin_linux_mouse_create(NULL, tf->screen);
+	twin_linux_touch_create(NULL, tf->screen);
 
 	cur = twin_load_X_cursor("ftwin.cursor", 0, &hx, &hy);
 	if (cur == NULL)