mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-12-11 13:21:10 +00:00
70aa970c32
It should have been this way all along, and it seems I thought they were when I did rua_gui.c as it already freed its resource block, which would have been a double free (oops). Fixes an invalid write when shutting down progs in qwaq-cmd (relevant change not committed).
927 lines
24 KiB
C
927 lines
24 KiB
C
/*
|
|
term-input.c
|
|
|
|
Input handling
|
|
|
|
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2020/03/21
|
|
|
|
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:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_SIGACTION // no sigaction, no window resize
|
|
#include <sys/ioctl.h>
|
|
#include <termios.h>
|
|
#endif
|
|
|
|
#include "QF/dstring.h"
|
|
#include "QF/hash.h"
|
|
#include "QF/input.h"
|
|
#include "QF/keys.h"
|
|
#include "QF/sys.h"
|
|
|
|
#include "QF/input/event.h"
|
|
|
|
#include "qfselect.h"
|
|
|
|
#include "ruamoko/qwaq/qwaq.h"
|
|
#include "ruamoko/qwaq/qwaq-input.h"
|
|
#include "ruamoko/qwaq/ui/event.h"
|
|
|
|
#define always_inline inline __attribute__((__always_inline__))
|
|
|
|
#define MOUSE_MOVES_ON "\033[?1003h"
|
|
#define MOUSE_MOVES_OFF "\033[?1003l"
|
|
#define SGR_ON "\033[?1006h"
|
|
#define SGR_OFF "\033[?1006l"
|
|
#define WHEEL_BUTTONS 0x78 // scroll up/down/left/right - always click
|
|
|
|
typedef enum qwaq_input_commands_e {
|
|
qwaq_cmd_send_connected_devices,
|
|
qwaq_cmd_get_device_info,
|
|
} qwaq_input_commands;
|
|
|
|
static const char *qwaq_input_command_names[]= {
|
|
"send_connected_devices",
|
|
"get_device_info",
|
|
};
|
|
|
|
typedef struct qwaq_key_s {
|
|
const char *sequence;
|
|
knum_t key;
|
|
unsigned shift;
|
|
} qwaq_key_t;
|
|
|
|
static qwaq_key_t default_keys[] = {
|
|
{ "\033OP", QFK_F1 },
|
|
{ "\033OQ", QFK_F2 },
|
|
{ "\033OR", QFK_F3 },
|
|
{ "\033OS", QFK_F4 },
|
|
{ "\033[15~", QFK_F5 },
|
|
{ "\033[17~", QFK_F6 },
|
|
{ "\033[18~", QFK_F7 },
|
|
{ "\033[19~", QFK_F8 },
|
|
{ "\033[20~", QFK_F9 },
|
|
{ "\033[21~", QFK_F10 },
|
|
{ "\033[23~", QFK_F11 },
|
|
{ "\033[24~", QFK_F12 },
|
|
// shift F1-F12
|
|
{ "\033[1;2P", QFK_F13 },
|
|
{ "\033[1;2Q", QFK_F14 },
|
|
{ "\033[1;2R", QFK_F15 },
|
|
{ "\033[1;2S", QFK_F16 },
|
|
{ "\033[15;2~", QFK_F17 },
|
|
{ "\033[17;2~", QFK_F18 },
|
|
{ "\033[18;2~", QFK_F19 },
|
|
{ "\033[19;2~", QFK_F20 },
|
|
{ "\033[20;2~", QFK_F21 },
|
|
{ "\033[21;2~", QFK_F22 },
|
|
{ "\033[23;2~", QFK_F23 },
|
|
{ "\033[24;2~", QFK_F24 },
|
|
// control F1-F12
|
|
{ "\033[1;5P", QFK_F25 },
|
|
{ "\033[1;5Q", QFK_F26 },
|
|
{ "\033[1;5R", QFK_F27 },
|
|
{ "\033[1;5S", QFK_F28 },
|
|
{ "\033[15;5~", QFK_F29 },
|
|
{ "\033[17;5~", QFK_F30 },
|
|
{ "\033[18;5~", QFK_F31 },
|
|
{ "\033[19;5~", QFK_F32 },
|
|
{ "\033[20;5~", QFK_F33 },
|
|
{ "\033[21;5~", QFK_F34 },
|
|
{ "\033[23;5~", QFK_F35 },
|
|
{ "\033[24;5~", QFK_F36 },
|
|
// shift control F1-F12
|
|
{ "\033[1;6P", QFK_F37 },
|
|
{ "\033[1;6Q", QFK_F38 },
|
|
{ "\033[1;6R", QFK_F39 },
|
|
{ "\033[1;6S", QFK_F40 },
|
|
{ "\033[15;6~", QFK_F41 },
|
|
{ "\033[17;6~", QFK_F42 },
|
|
{ "\033[18;6~", QFK_F43 },
|
|
{ "\033[19;6~", QFK_F44 },
|
|
{ "\033[20;6~", QFK_F45 },
|
|
{ "\033[21;6~", QFK_F46 },
|
|
{ "\033[23;6~", QFK_F47 },
|
|
{ "\033[24;6~", QFK_F48 },
|
|
|
|
{ "\033[2~", QFK_INSERT, 0 },
|
|
{ "\033[3~", QFK_DELETE, 0 },
|
|
{ "\033[H", QFK_HOME, 0 },
|
|
{ "\033[F", QFK_END, 0 },
|
|
{ "\033[5~", QFK_PAGEUP, 0 },
|
|
{ "\033[6~", QFK_PAGEDOWN, 0 },
|
|
{ "\033[A", QFK_UP, 0 },
|
|
{ "\033[B", QFK_DOWN, 0 },
|
|
{ "\033[C", QFK_RIGHT, 0 },
|
|
{ "\033[D", QFK_LEFT, 0 },
|
|
// shift
|
|
// there may be a setting to tell xterm to NOT act on shift ins/pgup/pgdn
|
|
{ "\033[2;2~", QFK_INSERT, 1 }, // xterm gobbles (and pastes)
|
|
{ "\033[3;2~", QFK_DELETE, 1 },
|
|
{ "\033[1;2H", QFK_HOME, 1 },
|
|
{ "\033[1;2F", QFK_END, 1 },
|
|
{ "\033[5;2~", QFK_PAGEUP, 1 }, // xterm gobbles (scrolls term)
|
|
{ "\033[6;2~", QFK_PAGEDOWN, 1 }, // xterm gobbles (scrolls term)
|
|
{ "\033[1;2A", QFK_UP, 1 },
|
|
{ "\033[1;2B", QFK_DOWN, 1 },
|
|
{ "\033[1;2C", QFK_RIGHT, 1 },
|
|
{ "\033[1;2D", QFK_LEFT, 1 },
|
|
{ "\033[Z", QFK_TAB, 1 },
|
|
// control
|
|
{ "\033[2;5~", QFK_INSERT, 4 },
|
|
{ "\033[3;5~", QFK_DELETE, 4 },
|
|
{ "\033[1;5H", QFK_HOME, 4 },
|
|
{ "\033[1;5F", QFK_END, 4 },
|
|
{ "\033[5;5~", QFK_PAGEUP, 4 },
|
|
{ "\033[6;5~", QFK_PAGEDOWN, 4 },
|
|
{ "\033[1;5A", QFK_UP, 4 },
|
|
{ "\033[1;5B", QFK_DOWN, 4 },
|
|
{ "\033[1;5C", QFK_RIGHT, 4 },
|
|
{ "\033[1;5D", QFK_LEFT, 4 },
|
|
// shift control
|
|
{ "\033[2;6~", QFK_INSERT, 5 },
|
|
{ "\033[3;6~", QFK_DELETE, 5 },
|
|
{ "\033[1;6H", QFK_HOME, 5 },
|
|
{ "\033[1;6F", QFK_END, 5 },
|
|
{ "\033[5;6~", QFK_PAGEUP, 5 },
|
|
{ "\033[6;6~", QFK_PAGEDOWN, 5 },
|
|
{ "\033[1;6A", QFK_UP, 5 },
|
|
{ "\033[1;6B", QFK_DOWN, 5 },
|
|
{ "\033[1;6C", QFK_RIGHT, 5 },
|
|
{ "\033[1;6D", QFK_LEFT, 5 },
|
|
};
|
|
|
|
#define CMD_SIZE(x) sizeof(x)/sizeof(x[0])
|
|
|
|
static int
|
|
qwaq_cmd_peek (qwaq_input_resources_t *res, int ahead)
|
|
{
|
|
return *RB_PEEK_DATA (res->commands.pipe, ahead);
|
|
}
|
|
#if 0
|
|
static dstring_t *
|
|
qwaq_cmd_string (qwaq_input_resources_t *res, int string_id)
|
|
{
|
|
return res->commands.strings + string_id;
|
|
}
|
|
#endif
|
|
static dstring_t *
|
|
qwaq_res_string (qwaq_input_resources_t *res, int string_id)
|
|
{
|
|
return res->results.strings + string_id;
|
|
}
|
|
|
|
#ifdef HAVE_SIGACTION
|
|
static struct sigaction save_winch;
|
|
static sigset_t winch_mask;
|
|
static volatile sig_atomic_t winch_arrived;
|
|
|
|
static void
|
|
handle_winch (int sig)
|
|
{
|
|
winch_arrived = 1;
|
|
}
|
|
#endif
|
|
|
|
int
|
|
qwaq_add_event (qwaq_input_resources_t *res, qwaq_event_t *event)
|
|
{
|
|
struct timespec timeout;
|
|
int merged = 0;
|
|
int ret = 0;
|
|
|
|
// merge motion events
|
|
pthread_mutex_lock (&res->events.cond.mut);
|
|
unsigned last = RB_DATA_AVAILABLE (res->events.queue);
|
|
if (event->what == qe_mousemove && last > 1
|
|
&& RB_PEEK_DATA(res->events.queue, last - 1)->what == qe_mousemove) {
|
|
RB_POKE_DATA(res->events.queue, last - 1, *event);
|
|
merged = 1;
|
|
pthread_cond_broadcast (&res->events.cond.rcond);
|
|
}
|
|
pthread_mutex_unlock (&res->events.cond.mut);
|
|
if (merged) {
|
|
return 0;
|
|
}
|
|
|
|
pthread_mutex_lock (&res->events.cond.mut);
|
|
qwaq_init_timeout (&timeout, 5000 * (int64_t) 1000000);
|
|
while (RB_SPACE_AVAILABLE (res->events.queue) < 1 && ret == 0) {
|
|
ret = pthread_cond_timedwait (&res->events.cond.wcond,
|
|
&res->events.cond.mut, &timeout);
|
|
}
|
|
RB_WRITE_DATA (res->events.queue, event, 1);
|
|
pthread_cond_broadcast (&res->events.cond.rcond);
|
|
pthread_mutex_unlock (&res->events.cond.mut);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef HAVE_SIGACTION
|
|
static void
|
|
resize_event (qwaq_input_resources_t *res)
|
|
{
|
|
qwaq_event_t event = {};
|
|
struct winsize size;
|
|
if (ioctl (fileno (stdout), TIOCGWINSZ, &size) != 0) {
|
|
return;
|
|
}
|
|
event.what = qe_resize;
|
|
event.resize.width = size.ws_col;
|
|
event.resize.height = size.ws_row;
|
|
qwaq_add_event (res, &event);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
key_event (qwaq_input_resources_t *res, int key, unsigned shift)
|
|
{
|
|
qwaq_event_t event = {};
|
|
event.what = qe_keydown;
|
|
event.when = Sys_DoubleTime ();
|
|
event.key.code = key;
|
|
event.key.shift = shift;
|
|
qwaq_add_event (res, &event);
|
|
}
|
|
|
|
static void
|
|
mouse_event (qwaq_input_resources_t *res, int what, int x, int y)
|
|
{
|
|
qwaq_event_t event = {};
|
|
|
|
event.what = what;
|
|
event.when = Sys_DoubleTime ();
|
|
event.mouse.x = x;
|
|
event.mouse.y = y;
|
|
event.mouse.buttons = res->button_state;
|
|
|
|
if (event.what == qe_mousedown) {
|
|
if (event.when - res->lastClick.when <= 0.4
|
|
&& res->lastClick.mouse.buttons == event.mouse.buttons
|
|
&& res->lastClick.mouse.click < 3) {
|
|
event.mouse.click = res->lastClick.mouse.click + 1;
|
|
event.what = qe_mouseclick;
|
|
}
|
|
res->lastClick = event;
|
|
} else if (event.what == qe_mouseclick) {
|
|
// scroll button event, so always single click
|
|
event.mouse.click = 1;
|
|
}
|
|
qwaq_add_event (res, &event);
|
|
}
|
|
|
|
static void
|
|
parse_mouse (qwaq_input_resources_t *res, unsigned ctrl, int x, int y, int cmd)
|
|
{
|
|
int button = ctrl & 3;
|
|
int ext = (ctrl >> 6);
|
|
int what = qe_none;
|
|
|
|
if (ctrl & 0x20) {
|
|
// motion-only event
|
|
button = -1;
|
|
what = qe_mousemove;
|
|
} else {
|
|
// xterm doesn't send release events for buttons 4-7
|
|
res->button_state &= ~(0x78);
|
|
if (!ext && button == 3 && !cmd) {
|
|
res->button_state = 0; // unknown which button was released
|
|
button = -1;
|
|
what = qe_mouseup;
|
|
} else {
|
|
if (ext) {
|
|
button += 4 * ext - 1;
|
|
}
|
|
if (cmd == 'm') {
|
|
res->button_state &= ~(1 << button);
|
|
what = qe_mouseup;
|
|
} else {
|
|
res->button_state |= 1 << button;
|
|
if (res->button_state & WHEEL_BUTTONS) {
|
|
what = qe_mouseclick;
|
|
} else {
|
|
what = qe_mousedown;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
res->mouse_x = x;
|
|
res->mouse_y = y;
|
|
mouse_event (res, what, x, y);
|
|
}
|
|
|
|
static void
|
|
parse_x10 (qwaq_input_resources_t *res)
|
|
{
|
|
int x = (byte) res->escbuff.str[1] - '!'; // want 0-based
|
|
int y = (byte) res->escbuff.str[2] - '!'; // want 0-based
|
|
unsigned ctrl = (byte) res->escbuff.str[0] - ' ';
|
|
|
|
parse_mouse (res, ctrl, x, y, 0);
|
|
}
|
|
|
|
static void
|
|
parse_sgr (qwaq_input_resources_t *res, char cmd)
|
|
{
|
|
unsigned ctrl, x, y;
|
|
|
|
sscanf (res->escbuff.str, "%u;%u;%u", &ctrl, &x, &y);
|
|
// mouse coords are 1-based, but want 0-based
|
|
parse_mouse (res, ctrl, x - 1, y - 1, cmd);
|
|
}
|
|
|
|
static void
|
|
parse_key (qwaq_input_resources_t *res)
|
|
{
|
|
qwaq_key_t *key = Hash_Find (res->key_sequences, res->escbuff.str);
|
|
|
|
//Sys_Printf ("parse_key: %s\n", res->escbuff.str + 1);
|
|
if (key) {
|
|
//Sys_Printf (" %d %03x %s\n", key->key, key->shift,
|
|
// Key_KeynumToString (key->key));
|
|
key_event (res, key->key, key->shift);
|
|
}
|
|
}
|
|
|
|
static void process_char (qwaq_input_resources_t *res, char ch)
|
|
{
|
|
if (ch == 0x1b) {
|
|
// always reset if escape is seen, may be a desync
|
|
res->escstate = esc_escape;
|
|
} else {
|
|
switch (res->escstate) {
|
|
case esc_ground:
|
|
key_event (res, (byte) ch, 0); // shift state unknown
|
|
break;
|
|
case esc_escape:
|
|
if (ch == '[') {
|
|
res->escstate = esc_csi;
|
|
} else if (ch == 'O') {
|
|
// will wind up accepting P;P... but meh
|
|
res->escstate = esc_key;
|
|
dstring_clear (&res->escbuff);
|
|
// start the buffer with what got us here: eases key lookup
|
|
dstring_append (&res->escbuff, "\033O", 2);
|
|
} else {
|
|
res->escstate = esc_ground;
|
|
}
|
|
break;
|
|
case esc_csi:
|
|
if (ch == 'M') {
|
|
res->escstate = esc_mouse;
|
|
dstring_clear (&res->escbuff);
|
|
} else if (ch == '<') {
|
|
res->escstate = esc_sgr;
|
|
dstring_clear (&res->escbuff);
|
|
} else if (ch >= '0' && ch < 127) {
|
|
res->escstate = esc_key;
|
|
dstring_clear (&res->escbuff);
|
|
// start the buffer with what got us here: eases key lookup
|
|
dstring_append (&res->escbuff, "\033[", 2);
|
|
// the csi code might be short (eg, \e[H for home) so
|
|
// need to check for end of string right away
|
|
goto esc_key_jump;
|
|
} else {
|
|
res->escstate = esc_ground;
|
|
}
|
|
break;
|
|
case esc_mouse:
|
|
dstring_append (&res->escbuff, &ch, 1);
|
|
if (res->escbuff.size == 3) {
|
|
parse_x10 (res);
|
|
res->escstate = esc_ground;
|
|
}
|
|
break;
|
|
case esc_sgr:
|
|
if (isdigit (ch) || ch == ';') {
|
|
dstring_append (&res->escbuff, &ch, 1);
|
|
} else {
|
|
if (ch == 'm' || ch == 'M') {
|
|
// terminate the string
|
|
dstring_append (&res->escbuff, "", 1);
|
|
parse_sgr (res, ch);
|
|
}
|
|
res->escstate = esc_ground;
|
|
}
|
|
break;
|
|
case esc_key:
|
|
esc_key_jump:
|
|
dstring_append (&res->escbuff, &ch, 1);
|
|
if (!isdigit (ch) && ch != ';') {
|
|
// terminate the string
|
|
dstring_append (&res->escbuff, "", 1);
|
|
// parse_key will sort out whether it was a valid sequence
|
|
parse_key (res);
|
|
res->escstate = esc_ground;
|
|
}
|
|
break;
|
|
}
|
|
//printf("res->escstate %d\n", res->escstate);
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
key_sequence_getkey (const void *_seq, void *unused)
|
|
{
|
|
__auto_type seq = (const qwaq_key_t *) _seq;
|
|
return seq->sequence;
|
|
}
|
|
|
|
static void
|
|
term_init (void *_res)
|
|
{
|
|
}
|
|
|
|
static void
|
|
term_shutdown (void *_res)
|
|
{
|
|
}
|
|
|
|
static void
|
|
term_set_device_event_data (void *device, void *event_data, void *data)
|
|
{
|
|
}
|
|
|
|
static void *
|
|
term_get_device_event_data (void *device, void *data)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#define FD 0
|
|
static void
|
|
term_add_select (qf_fd_set *fdset, int *maxfd, void *_res)
|
|
{
|
|
QF_FD_SET (FD, fdset);
|
|
if (*maxfd < FD) {
|
|
*maxfd = FD;
|
|
}
|
|
}
|
|
|
|
static void
|
|
term_check_select (qf_fd_set *fdset, void *_res)
|
|
{
|
|
qwaq_input_resources_t *res = _res;
|
|
char buf[256];
|
|
int len;
|
|
#ifdef HAVE_SIGACTION
|
|
sigset_t save_set;
|
|
int saw_winch;
|
|
|
|
pthread_sigmask (SIG_BLOCK, &winch_mask, &save_set);
|
|
saw_winch = winch_arrived;
|
|
winch_arrived = 0;
|
|
pthread_sigmask (SIG_SETMASK, &save_set, 0);
|
|
if (saw_winch) {
|
|
resize_event (res);
|
|
}
|
|
#endif
|
|
if (QF_FD_ISSET (FD, fdset)) {
|
|
len = read(0, buf, sizeof (buf));
|
|
for (int i = 0; i < len; i++) {
|
|
process_char (res, buf[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static in_driver_t term_driver = {
|
|
.init = term_init,
|
|
.shutdown = term_shutdown,
|
|
.set_device_event_data = term_set_device_event_data,
|
|
.get_device_event_data = term_get_device_event_data,
|
|
.add_select = term_add_select,
|
|
.check_select = term_check_select,
|
|
};
|
|
static int term_driver_handle;
|
|
|
|
static void __attribute__((constructor))
|
|
term_register_driver (void)
|
|
{
|
|
term_driver_handle = IN_RegisterDriver (&term_driver, 0);
|
|
}
|
|
|
|
static int
|
|
qwaq_input_event_handler (const IE_event_t *ie_event, void *_res)
|
|
{
|
|
qwaq_input_resources_t *res = _res;
|
|
qwaq_event_t event = {};
|
|
|
|
event.when = ie_event->when * 1e-6 + Sys_DoubleTimeBase ();
|
|
|
|
switch (ie_event->type) {
|
|
case ie_event_count:
|
|
case ie_none:
|
|
case ie_gain_focus:
|
|
case ie_lose_focus:
|
|
case ie_app_gain_focus:
|
|
case ie_app_lose_focus:
|
|
case ie_app_window:
|
|
return 0;
|
|
case ie_add_device:
|
|
event.what = qe_dev_add;
|
|
event.message.int_val = ie_event->device.devid;
|
|
break;
|
|
case ie_remove_device:
|
|
event.what = qe_dev_rem;
|
|
event.message.int_val = ie_event->device.devid;
|
|
break;
|
|
case ie_mouse:
|
|
case ie_key:
|
|
// these are handled directly for now
|
|
break;
|
|
case ie_axis:
|
|
event.what = qe_axis;
|
|
event.message.ivector_val[0] = ie_event->axis.devid;
|
|
event.message.ivector_val[1] = ie_event->axis.axis;
|
|
event.message.ivector_val[2] = ie_event->axis.value;
|
|
break;
|
|
case ie_button:
|
|
event.what = qe_button;
|
|
event.message.ivector_val[0] = ie_event->button.devid;
|
|
event.message.ivector_val[1] = ie_event->button.button;
|
|
event.message.ivector_val[2] = ie_event->button.state;
|
|
break;
|
|
}
|
|
qwaq_add_event (res, &event);
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
qwaq_input_init (qwaq_input_resources_t *res)
|
|
{
|
|
res->input_event_handler = IE_Add_Handler (qwaq_input_event_handler, res);
|
|
IE_Set_Focus (res->input_event_handler);
|
|
IN_DriverData (term_driver_handle, res);
|
|
|
|
if (res->key_sequences) {
|
|
Hash_FlushTable (res->key_sequences);
|
|
} else {
|
|
res->key_sequences = Hash_NewTable (127, key_sequence_getkey, 0, 0,
|
|
res->pr->hashctx);
|
|
}
|
|
for (size_t i = 0; i < sizeof (default_keys) / sizeof (default_keys[0]);
|
|
i++) {
|
|
Hash_Add (res->key_sequences, &default_keys[i]);
|
|
}
|
|
|
|
#ifdef HAVE_SIGACTION
|
|
sigemptyset (&winch_mask);
|
|
sigaddset (&winch_mask, SIGWINCH);
|
|
struct sigaction action = {};
|
|
action.sa_handler = handle_winch;
|
|
sigaction (SIGWINCH, &action, &save_winch);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
qwaq_input_enable_mouse (void)
|
|
{
|
|
// ncurses takes care of input mode for us, so need only tell xterm
|
|
// what we need
|
|
(void) !write(1, MOUSE_MOVES_ON, sizeof (MOUSE_MOVES_ON) - 1);
|
|
(void) !write(1, SGR_ON, sizeof (SGR_ON) - 1);
|
|
}
|
|
|
|
void
|
|
qwaq_input_disable_mouse (void)
|
|
{
|
|
// ncurses takes care of input mode for us, so need only tell xterm
|
|
// what we need
|
|
(void) !write(1, SGR_OFF, sizeof (SGR_OFF) - 1);
|
|
(void) !write(1, MOUSE_MOVES_OFF, sizeof (MOUSE_MOVES_OFF) - 1);
|
|
}
|
|
|
|
static void
|
|
qwaq_process_input (qwaq_input_resources_t *res)
|
|
{
|
|
IN_ProcessEvents ();
|
|
}
|
|
|
|
static void
|
|
qwaq_input_shutdown (qwaq_input_resources_t *res)
|
|
{
|
|
IE_Remove_Handler (res->input_event_handler);
|
|
IN_DriverData (term_driver_handle, 0);
|
|
|
|
#ifdef HAVE_SIGACTION
|
|
sigaction (SIGWINCH, &save_winch, 0);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
bi_send_connected_devices (progs_t *pr, void *_res)
|
|
{
|
|
qwaq_input_resources_t *res = _res;
|
|
|
|
int command[] = {
|
|
qwaq_cmd_send_connected_devices, 0,
|
|
};
|
|
|
|
command[1] = CMD_SIZE(command);
|
|
qwaq_pipe_submit (&res->commands, command, command[1]);
|
|
}
|
|
|
|
static void
|
|
bi_get_device_info (progs_t *pr, void *_res)
|
|
{
|
|
qwaq_input_resources_t *res = _res;
|
|
int devid = P_INT (pr, 0);
|
|
|
|
int command[] = {
|
|
qwaq_cmd_get_device_info, 0,
|
|
devid,
|
|
};
|
|
|
|
command[1] = CMD_SIZE(command);
|
|
qwaq_pipe_submit (&res->commands, command, command[1]);
|
|
|
|
int cmd_result[5];
|
|
qwaq_pipe_receive (&res->results, cmd_result, qwaq_cmd_get_device_info,
|
|
CMD_SIZE (cmd_result));
|
|
int name_id = cmd_result[1];
|
|
int id_id = cmd_result[2];
|
|
int axis_id = cmd_result[3];
|
|
int button_id = cmd_result[4];
|
|
|
|
dstring_t *name_string = qwaq_res_string (res, name_id);
|
|
dstring_t *id_string = qwaq_res_string (res, id_id);
|
|
dstring_t *axis_buffer = qwaq_res_string (res, axis_id);
|
|
dstring_t *button_buffer = qwaq_res_string (res, button_id);
|
|
|
|
qwaq_devinfo_t *devinfo = PR_Zone_Malloc (pr, sizeof (qwaq_devinfo_t));
|
|
in_axisinfo_t *axes = PR_Zone_Malloc (pr, axis_buffer->size);
|
|
in_buttoninfo_t *buttons = PR_Zone_Malloc (pr, button_buffer->size);
|
|
|
|
if (axes) {
|
|
memcpy (axes, axis_buffer->str, axis_buffer->size);
|
|
}
|
|
if (buttons) {
|
|
memcpy (buttons, button_buffer->str, button_buffer->size);
|
|
}
|
|
|
|
devinfo->name = PR_SetDynamicString (pr, name_string->str);
|
|
devinfo->id = PR_SetDynamicString (pr, id_string->str);
|
|
devinfo->numaxes = axis_buffer->size / sizeof (in_axisinfo_t);
|
|
devinfo->axes = PR_SetPointer (pr, axes);
|
|
devinfo->numbuttons = button_buffer->size / sizeof (in_buttoninfo_t);
|
|
devinfo->buttons = PR_SetPointer (pr, buttons);
|
|
|
|
qwaq_pipe_release_string (&res->results, name_id);
|
|
qwaq_pipe_release_string (&res->results, id_id);
|
|
qwaq_pipe_release_string (&res->results, axis_id);
|
|
qwaq_pipe_release_string (&res->results, button_id);
|
|
|
|
RETURN_POINTER (pr, devinfo);
|
|
}
|
|
|
|
static int
|
|
get_event (qwaq_input_resources_t *res, qwaq_event_t *event)
|
|
{
|
|
struct timespec timeout;
|
|
int ret = 0;
|
|
int was_event = 0;
|
|
|
|
pthread_mutex_lock (&res->events.cond.mut);
|
|
qwaq_init_timeout (&timeout, 20 * 1000000);
|
|
while (RB_DATA_AVAILABLE (res->events.queue) < 1 && ret == 0) {
|
|
ret = pthread_cond_timedwait (&res->events.cond.rcond,
|
|
&res->events.cond.mut, &timeout);
|
|
}
|
|
if (event) {
|
|
if (ret == 0) {
|
|
RB_READ_DATA (res->events.queue, event, 1);
|
|
was_event = 1;
|
|
} else if (res->button_state) {
|
|
event->what = qe_mouseauto;
|
|
event->when = Sys_DoubleTime ();
|
|
event->mouse.buttons = res->button_state;
|
|
event->mouse.x = res->mouse_x;
|
|
event->mouse.y = res->mouse_y;
|
|
} else {
|
|
event->what = qe_none;
|
|
}
|
|
}
|
|
pthread_cond_broadcast (&res->events.cond.wcond);
|
|
pthread_mutex_unlock (&res->events.cond.mut);
|
|
return was_event;
|
|
}
|
|
|
|
static void
|
|
bi_get_event (progs_t *pr, void *_res)
|
|
{
|
|
qwaq_input_resources_t *res = _res;
|
|
qwaq_event_t *event = &P_STRUCT (pr, qwaq_event_t, 0);
|
|
|
|
R_INT (pr) = get_event (res, event);
|
|
}
|
|
|
|
static void
|
|
cmd_send_connected_devices (qwaq_input_resources_t *res)
|
|
{
|
|
IN_SendConnectedDevices ();
|
|
}
|
|
|
|
static void
|
|
cmd_get_device_info (qwaq_input_resources_t *res)
|
|
{
|
|
int devid = qwaq_cmd_peek (res, 2);
|
|
int name_id = qwaq_pipe_acquire_string (&res->results);
|
|
int id_id = qwaq_pipe_acquire_string (&res->results);
|
|
int axis_id = qwaq_pipe_acquire_string (&res->results);
|
|
int button_id = qwaq_pipe_acquire_string (&res->results);
|
|
|
|
dstring_t *name_string = qwaq_res_string (res, name_id);
|
|
dstring_t *id_string = qwaq_res_string (res, id_id);
|
|
dstring_t *axis_buffer = qwaq_res_string (res, axis_id);
|
|
dstring_t *button_buffer = qwaq_res_string (res, button_id);
|
|
|
|
dstring_copystr (name_string, IN_GetDeviceName (devid));
|
|
dstring_copystr (id_string, IN_GetDeviceId (devid));
|
|
|
|
int num;
|
|
IN_AxisInfo (devid, 0, &num);
|
|
axis_buffer->size = num * sizeof (in_axisinfo_t);
|
|
dstring_adjust (axis_buffer);
|
|
IN_AxisInfo (devid, (in_axisinfo_t *) axis_buffer->str, &num);
|
|
|
|
IN_ButtonInfo (devid, 0, &num);
|
|
button_buffer->size = num * sizeof (in_buttoninfo_t);
|
|
dstring_adjust (button_buffer);
|
|
IN_ButtonInfo (devid, (in_buttoninfo_t *) button_buffer->str, &num);
|
|
|
|
int cmd_result[] = {
|
|
qwaq_cmd_get_device_info,
|
|
name_id, id_id, axis_id, button_id
|
|
};
|
|
qwaq_pipe_submit (&res->results, cmd_result, CMD_SIZE (cmd_result));
|
|
}
|
|
|
|
static void
|
|
dump_command (qwaq_input_resources_t *res, int len)
|
|
{
|
|
if (0) {
|
|
qwaq_input_commands cmd = qwaq_cmd_peek (res, 0);
|
|
Sys_Printf ("%s[%d]", qwaq_input_command_names[cmd], len);
|
|
for (int i = 2; i < len; i++) {
|
|
Sys_Printf (" %d", qwaq_cmd_peek (res, i));
|
|
}
|
|
Sys_Printf ("\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_commands (qwaq_input_resources_t *res)
|
|
{
|
|
struct timespec timeout;
|
|
int avail;
|
|
int len;
|
|
int ret = 0;
|
|
|
|
pthread_mutex_lock (&res->commands.pipe_cond.mut);
|
|
qwaq_init_timeout (&timeout, 20 * 1000000);
|
|
while (RB_DATA_AVAILABLE (res->commands.pipe) < 2 && ret == 0) {
|
|
ret = pthread_cond_timedwait (&res->commands.pipe_cond.rcond,
|
|
&res->commands.pipe_cond.mut, &timeout);
|
|
}
|
|
// The mutex is unlocked at the top of the loop and locked again
|
|
// (for broadcast) at the bottom, then finally unlocked after the loop.
|
|
// It should be only the data availability check that's not threadsafe
|
|
// as the mutex is not released until after the data has been written to
|
|
// the buffer.
|
|
while ((avail = RB_DATA_AVAILABLE (res->commands.pipe)) >= 2
|
|
&& avail >= (len = qwaq_cmd_peek (res, 1))) {
|
|
pthread_mutex_unlock (&res->commands.pipe_cond.mut);
|
|
dump_command (res, len);
|
|
qwaq_input_commands cmd = qwaq_cmd_peek (res, 0);
|
|
switch (cmd) {
|
|
case qwaq_cmd_send_connected_devices:
|
|
cmd_send_connected_devices (res);
|
|
break;
|
|
case qwaq_cmd_get_device_info:
|
|
cmd_get_device_info (res);
|
|
break;
|
|
}
|
|
pthread_mutex_lock (&res->commands.pipe_cond.mut);
|
|
RB_RELEASE (res->commands.pipe, len);
|
|
pthread_cond_broadcast (&res->commands.pipe_cond.wcond);
|
|
// unlocked at top of top if there's more data, otherwise just after
|
|
// the loop
|
|
}
|
|
pthread_mutex_unlock (&res->commands.pipe_cond.mut);
|
|
}
|
|
|
|
static void *
|
|
qwaq_input_thread (qwaq_thread_t *thread)
|
|
{
|
|
qwaq_input_resources_t *res = thread->data;
|
|
|
|
while (1) {
|
|
qwaq_process_input (res);
|
|
process_commands (res);
|
|
}
|
|
thread->return_code = 0;
|
|
return thread;
|
|
}
|
|
|
|
static void
|
|
bi_init_input (progs_t *pr, void *_res)
|
|
{
|
|
qwaq_input_resources_t *res = _res;
|
|
qwaq_input_init (res);
|
|
res->initialized = 1;
|
|
create_thread (qwaq_input_thread, res);
|
|
|
|
IE_event_t event = {
|
|
.type = ie_app_gain_focus,
|
|
.when = Sys_LongTime (),
|
|
};
|
|
IE_Send_Event (&event);
|
|
}
|
|
|
|
#define bi(x,n,np,params...) {#x, bi_##x, n, np, {params}}
|
|
#define p(type) PR_PARAM(type)
|
|
static builtin_t builtins[] = {
|
|
bi(send_connected_devices, -1, 0),
|
|
bi(get_device_info, -1, 1, p(int)),
|
|
bi(get_event, -1, 1, p(ptr)),
|
|
bi(init_input, -1, 0),
|
|
|
|
{0}
|
|
};
|
|
|
|
static void
|
|
bi_input_clear (progs_t *pr, void *_res)
|
|
{
|
|
__auto_type res = (qwaq_input_resources_t *) _res;
|
|
|
|
if (res->initialized) {
|
|
qwaq_input_shutdown (res);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bi_input_destroy (progs_t *pr, void *_res)
|
|
{
|
|
free (_res);
|
|
}
|
|
|
|
static void
|
|
bi_input_shutdown (void *_pr)
|
|
{
|
|
__auto_type pr = (progs_t *) _pr;
|
|
qwaq_input_resources_t *res = PR_Resources_Find (pr, "input");
|
|
|
|
qwaq_input_shutdown (res);
|
|
}
|
|
|
|
void
|
|
BI_TermInput_Init (progs_t *pr)
|
|
{
|
|
qwaq_input_resources_t *res = calloc (sizeof (*res), 1);
|
|
res->pr = pr;
|
|
|
|
qwaq_init_pipe (&res->commands);
|
|
qwaq_init_pipe (&res->results);
|
|
|
|
res->escbuff.mem = &dstring_default_mem;
|
|
// ensure the backing buffer exists
|
|
dstring_clearstr (&res->escbuff);
|
|
|
|
res->input_event_handler = -1;
|
|
|
|
qwaq_init_cond (&res->events.cond);
|
|
|
|
PR_Resources_Register (pr, "input", res, bi_input_clear, bi_input_destroy);
|
|
PR_RegisterBuiltins (pr, builtins, res);
|
|
Sys_RegisterShutdown (bi_input_shutdown, pr);
|
|
}
|