From d3527496668029ad7f194d0ce25ee16ef4f0d8f7 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Sun, 22 Mar 2020 01:00:05 +0900 Subject: [PATCH] [qwaq] Bypass ncurses for input handling For now, only 1003 mouse mode is supported, and function keys are not yet supported, but the mouse is much more reliable: no lost events. --- ruamoko/qwaq/Makefile.am | 2 +- ruamoko/qwaq/event.h | 11 +- ruamoko/qwaq/qwaq-curses.c | 228 +++---------------------------------- ruamoko/qwaq/qwaq-curses.h | 65 +++++++++++ ruamoko/qwaq/qwaq-input.c | 226 ++++++++++++++++++++++++++++++++++++ ruamoko/qwaq/qwaq.h | 1 + 6 files changed, 313 insertions(+), 220 deletions(-) create mode 100644 ruamoko/qwaq/qwaq-input.c diff --git a/ruamoko/qwaq/Makefile.am b/ruamoko/qwaq/Makefile.am index 683a0923c..df02f64ab 100644 --- a/ruamoko/qwaq/Makefile.am +++ b/ruamoko/qwaq/Makefile.am @@ -37,7 +37,7 @@ qwaq_app_dat_src= \ qwaq-window.r qwaq_curses_libs= -qwaq_curses_SOURCES=main.c qwaq-curses.c +qwaq_curses_SOURCES=main.c qwaq-curses.c qwaq-input.c qwaq_curses_LDADD= $(qwaq_curses_libs) $(QWAQ_LIBS) \ $(PANEL_LIBS) $(CURSES_LIBS) $(PTHREAD_LDFLAGS) $(DL_LIBS) qwaq_curses_LDFLAGS= diff --git a/ruamoko/qwaq/event.h b/ruamoko/qwaq/event.h index 56327c42d..f1d9e6fbb 100644 --- a/ruamoko/qwaq/event.h +++ b/ruamoko/qwaq/event.h @@ -9,14 +9,6 @@ typedef enum { qe_mouseauto = 0x0010, } qwaq_mouse_event; -typedef enum { - qe_mouse1 = 0x0001, - qe_mouse2 = 0x0002, - qe_mouse3 = 0x0004, - qe_mouse4 = 0x0008, - qe_mouse5 = 0x0010, -} qwaq_button; - typedef enum { qe_keydown = 0x0020, } qwaq_key_event; @@ -45,7 +37,7 @@ typedef enum { typedef struct qwaq_mevent_s { int x, y; - qwaq_button buttons; // current button state + int buttons; // current button state int click; } qwaq_mevent_t; @@ -55,6 +47,7 @@ typedef struct qwaq_message_s { typedef struct qwaq_event_s { int what; + double when; // NOTE: 1<<32 based union { int key; qwaq_mevent_t mouse; diff --git a/ruamoko/qwaq/qwaq-curses.c b/ruamoko/qwaq/qwaq-curses.c index 27306fc28..23f216353 100644 --- a/ruamoko/qwaq/qwaq-curses.c +++ b/ruamoko/qwaq/qwaq-curses.c @@ -47,19 +47,11 @@ #include "QF/sys.h" #include "qwaq.h" -#include "event.h" #include "qwaq-curses.h" #include "qwaq-rect.h" #include "qwaq-textcontext.h" #define always_inline inline __attribute__((__always_inline__)) -#define QUEUE_SIZE 16 -#define MOUSE_MOVES_ON "\033[?1003h" -#define MOUSE_MOVES_OFF "\033[?1003l" -#define SGR_ON "\033[?1006h" -#define SGR_OFF "\033[?1006l" -#define STRING_ID_QUEUE_SIZE 8 // must be > 1 -#define COMMAND_QUEUE_SIZE 1280 #define CMD_SIZE(x) sizeof(x)/sizeof(x[0]) typedef enum qwaq_commands_e { @@ -120,39 +112,6 @@ static const char *qwaq_command_names[]= { "mvwblit_line", }; -typedef struct window_s { - WINDOW *win; -} window_t; - -typedef struct panel_s { - PANEL *panel; - int window_id; -} panel_t; - -typedef struct cond_s { - pthread_cond_t rcond; - pthread_cond_t wcond; - pthread_mutex_t mut; -} cond_t; - -typedef struct qwaq_resources_s { - progs_t *pr; - int initialized; - window_t stdscr; - PR_RESMAP (window_t) window_map; - PR_RESMAP (panel_t) panel_map; - cond_t event_cond; - RING_BUFFER (qwaq_event_t, QUEUE_SIZE) event_queue; - qwaq_button button_state; - cond_t command_cond; - RING_BUFFER (int, COMMAND_QUEUE_SIZE) command_queue; - cond_t results_cond; - RING_BUFFER (int, COMMAND_QUEUE_SIZE) results; - cond_t string_id_cond; - RING_BUFFER (int, STRING_ID_QUEUE_SIZE) string_ids; - dstring_t strings[STRING_ID_QUEUE_SIZE - 1]; -} qwaq_resources_t; - static window_t * window_new (qwaq_resources_t *res) { @@ -320,7 +279,7 @@ cmd_newwin (qwaq_resources_t *res) window_t *window = window_new (res); window->win = newwin (ylen, xlen, ypos, xpos); - keypad (window->win, TRUE); + keypad (window->win, FALSE); // do it ourselves int window_id = window_index (res, window); int cmd_result[] = { qwaq_cmd_newwin, window_id }; @@ -615,8 +574,8 @@ dump_command (qwaq_resources_t *res, int len) } } -static void -init_timeout (struct timespec *timeout, long time) +void +qwaq_init_timeout (struct timespec *timeout, long time) { #define SEC 1000000000 struct timeval now; @@ -638,7 +597,7 @@ process_commands (qwaq_resources_t *res) int ret = 0; pthread_mutex_lock (&res->command_cond.mut); - init_timeout (&timeout, 20 * 1000000); + qwaq_init_timeout (&timeout, 20 * 1000000); while (RB_DATA_AVAILABLE (res->command_queue) < 2 && ret != ETIMEDOUT) { ret = pthread_cond_timedwait (&res->command_cond.rcond, &res->command_cond.mut, &timeout); @@ -736,38 +695,6 @@ process_commands (qwaq_resources_t *res) } } -static void -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; - } - - pthread_mutex_lock (&res->event_cond.mut); - init_timeout (&timeout, 5000 * 1000000L); - while (RB_SPACE_AVAILABLE (res->event_queue) < 1 && ret != ETIMEDOUT) { - 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); -} - static int get_event (qwaq_resources_t *res, qwaq_event_t *event) { @@ -775,7 +702,7 @@ get_event (qwaq_resources_t *res, qwaq_event_t *event) int ret = 0; pthread_mutex_lock (&res->event_cond.mut); - init_timeout (&timeout, 20 * 1000000); + qwaq_init_timeout (&timeout, 20 * 1000000); while (RB_DATA_AVAILABLE (res->event_queue) < 1 && ret != ETIMEDOUT) { ret = pthread_cond_timedwait (&res->event_cond.rcond, &res->event_cond.mut, &timeout); @@ -792,139 +719,15 @@ get_event (qwaq_resources_t *res, qwaq_event_t *event) return ret != ETIMEDOUT; } -#define M_MOVE REPORT_MOUSE_POSITION -#define M_PRESS ( BUTTON1_PRESSED \ - | BUTTON2_PRESSED \ - | BUTTON3_PRESSED \ - | BUTTON4_PRESSED \ - | BUTTON5_PRESSED) -#define M_RELEASE ( BUTTON1_RELEASED \ - | BUTTON2_RELEASED \ - | BUTTON3_RELEASED \ - | BUTTON4_RELEASED \ - | BUTTON5_RELEASED) -#define M_CLICK ( BUTTON1_CLICKED \ - | BUTTON2_CLICKED \ - | BUTTON3_CLICKED \ - | BUTTON4_CLICKED \ - | BUTTON5_CLICKED) -#define M_DCLICK ( BUTTON1_DOUBLE_CLICKED \ - | BUTTON2_DOUBLE_CLICKED \ - | BUTTON3_DOUBLE_CLICKED \ - | BUTTON4_DOUBLE_CLICKED \ - | BUTTON5_DOUBLE_CLICKED) -#define M_TCLICK ( BUTTON1_TRIPLE_CLICKED \ - | BUTTON2_TRIPLE_CLICKED \ - | BUTTON3_TRIPLE_CLICKED \ - | BUTTON4_TRIPLE_CLICKED \ - | BUTTON5_TRIPLE_CLICKED) - -static void -mouse_event (qwaq_resources_t *res, const MEVENT *mevent) -{ - int mask = mevent->bstate; - qwaq_button buttons = res->button_state; - qwaq_event_t event = {}; - - if (mevent->bstate & ~M_MOVE) { - mvprintw (1, 0, "%08x", mevent->bstate); - doupdate(); - } - event.mouse.x = mevent->x; - event.mouse.y = mevent->y; - if (mask & M_MOVE) { - event.what = qe_mousemove; - } - if (mask & M_PRESS) { - event.what = qe_mousedown; - if (mask & BUTTON1_PRESSED) { - buttons |= qe_mouse1; - } - if (mask & BUTTON2_PRESSED) { - buttons |= qe_mouse2; - } - if (mask & BUTTON3_PRESSED) { - buttons |= qe_mouse3; - } -#if 0 // wheel events don't report release - if (mask & BUTTON4_PRESSED) { - buttons |= qe_mouse4; - } - if (mask & BUTTON5_PRESSED) { - buttons |= qe_mouse5; - } -#endif - } - if (mask & M_RELEASE) { - event.what = qe_mouseup; - if (mask & BUTTON1_RELEASED) { - buttons &= ~qe_mouse1; - } - if (mask & BUTTON2_RELEASED) { - buttons &= ~qe_mouse2; - } - if (mask & BUTTON3_RELEASED) { - buttons &= ~qe_mouse3; - } -#if 0 // wheel events don't report release - if (mask & BUTTON4_RELEASED) { - buttons &= ~qe_mouse4; - } - if (mask & BUTTON5_RELEASED) { - buttons &= ~qe_mouse5; - } -#endif - } - event.mouse.buttons = buttons; - res->button_state = buttons; - if (mask & M_CLICK) { - event.what = qe_mouseclick; - event.mouse.click = 1; - } - if (mask & M_DCLICK) { - event.what = qe_mouseclick; - event.mouse.click = 2; - } - if (mask & M_TCLICK) { - event.what = qe_mouseclick; - event.mouse.click = 3; - } - add_event (res, &event); -} - -static void -key_event (qwaq_resources_t *res, int key) -{ - qwaq_event_t event = {}; - event.what = qe_keydown; - event.key = key; - add_event (res, &event); -} - -static void -process_input (qwaq_resources_t *res) -{ - if (Sys_CheckInput (1, -1)) { - int ch; - while ((ch = getch ()) != ERR && ch) { - fflush (stderr); - if (ch == KEY_MOUSE) { - MEVENT mevent; - getmouse (&mevent); - mouse_event (res, &mevent); - } else { - key_event (res, ch); - } - } - } -} - static int need_endwin; static void bi_shutdown (void *_pr) { + __auto_type pr = (progs_t *) _pr; + qwaq_resources_t *res = PR_Resources_Find (pr, "qwaq"); + if (need_endwin) { - write(1, MOUSE_MOVES_OFF, sizeof (MOUSE_MOVES_OFF) - 1); + qwaq_input_shutdown (res); endwin (); } } @@ -1598,7 +1401,7 @@ qwaq_curses_thread (qwaq_thread_t *thread) while (1) { process_commands (res); - process_input (res); + qwaq_process_input (res); } thread->return_code = 0; return thread; @@ -1614,12 +1417,12 @@ bi_initialize (progs_t *pr) res->initialized = 1; start_color (); raw (); - keypad (stdscr, TRUE); + keypad (stdscr, FALSE); // do it ourselves noecho (); nonl (); nodelay (stdscr, TRUE); mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); - write(1, MOUSE_MOVES_ON, sizeof (MOUSE_MOVES_ON) - 1); + qwaq_input_init (res); refresh(); res->stdscr.win = stdscr; @@ -1844,7 +1647,7 @@ bi_qwaq_clear (progs_t *pr, void *data) __auto_type res = (qwaq_resources_t *) data; if (res->initialized) { - write(1, MOUSE_MOVES_OFF, sizeof (MOUSE_MOVES_OFF) - 1); + qwaq_input_shutdown (res); endwin (); } need_endwin = 0; @@ -1939,10 +1742,15 @@ BI_Init (progs_t *pr) { qwaq_resources_t *res = calloc (sizeof (*res), 1); res->pr = pr; + for (int i = 0; i < STRING_ID_QUEUE_SIZE - 1; i++) { RB_WRITE_DATA (res->string_ids, &i, 1); res->strings[i].mem = &dstring_default_mem; } + res->escbuff.mem = &dstring_default_mem; + // ensure the backing buffer exists + dstring_clearstr (&res->escbuff); + qwaq_init_cond (&res->event_cond); qwaq_init_cond (&res->command_cond); qwaq_init_cond (&res->results_cond); diff --git a/ruamoko/qwaq/qwaq-curses.h b/ruamoko/qwaq/qwaq-curses.h index 6d0c43df2..9bc5c1d2d 100644 --- a/ruamoko/qwaq/qwaq-curses.h +++ b/ruamoko/qwaq/qwaq-curses.h @@ -130,6 +130,71 @@ typedef struct panel_s *panel_t; @extern Rect getwrect (struct window_s *window); @extern void printf(string fmt, ...); +// qfcc stuff +#else +// gcc stuff + +#include +#include + +#include "QF/dstring.h" +#include "QF/progs.h" +#include "QF/ringbuffer.h" + +#define QUEUE_SIZE 16 +#define STRING_ID_QUEUE_SIZE 8 // must be > 1 +#define COMMAND_QUEUE_SIZE 1280 + +typedef struct window_s { + WINDOW *win; +} window_t; + +typedef struct panel_s { + PANEL *panel; + int window_id; +} panel_t; + +typedef struct cond_s { + pthread_cond_t rcond; + pthread_cond_t wcond; + pthread_mutex_t mut; +} cond_t; + +typedef enum { + esc_ground, + esc_escape, + esc_csi, + esc_mouse, + esc_sgr, +} esc_state_t; + +typedef struct qwaq_resources_s { + progs_t *pr; + int initialized; + window_t stdscr; + PR_RESMAP (window_t) window_map; + PR_RESMAP (panel_t) panel_map; + cond_t event_cond; + RING_BUFFER (qwaq_event_t, QUEUE_SIZE) event_queue; + cond_t command_cond; + RING_BUFFER (int, COMMAND_QUEUE_SIZE) command_queue; + cond_t results_cond; + RING_BUFFER (int, COMMAND_QUEUE_SIZE) results; + cond_t string_id_cond; + RING_BUFFER (int, STRING_ID_QUEUE_SIZE) string_ids; + dstring_t strings[STRING_ID_QUEUE_SIZE - 1]; + + dstring_t escbuff; + esc_state_t escstate; + unsigned button_state; + qwaq_event_t lastClick; +} qwaq_resources_t; +// gcc stuff + +void qwaq_input_init (qwaq_resources_t *res); +void qwaq_input_shutdown (qwaq_resources_t *res); +void qwaq_process_input (qwaq_resources_t *res); +void qwaq_init_timeout (struct timespec *timeout, long time); #endif #endif//__qwaq_curses_h diff --git a/ruamoko/qwaq/qwaq-input.c b/ruamoko/qwaq/qwaq-input.c new file mode 100644 index 000000000..2875e44f6 --- /dev/null +++ b/ruamoko/qwaq/qwaq-input.c @@ -0,0 +1,226 @@ +/* + qwaq-input.c + + Input handling + + Copyright (C) 2020 Bill Currie + + Author: Bill Currie + 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 +#include +#include +#include +#include +#include + +#include "QF/dstring.h" +#include "QF/sys.h" + +#include "qwaq.h" +#include "event.h" +#include "qwaq-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 0x7c // scroll up/down/left/right - always click + +static void +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; + } + + pthread_mutex_lock (&res->event_cond.mut); + qwaq_init_timeout (&timeout, 5000 * 1000000L); + while (RB_SPACE_AVAILABLE (res->event_queue) < 1 && ret != ETIMEDOUT) { + 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); +} + +static void +key_event (qwaq_resources_t *res, int key) +{ + qwaq_event_t event = {}; + event.what = qe_keydown; + event.when = Sys_DoubleTime (); + event.key = key; + 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; + } + add_event (res, &event); +} + +static void +parse_mouse (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] - ' '; + 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 &= ~(0x7c); + if (!ext && button == 3) { + res->button_state = 0; // unknown which button was released + button = -1; + what = qe_mouseup; + } else { + if (ext) { + button += 4 * ext - 1; + } + res->button_state |= 1 << button; + if (res->button_state & WHEEL_BUTTONS) { + what = qe_mouseclick; + } else { + what = qe_mousedown; + } + } + } + Sys_Printf ("%3d %3d %02x %03x %02x %2d\n", x, y, ctrl, res->button_state, + ext, button + 1); + + mouse_event (res, what, x, y); +} + +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); + break; + case esc_escape: + if (ch == '[') { + res->escstate = esc_csi; + } + break; + case esc_csi: + if (ch == 'M') { + res->escstate = esc_mouse; + dstring_clear (&res->escbuff); + } + break; + case esc_mouse: + dstring_append (&res->escbuff, &ch, 1); + if (res->escbuff.size == 3) { + parse_mouse (res); + res->escstate = esc_ground; + } + break; + case esc_sgr: + break; + } + //printf("res->escstate %d\n", res->escstate); + } +} + +void qwaq_input_init (qwaq_resources_t *res) +{ + // 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); +} + +void qwaq_process_input (qwaq_resources_t *res) +{ + char buf[256]; + int len; + + while (Sys_CheckInput (1, -1)) { + len = read(0, buf, sizeof (buf)); + //Sys_Printf ("%.*s\n", len-1, buf+1); + for (int i = 0; i < len; i++) { + process_char (res, buf[i]); + } + } +} diff --git a/ruamoko/qwaq/qwaq.h b/ruamoko/qwaq/qwaq.h index f0974e61c..3172cd7f7 100644 --- a/ruamoko/qwaq/qwaq.h +++ b/ruamoko/qwaq/qwaq.h @@ -2,6 +2,7 @@ #define __qwaq_h #include "QF/darray.h" +#include "QF/progs.h" typedef struct qwaq_thread_s { pthread_t thread_id;