upiwin/src/sysinput.c

256 lines
7.3 KiB
C

/*
* UPIWIN - Micro Pi Windowing Framework Kernel
* Copyright (C) 2019 Amy Bowersox/Erbosoft Metaverse Design Solutions
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*-------------------------------------------------------------------------
*/
#include <stddef.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/input.h>
#include "scode.h"
#include "config.h"
#include "log.h"
#include "msg_queue.h"
#include "gpio.h"
#include "fbinit.h"
#include "time_func.h"
#define INPUT_EVENT_BATCH 16 /* number of events to retrieve from touchscreen at once */
PMSG_QUEUE Sys_Queue = NULL; /* system message queue */
INT32 Sys_Exit_Code = -1; /* system exit code, set on WM_QUIT */
static int ts_fd = 0; /* file descriptor for touchscreen */
static pthread_t ithread; /* input thread handle */
static volatile sig_atomic_t running = 1; /* "running" flag for input thread */
static pthread_mutex_t wait_mutex = PTHREAD_MUTEX_INITIALIZER; /* mutex for waiting on input */
static pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER; /* condition for waiting on input */
/* Local data for poll_buttons() */
static UINT32 last_bstate = 0; /* previous button state */
static TIMESTAMP button_event_ok[GPIO_BUTTON_COUNT]; /* timestamps to debounce events */
static TIMESTAMP button_down_time[GPIO_BUTTON_COUNT]; /* timestamps for click detection */
/* Local data for poll_touchscreen() */
static UINT_PTR touch_x = 0; /* X coordinate to send with next message */
static UINT_PTR touch_y = 0; /* Y coordinate to send with next message */
static UINT32 touch_nextmsg = WM_TOUCHMOVE; /* identifier of next message to send */
static UINT_PTR touch_down_x, touch_down_y; /* location of the touch-down event for click detection */
static TIMESTAMP touch_down_time; /* timestamp for click detection */
static BOOL poll_buttons(void)
{
BOOL posted = FALSE;
UINT32 st, down, up, mask;
UINT_PTR attr;
TIMESTAMP now;
/* poll hardware buttons */
st = Gpio_read_buttons();
if (st != last_bstate)
{
now = Time_since_start();
up = last_bstate & ~st;
down = st & ~last_bstate;
for (attr = 1, mask = GRB_STATE_BUTTON1; attr <= GPIO_BUTTON_COUNT; attr++, mask <<= 1)
{
if (now < button_event_ok[attr - 1])
continue; /* this is a "contact bounce" event, don't bother */
if (up & mask)
{
/* reset contact bounce timer - only seems to happen after button releases */
button_event_ok[attr - 1] = now + Gconfig.button_debounce;
Mq_post1(Sys_Queue, 0, WM_HWBUTTONUP, attr);
if ((now - button_down_time[attr - 1]) <= Gconfig.click_time)
Mq_post1(Sys_Queue, 0, WM_HWBUTTONCLICK, attr);
posted = TRUE;
}
else if (down & mask)
{
Mq_post1(Sys_Queue, 0, WM_HWBUTTONDOWN, attr);
button_down_time[attr - 1] = now;
posted = TRUE;
}
}
last_bstate = st;
}
return posted;
}
static BOOL poll_touchscreen(void)
{
BOOL posted = FALSE;
int nb, nev, xerrno, i;
TIMESTAMP now;
struct input_event buffer[INPUT_EVENT_BATCH];
nb = read(ts_fd, buffer, INPUT_EVENT_BATCH * sizeof(struct input_event));
if (nb == -1)
{
xerrno = errno;
if ((xerrno != EAGAIN) && (xerrno != EWOULDBLOCK))
Log(LERROR, "Error reading from touchscreen device (%d)", xerrno);
return FALSE;
}
else if (nb == 0)
{
Log(LERROR, "Unexpected end of file reading from touchscreen device");
return FALSE;
}
nev = nb / sizeof(struct input_event);
xerrno = nev * sizeof(struct input_event);
if (nb > xerrno)
Log(LERROR, "read %d bytes from touchscreen but we can only use %d", nb, xerrno);
for (i=0; i<nev; i++)
{
switch (buffer[i].type)
{
case EV_SYN:
if (buffer[i].code == SYN_REPORT)
{
now = Time_since_start();
Mq_post2(Sys_Queue, 0, touch_nextmsg, touch_x, touch_y);
if (touch_nextmsg == WM_TOUCHDOWN)
{
touch_down_x = touch_x;
touch_down_y = touch_y;
touch_down_time = now;
}
else if (touch_nextmsg == WM_TOUCHUP)
{
if ( ((now - touch_down_time) <= Gconfig.click_time)
&& (ABS((INT32)touch_x - (INT32)touch_down_x) <= Gconfig.click_radius)
&& (ABS((INT32)touch_y - (INT32)touch_down_y) <= Gconfig.click_radius))
Mq_post2(Sys_Queue, 0, WM_TOUCHCLICK, touch_x, touch_y);
}
touch_nextmsg = WM_TOUCHMOVE;
posted = TRUE;
}
break;
case EV_ABS:
/* Note that the touchscreen driver assumes the screen is "vertical," so swap the x and y axes */
/* Also it thinks origin is lower left with up = +y */
if (buffer[i].code == ABS_X)
touch_y = Fb_Info->height - buffer[i].value;
else if (buffer[i].code == ABS_Y)
touch_x = buffer[i].value;
break;
case EV_KEY:
if (buffer[i].code == BTN_TOUCH)
touch_nextmsg = (buffer[i].value ? WM_TOUCHDOWN : WM_TOUCHUP);
break;
default:
break;
}
}
return posted;
}
static void *input_thread(void *arg)
{
BOOL gotinput;
/* clear all state at startup */
last_bstate = 0;
memset(button_event_ok, 0, GPIO_BUTTON_COUNT * sizeof(TIMESTAMP));
touch_x = touch_y = 0;
touch_nextmsg = WM_TOUCHMOVE;
while (running)
{
gotinput = poll_buttons();
gotinput = poll_touchscreen() || gotinput;
if (gotinput)
{
pthread_mutex_lock(&wait_mutex);
pthread_cond_signal(&wait_cond);
pthread_mutex_unlock(&wait_mutex);
}
}
return NULL;
}
static void do_disable_input(void)
{
running = 0;
pthread_join(ithread, NULL);
close(ts_fd);
ts_fd = -1;
Mq_destroy(Sys_Queue);
Sys_Queue = NULL;
}
HRESULT Sys_enable_input(void)
{
HRESULT rc = S_OK;
int threadrc;
Sys_Queue = Mq_alloc(Gconfig.sys_mq_length);
if (!Sys_Queue)
{
Log(LFATAL, "Unable to allocate system message queue.");
return E_OUTOFMEMORY;
}
ts_fd = open(Gconfig.touchscreen_device, O_RDONLY|O_NONBLOCK);
if (ts_fd < 0)
{
rc = ERRNO_AS_SCODE;
Log(LFATAL, "Unable to open touchscreen device %s (%08X).", Gconfig.touchscreen_device, rc);
goto error_0;
}
running = 1;
threadrc = pthread_create(&ithread, NULL, input_thread, NULL);
if (threadrc != 0)
{
rc = SCODE_FROM_ERRNO(threadrc);
Log(LFATAL, "Unable to start system input thread (%08X).", rc);
goto error_1;
}
rc = Config_exitfunc(do_disable_input);
if (FAILED(rc))
do_disable_input();
return rc;
error_1:
close(ts_fd);
ts_fd = -1;
error_0:
Mq_destroy(Sys_Queue);
return rc;
}
void Sys_wait_for_input(void)
{
pthread_mutex_lock(&wait_mutex);
while (Mq_is_empty(Sys_Queue))
pthread_cond_wait(&wait_cond, &wait_mutex);
pthread_mutex_unlock(&wait_mutex);
}