quakeforge/ruamoko/qwaq/builtins/input.c
Bill Currie 6d5ffa9f8e [build] Move to non-recursive make
There's still some cleanup to do, but everything seems to be working
nicely: `make -j` works, `make distcheck` passes. There is probably
plenty of bitrot in the package directories (RPM, debian), though.

The vc project files have been removed since those versions are way out
of date and quakeforge is pretty much dependent on gcc now anyway.

Most of the old Makefile.am files  are now Makemodule.am.  This should
allow for new Makefile.am files that allow local building (to be added
on an as-needed bases).  The current remaining Makefile.am files are for
standalone sub-projects.a

The installable bins are currently built in the top-level build
directory. This may change if the clutter gets to be too much.

While this does make a noticeable difference in build times, the main
reason for the switch was to take care of the growing dependency issues:
now it's possible to build tools for code generation (eg, using qfcc and
ruamoko programs for code-gen).
2020-06-25 11:35:37 +09:00

473 lines
12 KiB
C

/*
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 <sys/ioctl.h>
#include <ctype.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/keys.h"
#include "QF/sys.h"
#include "ruamoko/qwaq/qwaq.h"
#include "ruamoko/qwaq/ui/event.h"
#include "ruamoko/qwaq/ui/curses.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 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 },
};
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;
}
int
qwaq_add_event (qwaq_resources_t *res, qwaq_event_t *event)
{
struct timespec timeout;
int merged = 0;
int ret = 0;
// merge motion events
pthread_mutex_lock (&res->event_cond.mut);
unsigned last = RB_DATA_AVAILABLE (res->event_queue);
if (event->what == qe_mousemove && last > 1
&& RB_PEEK_DATA(res->event_queue, last - 1).what == qe_mousemove) {
RB_POKE_DATA(res->event_queue, last - 1, *event);
merged = 1;
pthread_cond_broadcast (&res->event_cond.rcond);
}
pthread_mutex_unlock (&res->event_cond.mut);
if (merged) {
return 0;
}
pthread_mutex_lock (&res->event_cond.mut);
qwaq_init_timeout (&timeout, 5000 * 1000000L);
while (RB_SPACE_AVAILABLE (res->event_queue) < 1 && ret == 0) {
ret = pthread_cond_timedwait (&res->event_cond.wcond,
&res->event_cond.mut, &timeout);
}
RB_WRITE_DATA (res->event_queue, event, 1);
pthread_cond_broadcast (&res->event_cond.rcond);
pthread_mutex_unlock (&res->event_cond.mut);
return ret;
}
static void
resize_event (qwaq_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);
}
static void
key_event (qwaq_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_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_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_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_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_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_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 hear: 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 hear: 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;
}
void qwaq_input_init (qwaq_resources_t *res)
{
if (res->key_sequences) {
Hash_FlushTable (res->key_sequences);
} else {
res->key_sequences = Hash_NewTable (127, key_sequence_getkey, 0, 0,
res->pr->hashlink_freelist);
}
for (size_t i = 0; i < sizeof (default_keys) / sizeof (default_keys[0]);
i++) {
Hash_Add (res->key_sequences, &default_keys[i]);
}
sigemptyset (&winch_mask);
sigaddset (&winch_mask, SIGWINCH);
struct sigaction action = {};
action.sa_handler = handle_winch;
sigaction (SIGWINCH, &action, &save_winch);
// ncurses takes care of input mode for us, so need only tell xterm
// what we need
write(1, MOUSE_MOVES_ON, sizeof (MOUSE_MOVES_ON) - 1);
write(1, SGR_ON, sizeof (SGR_ON) - 1);
}
void qwaq_input_shutdown (qwaq_resources_t *res)
{
// ncurses takes care of input mode for us, so need only tell xterm
// what we need
write(1, SGR_OFF, sizeof (SGR_OFF) - 1);
write(1, MOUSE_MOVES_OFF, sizeof (MOUSE_MOVES_OFF) - 1);
sigaction (SIGWINCH, &save_winch, 0);
}
void qwaq_process_input (qwaq_resources_t *res)
{
char buf[256];
int len;
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);
}
while (Sys_CheckInput (1, -1)) {
len = read(0, buf, sizeof (buf));
for (int i = 0; i < len; i++) {
process_char (res, buf[i]);
}
}
}