/* * 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 #include #include #include #include #include #include #include #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; iheight - 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); }