blob: 56d75955c9d171d37ab004b3904df01741ea2144 [file] [log] [blame]
/*
* 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);
}