| /**************************************************************************** |
| * Copyright (c) 1998-2007,2008 Free Software Foundation, Inc. * |
| * * |
| * Permission is hereby granted, free of charge, to any person obtaining a * |
| * copy of this software and associated documentation files (the * |
| * "Software"), to deal in the Software without restriction, including * |
| * without limitation the rights to use, copy, modify, merge, publish, * |
| * distribute, distribute with modifications, sublicense, and/or sell * |
| * copies of the Software, and to permit persons to whom the Software is * |
| * furnished to do so, subject to the following conditions: * |
| * * |
| * The above copyright notice and this permission notice shall be included * |
| * in all copies or substantial portions of the Software. * |
| * * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * |
| * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * |
| * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * |
| * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * |
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * |
| * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * |
| * * |
| * Except as contained in this notice, the name(s) of the above copyright * |
| * holders shall not be used in advertising or otherwise to promote the * |
| * sale, use or other dealings in this Software without prior written * |
| * authorization. * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * |
| * and: Eric S. Raymond <esr@snark.thyrsus.com> * |
| * and: Thomas E. Dickey 1996-on * |
| ****************************************************************************/ |
| |
| /* |
| ** lib_getch.c |
| ** |
| ** The routine getch(). |
| ** |
| */ |
| |
| #include <curses.priv.h> |
| |
| MODULE_ID("$Id: lib_getch.c,v 1.99 2008/09/20 19:46:13 tom Exp $") |
| |
| #include <fifo_defs.h> |
| |
| #if USE_REENTRANT |
| #define GetEscdelay(sp) (sp)->_ESCDELAY |
| NCURSES_EXPORT(int) |
| NCURSES_PUBLIC_VAR(ESCDELAY) (void) |
| { |
| return SP ? GetEscdelay(SP) : 1000; |
| } |
| #else |
| #define GetEscdelay(sp) ESCDELAY |
| NCURSES_EXPORT_VAR(int) |
| ESCDELAY = 1000; /* max interval betw. chars in funkeys, in millisecs */ |
| #endif |
| |
| #if NCURSES_EXT_FUNCS |
| NCURSES_EXPORT(int) |
| set_escdelay(int value) |
| { |
| int code = OK; |
| #if USE_REENTRANT |
| if (SP) { |
| SP->_ESCDELAY = value; |
| } else { |
| code = ERR; |
| } |
| #else |
| ESCDELAY = value; |
| #endif |
| return code; |
| } |
| #endif |
| |
| static int |
| _nc_use_meta(WINDOW *win) |
| { |
| SCREEN *sp = _nc_screen_of(win); |
| return (sp ? sp->_use_meta : 0); |
| } |
| |
| #ifdef NCURSES_WGETCH_EVENTS |
| #define TWAIT_MASK 7 |
| #else |
| #define TWAIT_MASK 3 |
| #endif |
| |
| /* |
| * Check for mouse activity, returning nonzero if we find any. |
| */ |
| static int |
| check_mouse_activity(SCREEN *sp, int delay EVENTLIST_2nd(_nc_eventlist * evl)) |
| { |
| int rc; |
| |
| #if USE_SYSMOUSE |
| if ((sp->_mouse_type == M_SYSMOUSE) |
| && (sp->_sysmouse_head < sp->_sysmouse_tail)) { |
| return 2; |
| } |
| #endif |
| rc = _nc_timed_wait(sp, TWAIT_MASK, delay, (int *) 0 EVENTLIST_2nd(evl)); |
| #if USE_SYSMOUSE |
| if ((sp->_mouse_type == M_SYSMOUSE) |
| && (sp->_sysmouse_head < sp->_sysmouse_tail) |
| && (rc == 0) |
| && (errno == EINTR)) { |
| rc |= 2; |
| } |
| #endif |
| return rc; |
| } |
| |
| static NCURSES_INLINE int |
| fifo_peek(SCREEN *sp) |
| { |
| int ch = sp->_fifo[peek]; |
| TR(TRACE_IEVENT, ("peeking at %d", peek)); |
| |
| p_inc(); |
| return ch; |
| } |
| |
| static NCURSES_INLINE int |
| fifo_pull(SCREEN *sp) |
| { |
| int ch; |
| ch = sp->_fifo[head]; |
| TR(TRACE_IEVENT, ("pulling %s from %d", _nc_tracechar(sp, ch), head)); |
| |
| if (peek == head) { |
| h_inc(); |
| peek = head; |
| } else |
| h_inc(); |
| |
| #ifdef TRACE |
| if (USE_TRACEF(TRACE_IEVENT)) { |
| _nc_fifo_dump(sp); |
| _nc_unlock_global(tracef); |
| } |
| #endif |
| return ch; |
| } |
| |
| static NCURSES_INLINE int |
| fifo_push(SCREEN *sp EVENTLIST_2nd(_nc_eventlist * evl)) |
| { |
| int n; |
| int ch = 0; |
| int mask = 0; |
| |
| (void) mask; |
| if (tail == -1) |
| return ERR; |
| |
| #ifdef HIDE_EINTR |
| again: |
| errno = 0; |
| #endif |
| |
| #ifdef NCURSES_WGETCH_EVENTS |
| if (evl |
| #if USE_GPM_SUPPORT || USE_EMX_MOUSE || USE_SYSMOUSE |
| || (sp->_mouse_fd >= 0) |
| #endif |
| ) { |
| mask = check_mouse_activity(sp, -1 EVENTLIST_2nd(evl)); |
| } else |
| mask = 0; |
| |
| if (mask & 4) { |
| T(("fifo_push: ungetch KEY_EVENT")); |
| _nc_ungetch(sp, KEY_EVENT); |
| return KEY_EVENT; |
| } |
| #elif USE_GPM_SUPPORT || USE_EMX_MOUSE || USE_SYSMOUSE |
| if (sp->_mouse_fd >= 0) { |
| mask = check_mouse_activity(sp, -1 EVENTLIST_2nd(evl)); |
| } |
| #endif |
| |
| #if USE_GPM_SUPPORT || USE_EMX_MOUSE |
| if ((sp->_mouse_fd >= 0) && (mask & 2)) { |
| sp->_mouse_event(sp); |
| ch = KEY_MOUSE; |
| n = 1; |
| } else |
| #endif |
| #if USE_SYSMOUSE |
| if ((sp->_mouse_type == M_SYSMOUSE) |
| && (sp->_sysmouse_head < sp->_sysmouse_tail)) { |
| sp->_mouse_event(sp); |
| ch = KEY_MOUSE; |
| n = 1; |
| } else if ((sp->_mouse_type == M_SYSMOUSE) |
| && (mask <= 0) && errno == EINTR) { |
| sp->_mouse_event(sp); |
| ch = KEY_MOUSE; |
| n = 1; |
| } else |
| #endif |
| { /* Can block... */ |
| unsigned char c2 = 0; |
| n = read(sp->_ifd, &c2, 1); |
| ch = c2; |
| } |
| |
| #ifdef HIDE_EINTR |
| /* |
| * Under System V curses with non-restarting signals, getch() returns |
| * with value ERR when a handled signal keeps it from completing. |
| * If signals restart system calls, OTOH, the signal is invisible |
| * except to its handler. |
| * |
| * We don't want this difference to show. This piece of code |
| * tries to make it look like we always have restarting signals. |
| */ |
| if (n <= 0 && errno == EINTR) |
| goto again; |
| #endif |
| |
| if ((n == -1) || (n == 0)) { |
| TR(TRACE_IEVENT, ("read(%d,&ch,1)=%d, errno=%d", sp->_ifd, n, errno)); |
| ch = ERR; |
| } |
| TR(TRACE_IEVENT, ("read %d characters", n)); |
| |
| sp->_fifo[tail] = ch; |
| sp->_fifohold = 0; |
| if (head == -1) |
| head = peek = tail; |
| t_inc(); |
| TR(TRACE_IEVENT, ("pushed %s at %d", _nc_tracechar(sp, ch), tail)); |
| #ifdef TRACE |
| if (USE_TRACEF(TRACE_IEVENT)) { |
| _nc_fifo_dump(sp); |
| _nc_unlock_global(tracef); |
| } |
| #endif |
| return ch; |
| } |
| |
| static NCURSES_INLINE void |
| fifo_clear(SCREEN *sp) |
| { |
| memset(sp->_fifo, 0, sizeof(sp->_fifo)); |
| head = -1; |
| tail = peek = 0; |
| } |
| |
| static int kgetch(SCREEN *EVENTLIST_2nd(_nc_eventlist * evl)); |
| |
| static void |
| recur_wrefresh(WINDOW *win) |
| { |
| #ifdef USE_PTHREADS |
| SCREEN *sp = _nc_screen_of(win); |
| if (_nc_use_pthreads && sp != SP) { |
| SCREEN *save_SP; |
| |
| /* temporarily switch to the window's screen to check/refresh */ |
| _nc_lock_global(curses); |
| save_SP = SP; |
| _nc_set_screen(sp); |
| recur_wrefresh(win); |
| _nc_set_screen(save_SP); |
| _nc_unlock_global(curses); |
| } else |
| #endif |
| if ((is_wintouched(win) || (win->_flags & _HASMOVED)) |
| && !(win->_flags & _ISPAD)) { |
| wrefresh(win); |
| } |
| } |
| |
| static int |
| recur_wgetnstr(WINDOW *win, char *buf) |
| { |
| SCREEN *sp = _nc_screen_of(win); |
| int rc; |
| |
| if (sp != 0) { |
| #ifdef USE_PTHREADS |
| if (_nc_use_pthreads && sp != SP) { |
| SCREEN *save_SP; |
| |
| /* temporarily switch to the window's screen to get cooked input */ |
| _nc_lock_global(curses); |
| save_SP = SP; |
| _nc_set_screen(sp); |
| rc = recur_wgetnstr(win, buf); |
| _nc_set_screen(save_SP); |
| _nc_unlock_global(curses); |
| } else |
| #endif |
| { |
| sp->_called_wgetch = TRUE; |
| rc = wgetnstr(win, buf, MAXCOLUMNS); |
| sp->_called_wgetch = FALSE; |
| } |
| } else { |
| rc = ERR; |
| } |
| return rc; |
| } |
| |
| NCURSES_EXPORT(int) |
| _nc_wgetch(WINDOW *win, |
| unsigned long *result, |
| int use_meta |
| EVENTLIST_2nd(_nc_eventlist * evl)) |
| { |
| SCREEN *sp; |
| int ch; |
| #ifdef NCURSES_WGETCH_EVENTS |
| long event_delay = -1; |
| #endif |
| |
| T((T_CALLED("_nc_wgetch(%p)"), win)); |
| |
| *result = 0; |
| |
| sp = _nc_screen_of(win); |
| if (win == 0 || sp == 0) { |
| returnCode(ERR); |
| } |
| |
| if (cooked_key_in_fifo()) { |
| recur_wrefresh(win); |
| *result = fifo_pull(sp); |
| returnCode(*result >= KEY_MIN ? KEY_CODE_YES : OK); |
| } |
| #ifdef NCURSES_WGETCH_EVENTS |
| if (evl && (evl->count == 0)) |
| evl = NULL; |
| event_delay = _nc_eventlist_timeout(evl); |
| #endif |
| |
| /* |
| * Handle cooked mode. Grab a string from the screen, |
| * stuff its contents in the FIFO queue, and pop off |
| * the first character to return it. |
| */ |
| if (head == -1 && |
| !sp->_notty && |
| !sp->_raw && |
| !sp->_cbreak && |
| !sp->_called_wgetch) { |
| char buf[MAXCOLUMNS], *bufp; |
| int rc; |
| |
| TR(TRACE_IEVENT, ("filling queue in cooked mode")); |
| |
| rc = recur_wgetnstr(win, buf); |
| |
| /* ungetch in reverse order */ |
| #ifdef NCURSES_WGETCH_EVENTS |
| if (rc != KEY_EVENT) |
| #endif |
| _nc_ungetch(sp, '\n'); |
| for (bufp = buf + strlen(buf); bufp > buf; bufp--) |
| _nc_ungetch(sp, bufp[-1]); |
| |
| #ifdef NCURSES_WGETCH_EVENTS |
| /* Return it first */ |
| if (rc == KEY_EVENT) { |
| *result = rc; |
| } else |
| #endif |
| *result = fifo_pull(sp); |
| returnCode(*result >= KEY_MIN ? KEY_CODE_YES : OK); |
| } |
| |
| if (win->_use_keypad != sp->_keypad_on) |
| _nc_keypad(sp, win->_use_keypad); |
| |
| recur_wrefresh(win); |
| |
| if (win->_notimeout || (win->_delay >= 0) || (sp->_cbreak > 1)) { |
| if (head == -1) { /* fifo is empty */ |
| int delay; |
| int rc; |
| |
| TR(TRACE_IEVENT, ("timed delay in wgetch()")); |
| if (sp->_cbreak > 1) |
| delay = (sp->_cbreak - 1) * 100; |
| else |
| delay = win->_delay; |
| |
| #ifdef NCURSES_WGETCH_EVENTS |
| if (event_delay >= 0 && delay > event_delay) |
| delay = event_delay; |
| #endif |
| |
| TR(TRACE_IEVENT, ("delay is %d milliseconds", delay)); |
| |
| rc = check_mouse_activity(sp, delay EVENTLIST_2nd(evl)); |
| |
| #ifdef NCURSES_WGETCH_EVENTS |
| if (rc & 4) { |
| *result = KEY_EVENT; |
| returnCode(KEY_CODE_YES); |
| } |
| #endif |
| if (!rc) { |
| returnCode(ERR); |
| } |
| } |
| /* else go on to read data available */ |
| } |
| |
| if (win->_use_keypad) { |
| /* |
| * This is tricky. We only want to get special-key |
| * events one at a time. But we want to accumulate |
| * mouse events until either (a) the mouse logic tells |
| * us it's picked up a complete gesture, or (b) |
| * there's a detectable time lapse after one. |
| * |
| * Note: if the mouse code starts failing to compose |
| * press/release events into clicks, you should probably |
| * increase the wait with mouseinterval(). |
| */ |
| int runcount = 0; |
| int rc; |
| |
| do { |
| ch = kgetch(sp EVENTLIST_2nd(evl)); |
| if (ch == KEY_MOUSE) { |
| ++runcount; |
| if (sp->_mouse_inline(sp)) |
| break; |
| } |
| if (sp->_maxclick < 0) |
| break; |
| } while |
| (ch == KEY_MOUSE |
| && (((rc = check_mouse_activity(sp, sp->_maxclick |
| EVENTLIST_2nd(evl))) != 0 |
| && !(rc & 4)) |
| || !sp->_mouse_parse(sp, runcount))); |
| #ifdef NCURSES_WGETCH_EVENTS |
| if ((rc & 4) && !ch == KEY_EVENT) { |
| _nc_ungetch(sp, ch); |
| ch = KEY_EVENT; |
| } |
| #endif |
| if (runcount > 0 && ch != KEY_MOUSE) { |
| #ifdef NCURSES_WGETCH_EVENTS |
| /* mouse event sequence ended by an event, report event */ |
| if (ch == KEY_EVENT) { |
| _nc_ungetch(sp, KEY_MOUSE); /* FIXME This interrupts a gesture... */ |
| } else |
| #endif |
| { |
| /* mouse event sequence ended by keystroke, store keystroke */ |
| _nc_ungetch(sp, ch); |
| ch = KEY_MOUSE; |
| } |
| } |
| } else { |
| if (head == -1) |
| fifo_push(sp EVENTLIST_2nd(evl)); |
| ch = fifo_pull(sp); |
| } |
| |
| if (ch == ERR) { |
| #if USE_SIZECHANGE |
| if (_nc_handle_sigwinch(sp)) { |
| _nc_update_screensize(sp); |
| /* resizeterm can push KEY_RESIZE */ |
| if (cooked_key_in_fifo()) { |
| *result = fifo_pull(sp); |
| returnCode(*result >= KEY_MIN ? KEY_CODE_YES : OK); |
| } |
| } |
| #endif |
| returnCode(ERR); |
| } |
| |
| /* |
| * If echo() is in effect, display the printable version of the |
| * key on the screen. Carriage return and backspace are treated |
| * specially by Solaris curses: |
| * |
| * If carriage return is defined as a function key in the |
| * terminfo, e.g., kent, then Solaris may return either ^J (or ^M |
| * if nonl() is set) or KEY_ENTER depending on the echo() mode. |
| * We echo before translating carriage return based on nonl(), |
| * since the visual result simply moves the cursor to column 0. |
| * |
| * Backspace is a different matter. Solaris curses does not |
| * translate it to KEY_BACKSPACE if kbs=^H. This does not depend |
| * on the stty modes, but appears to be a hardcoded special case. |
| * This is a difference from ncurses, which uses the terminfo entry. |
| * However, we provide the same visual result as Solaris, moving the |
| * cursor to the left. |
| */ |
| if (sp->_echo && !(win->_flags & _ISPAD)) { |
| chtype backup = (ch == KEY_BACKSPACE) ? '\b' : ch; |
| if (backup < KEY_MIN) |
| wechochar(win, backup); |
| } |
| |
| /* |
| * Simulate ICRNL mode |
| */ |
| if ((ch == '\r') && sp->_nl) |
| ch = '\n'; |
| |
| /* Strip 8th-bit if so desired. We do this only for characters that |
| * are in the range 128-255, to provide compatibility with terminals |
| * that display only 7-bit characters. Note that 'ch' may be a |
| * function key at this point, so we mustn't strip _those_. |
| */ |
| if (!use_meta) |
| if ((ch < KEY_MIN) && (ch & 0x80)) |
| ch &= 0x7f; |
| |
| T(("wgetch returning : %s", _nc_tracechar(sp, ch))); |
| |
| *result = ch; |
| returnCode(ch >= KEY_MIN ? KEY_CODE_YES : OK); |
| } |
| |
| #ifdef NCURSES_WGETCH_EVENTS |
| NCURSES_EXPORT(int) |
| wgetch_events(WINDOW *win, _nc_eventlist * evl) |
| { |
| int code; |
| unsigned long value; |
| |
| T((T_CALLED("wgetch_events(%p,%p)"), win, evl)); |
| code = _nc_wgetch(win, |
| &value, |
| _nc_use_meta(win) |
| EVENTLIST_2nd(evl)); |
| if (code != ERR) |
| code = value; |
| returnCode(code); |
| } |
| #endif |
| |
| NCURSES_EXPORT(int) |
| wgetch(WINDOW *win) |
| { |
| int code; |
| unsigned long value; |
| |
| T((T_CALLED("wgetch(%p)"), win)); |
| code = _nc_wgetch(win, |
| &value, |
| _nc_use_meta(win) |
| EVENTLIST_2nd((_nc_eventlist *) 0)); |
| if (code != ERR) |
| code = value; |
| returnCode(code); |
| } |
| |
| /* |
| ** int |
| ** kgetch() |
| ** |
| ** Get an input character, but take care of keypad sequences, returning |
| ** an appropriate code when one matches the input. After each character |
| ** is received, set an alarm call based on ESCDELAY. If no more of the |
| ** sequence is received by the time the alarm goes off, pass through |
| ** the sequence gotten so far. |
| ** |
| ** This function must be called when there are no cooked keys in queue. |
| ** (that is head==-1 || peek==head) |
| ** |
| */ |
| |
| static int |
| kgetch(SCREEN *sp EVENTLIST_2nd(_nc_eventlist * evl)) |
| { |
| TRIES *ptr; |
| int ch = 0; |
| int timeleft = GetEscdelay(sp); |
| |
| TR(TRACE_IEVENT, ("kgetch() called")); |
| |
| ptr = sp->_keytry; |
| |
| for (;;) { |
| if (cooked_key_in_fifo() && sp->_fifo[head] >= KEY_MIN) { |
| break; |
| } else if (!raw_key_in_fifo()) { |
| ch = fifo_push(sp EVENTLIST_2nd(evl)); |
| if (ch == ERR) { |
| peek = head; /* the keys stay uninterpreted */ |
| return ERR; |
| } |
| #ifdef NCURSES_WGETCH_EVENTS |
| else if (ch == KEY_EVENT) { |
| peek = head; /* the keys stay uninterpreted */ |
| return fifo_pull(sp); /* Remove KEY_EVENT from the queue */ |
| } |
| #endif |
| } |
| |
| ch = fifo_peek(sp); |
| if (ch >= KEY_MIN) { |
| /* If not first in queue, somebody put this key there on purpose in |
| * emergency. Consider it higher priority than the unfinished |
| * keysequence we are parsing. |
| */ |
| peek = head; |
| /* assume the key is the last in fifo */ |
| t_dec(); /* remove the key */ |
| return ch; |
| } |
| |
| TR(TRACE_IEVENT, ("ch: %s", _nc_tracechar(sp, (unsigned char) ch))); |
| while ((ptr != NULL) && (ptr->ch != (unsigned char) ch)) |
| ptr = ptr->sibling; |
| |
| if (ptr == NULL) { |
| TR(TRACE_IEVENT, ("ptr is null")); |
| break; |
| } |
| TR(TRACE_IEVENT, ("ptr=%p, ch=%d, value=%d", |
| ptr, ptr->ch, ptr->value)); |
| |
| if (ptr->value != 0) { /* sequence terminated */ |
| TR(TRACE_IEVENT, ("end of sequence")); |
| if (peek == tail) |
| fifo_clear(sp); |
| else |
| head = peek; |
| return (ptr->value); |
| } |
| |
| ptr = ptr->child; |
| |
| if (!raw_key_in_fifo()) { |
| int rc; |
| |
| TR(TRACE_IEVENT, ("waiting for rest of sequence")); |
| rc = check_mouse_activity(sp, timeleft EVENTLIST_2nd(evl)); |
| #ifdef NCURSES_WGETCH_EVENTS |
| if (rc & 4) { |
| TR(TRACE_IEVENT, ("interrupted by a user event")); |
| /* FIXME Should have preserved remainder timeleft for reuse... */ |
| peek = head; /* Restart interpreting later */ |
| return KEY_EVENT; |
| } |
| #endif |
| if (!rc) { |
| TR(TRACE_IEVENT, ("ran out of time")); |
| break; |
| } |
| } |
| } |
| ch = fifo_pull(sp); |
| peek = head; |
| return ch; |
| } |