/* Keyboard support routines. Copyright (C) 1994,1995 the Free Software Foundation. Written by: 1994, 1995 Miguel de Icaza. 1994, 1995 Janne Kukonlehto. 1995 Jakub Jelinek. 1997 Norbert Warmuth 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include /* FD_ZERO et al */ #ifndef SCO_FLAVOR /* alex: sys/select.h defines struct timeval */ # include /* struct timeval */ #endif /* SCO_FLAVOR */ #if HAVE_SYS_SELECT_H # include #endif #include "tty.h" #include #include #include #include "util.h" /* For xmalloc prototype */ #include "mad.h" /* The memory debugger */ #include "global.h" #include "mouse.h" #include "key.h" #include "main.h" #include "file.h" #include "win.h" #include "cons.saver.h" #include "../vfs/vfs.h" #ifdef __linux__ # if defined(__GLIBC__) && (__GLIBC__ < 2) # include /* This is needed for TIOCLINUX */ # else # include # endif # include #endif #include "x.h" /* "$Id$" */ /* This macros were stolen from gpm 0.15 */ #define GET_TIME(tv) (gettimeofday(&tv, (struct timezone *)NULL)) #define DIF_TIME(t1,t2) ((t2.tv_sec -t1.tv_sec) *1000+ \ (t2.tv_usec-t1.tv_usec)/1000) /* timeout for old_esc_mode in usec */ #define ESCMODE_TIMEOUT 1000000 int mou_auto_repeat = 100; int double_click_speed = 250; int old_esc_mode = 0; int use_8th_bit_as_meta = 1; #ifndef HAVE_X typedef struct key_def { char ch; /* Holds the matching char code */ int code; /* The code returned, valid if child == NULL */ struct key_def *next; struct key_def *child; /* sequence continuation */ int action; /* optional action to be done. Now used only to mark that we are just after the first Escape */ } key_def; /* This holds all the key definitions */ static key_def *keys = 0; #endif static int input_fd; static fd_set select_set; static int disabled_channels = 0; /* Disable channels checking */ int xgetch_second (void); #ifndef PORT_HAS_FILE_HANDLERS /* File descriptor monitoring add/remove routines */ typedef struct SelectList { int fd; select_fn callback; void *info; struct SelectList *next; } SelectList; SelectList *select_list = 0; void add_select_channel (int fd, select_fn callback, void *info) { SelectList *new; new = xmalloc (sizeof (SelectList), "add_select_channel"); new->fd = fd; new->callback = callback; new->info = info; new->next = select_list; select_list = new; } void delete_select_channel (int fd) { SelectList *p = select_list; SelectList *prev = 0; while (p){ if (p->fd == fd){ if (prev) prev->next = p->next; else select_list = p->next; free (p); } prev = p; p = p->next; } } inline static int add_selects (fd_set *select_set) { SelectList *p; int top_fd = 0; if (disabled_channels) return 0; for (p = select_list; p; p = p->next){ FD_SET (p->fd, select_set); if (p->fd > top_fd) top_fd = p->fd; } return top_fd; } static void check_selects (fd_set *select_set) { SelectList *p; if (disabled_channels) return; for (p = select_list; p; p = p->next) if (FD_ISSET (p->fd, select_set)) (*p->callback)(p->fd, p->info); } #endif void channels_down (void) { disabled_channels ++; } void channels_up (void) { if (!disabled_channels) fprintf (stderr, "Error: channels_up called with disabled_channels = 0\n"); disabled_channels--; } typedef struct { int code; char *seq; int action; } key_define_t; #ifndef HAVE_X key_define_t mc_bindings [] = { { KEY_END, ESC_STR ">", MCKEY_NOACTION }, { KEY_HOME, ESC_STR "<", MCKEY_NOACTION }, #ifdef linux /* Incredible, but many Linuxes still have old databases */ { KEY_IC, ESC_STR "[2~", MCKEY_NOACTION }, #endif { 0, 0, MCKEY_NOACTION }, }; /* Broken terminfo and termcap databases on xterminals */ key_define_t xterm_key_defines [] = { { KEY_F(1), ESC_STR "OP", MCKEY_NOACTION }, { KEY_F(2), ESC_STR "OQ", MCKEY_NOACTION }, { KEY_F(3), ESC_STR "OR", MCKEY_NOACTION }, { KEY_F(4), ESC_STR "OS", MCKEY_NOACTION }, { KEY_F(1), ESC_STR "[11~", MCKEY_NOACTION }, { KEY_F(2), ESC_STR "[12~", MCKEY_NOACTION }, { KEY_F(3), ESC_STR "[13~", MCKEY_NOACTION }, { KEY_F(4), ESC_STR "[14~", MCKEY_NOACTION }, { KEY_F(5), ESC_STR "[15~", MCKEY_NOACTION }, { KEY_F(6), ESC_STR "[17~", MCKEY_NOACTION }, { KEY_F(7), ESC_STR "[18~", MCKEY_NOACTION }, { KEY_F(8), ESC_STR "[19~", MCKEY_NOACTION }, { KEY_F(9), ESC_STR "[20~", MCKEY_NOACTION }, { KEY_F(10), ESC_STR "[21~", MCKEY_NOACTION }, { 0, 0, MCKEY_NOACTION }, }; key_define_t mc_default_keys [] = { { ESC_CHAR, ESC_STR, MCKEY_ESCAPE }, { ESC_CHAR, ESC_STR ESC_STR, MCKEY_NOACTION }, { 0, 0, MCKEY_NOACTION }, }; #endif void define_sequences (key_define_t *kd) { #ifndef HAVE_X int i; for (i = 0; kd [i].code; i++) define_sequence(kd [i].code, kd [i].seq, kd [i].action); #endif } /* This has to be called before slang_init or whatever routine calls any define_sequence */ void init_key (void) { #ifndef HAVE_X char *term = (char *) getenv ("TERM"); /* This has to be the first define_sequence */ /* So, we can assume that the first keys member has ESC */ define_sequences (mc_default_keys); /* Terminfo on irix does not have some keys */ if ((!strncmp (term, "iris-ansi", 9)) || (!strncmp (term, "xterm", 5))) define_sequences (xterm_key_defines); define_sequences (mc_bindings); #endif /* !HAVE_X */ } /* This has to be called after SLang_init_tty/slint_init */ void init_key_input_fd (void) { #ifndef HAVE_X #ifdef HAVE_SLANG input_fd = SLang_TT_Read_FD; #endif #endif /* !HAVE_X */ } #ifndef HAVE_X void xmouse_get_event (Gpm_Event *ev) { int btn; static struct timeval tv1 = { 0, 0 }; /* Force first click as single */ static struct timeval tv2; static int clicks; /* Decode Xterm mouse information to a GPM style event */ /* Variable btn has following meaning: */ /* 0 = btn1 dn, 1 = btn2 dn, 2 = btn3 dn, 3 = btn up */ btn = xgetch () - 32; /* There seems to be no way of knowing which button was released */ /* So we assume all the buttons were released */ if (btn == 3){ ev->type = GPM_UP | (GPM_SINGLE << clicks); ev->buttons = 0; GET_TIME (tv1); clicks = 0; } else { ev->type = GPM_DOWN; GET_TIME (tv2); if (tv1.tv_sec && (DIF_TIME (tv1,tv2) < double_click_speed)){ clicks++; clicks %= 3; } else clicks = 0; switch (btn) { case 0: ev->buttons = GPM_B_LEFT; break; case 1: ev->buttons = GPM_B_MIDDLE; break; case 2: ev->buttons = GPM_B_RIGHT; break; default: /* Nothing */ break; } } /* Coordinates are 33-based */ /* Transform them to 1-based */ ev->x = xgetch () - 32; ev->y = xgetch () - 32; } static key_def *create_sequence (char *seq, int code, int action) { key_def *base, *p, *attach; for (base = attach = NULL; *seq; seq++){ p = xmalloc (sizeof (key_def), "create_sequence"); if (!base) base = p; if (attach) attach->child = p; p->ch = *seq; p->code = code; p->child = p->next = NULL; if (!seq[1]) p->action = action; else p->action = MCKEY_NOACTION; attach = p; } return base; } /* The maximum sequence length (32 + null terminator) */ static int seq_buffer [33]; static int *seq_append = 0; static int push_char (int c) { if (!seq_append) seq_append = seq_buffer; if (seq_append == &(seq_buffer [sizeof (seq_buffer)-2])) return 0; *(seq_append++) = c; *seq_append = 0; return 1; } #endif /* !HAVE_X */ void define_sequence (int code, char *seq, int action) { #ifndef HAVE_X key_def *base; if (strlen (seq) > sizeof (seq_buffer)-1) return; for (base = keys; (base != 0) && *seq; ){ if (*seq == base->ch){ if (base->child == 0){ if (*(seq+1)){ base->child = create_sequence (seq+1, code, action); return; } else { /* The sequence clashes */ return; } } else { base = base->child; seq++; } } else { if (base->next) base = base->next; else { base->next = create_sequence (seq, code, action); return; } } } keys = create_sequence (seq, code, action); #endif } #ifndef HAVE_X static int *pending_keys; #endif int correct_key_code (int c) { /* This is needed on some OS that do not support ncurses and */ /* do some magic after read()ing the data */ if (c == '\r') return '\n'; #ifdef IS_AIX if (c == KEY_SCANCEL) return '\t'; #endif if (c == KEY_F(0)) return KEY_F(10); if (!alternate_plus_minus) switch (c) { case KEY_KP_ADD: c = '+'; break; case KEY_KP_SUBTRACT: c = '-'; break; case KEY_KP_MULTIPLY: c = '*'; break; } return c; } int get_key_code (int no_delay) { #ifndef HAVE_X int c; static key_def *this = NULL, *parent; static struct timeval esctime = { -1, -1 }; static int lastnodelay = -1; if (no_delay != lastnodelay) { this = NULL; lastnodelay = no_delay; } pend_send: if (pending_keys){ int d = *pending_keys++; check_pend: if (!*pending_keys){ pending_keys = 0; seq_append = 0; } if (d == ESC_CHAR && pending_keys){ d = ALT(*pending_keys++); goto check_pend; } if ((d & 0x80) && use_8th_bit_as_meta) d = ALT(d & 0x7f); this = NULL; return correct_key_code (d); } nodelay_try_again: if (no_delay) { #ifdef BUGGY_CURSES wtimeout(stdscr, 500); #else nodelay (stdscr, TRUE); #endif } c = xgetch (); if (no_delay) { #ifdef BUGGY_CURSES notimeout (stdscr, TRUE); #else nodelay (stdscr, FALSE); #endif if (c == ERR) { if (this != NULL && parent != NULL && parent->action == MCKEY_ESCAPE && old_esc_mode) { struct timeval current, timeout; if (esctime.tv_sec == -1) return ERR; GET_TIME (current); timeout.tv_sec = ESCMODE_TIMEOUT / 1000000 + esctime.tv_sec; timeout.tv_usec = ESCMODE_TIMEOUT % 1000000 + esctime.tv_usec; if (timeout.tv_usec > 1000000) { timeout.tv_usec -= 1000000; timeout.tv_sec++; } if (current.tv_sec < timeout.tv_sec) return ERR; if (current.tv_sec == timeout.tv_sec && current.tv_usec < timeout.tv_usec) return ERR; this = NULL; pending_keys = seq_append = NULL; return ESC_CHAR; } return ERR; } } else if (c == ERR){ /* Maybe we got an incomplete match. This we do only in delay mode, since otherwise xgetch can return ERR at any time. */ if (seq_append) { pending_keys = seq_buffer; goto pend_send; } this = NULL; return ERR; } /* Search the key on the root */ if (!no_delay || this == NULL) { this = keys; parent = NULL; if ((c & 0x80) && use_8th_bit_as_meta) { c &= ~0x7f; /* The first sequence defined starts with esc */ parent = keys; this = keys->child; } } while (this){ if (c == this->ch){ if (this->child){ if (!push_char (c)){ pending_keys = seq_buffer; goto pend_send; } parent = this; this = this->child; if (parent->action == MCKEY_ESCAPE && old_esc_mode) { if (no_delay) { GET_TIME (esctime); if (this == NULL) { /* Shouldn't happen */ fprintf (stderr, "Internal error\n"); exit (1); } goto nodelay_try_again; } esctime.tv_sec = -1; c = xgetch_second (); if (c == ERR) { pending_keys = seq_append = NULL; this = NULL; return ESC_CHAR; } } else { if (no_delay) goto nodelay_try_again; c = xgetch (); } } else { /* We got a complete match, return and reset search */ int code; pending_keys = seq_append = NULL; code = this->code; this = NULL; return correct_key_code (code); } } else { if (this->next) this = this->next; else { if (parent != NULL && parent->action == MCKEY_ESCAPE) { /* This is just to save a lot of define_sequences */ if (isalpha(c) || (c == '\n') || (c == '\t') || (c == XCTRL('h')) || (c == KEY_BACKSPACE) || (c == '!') || (c == '\r') || c == 127 || c == '+' || c == '-' || c == '\\' || c == '?') c = ALT(c); else if (isdigit(c)) c = KEY_F (c-'0'); else if (c == ' ') c = ESC_CHAR; pending_keys = seq_append = NULL; this = NULL; return correct_key_code (c); } /* Did not find a match or {c} was changed in the if above, so we have to return everything we had skipped */ push_char (c); pending_keys = seq_buffer; goto pend_send; } } } this = NULL; return correct_key_code (c); #else return ERR; #endif /* HAVE_X */ } #ifndef PORT_HAS_FILE_HANDLERS /* If set timeout is set, then we wait 0.1 seconds, else, we block */ void try_channels (int set_timeout) { struct timeval timeout; static fd_set select_set; struct timeval *timeptr; int v; while (1){ FD_ZERO (&select_set); FD_SET (input_fd, &select_set); /* Add stdin */ add_selects (&select_set); if (set_timeout){ timeout.tv_sec = 0; timeout.tv_usec = 100000; timeptr = &timeout; } else timeptr = 0; v = select (FD_SETSIZE, &select_set, NULL, NULL, timeptr); if (v > 0){ check_selects (&select_set); if (FD_ISSET (input_fd, &select_set)) return; } } } #ifndef HAVE_X /* Workaround for System V Curses vt100 bug */ static int getch_with_delay (void) { int c; /* This routine could be used on systems without mouse support, so we need to do the select check :-( */ while (1){ if (!pending_keys) try_channels (0); /* Try to get a character */ c = get_key_code (0); if (c != ERR) break; /* Failed -> wait 0.1 secs and try again */ try_channels (1); } /* Success -> return the character */ return c; } #endif /* !HAVE_X */ #ifndef HAVE_LIBGPM #define gpm_flag 0 #endif #endif /* !HAVE_FILE_HANDLERS */ extern int max_dirt_limit; /* Returns a character read from stdin with appropriate interpretation */ /* Also takes care of generated mouse events */ /* Returns EV_MOUSE if it is a mouse event */ /* Returns EV_NONE if non-blocking or interrupt set and nothing was done */ int get_event (Gpm_Event *event, int redo_event, int block) { #ifndef HAVE_X int c; static int flag; /* Return value from select */ #ifdef HAVE_LIBGPM static Gpm_Event ev; /* Mouse event */ #endif struct timeval timeout; struct timeval *time_addr = NULL; static int dirty = 3; if ((dirty == 3) || is_idle ()){ mc_refresh (); doupdate (); dirty = 1; } else dirty++; vfs_timeout_handler (); /* Ok, we use (event->x < 0) to signal that the event does not contain a suitable position for the mouse, so we can't use show_mouse_pointer on it. */ if (event->x > 0){ show_mouse_pointer (event->x, event->y); if (!redo_event) event->x = -1; } /* Repeat if using mouse */ while ((xmouse_flag || gpm_flag) && !pending_keys) { if (xmouse_flag || gpm_flag) { FD_ZERO (&select_set); FD_SET (input_fd, &select_set); add_selects (&select_set); #ifdef HAVE_LIBGPM if (gpm_flag) { FD_SET (gpm_fd, &select_set); } #endif if (redo_event){ timeout.tv_usec = mou_auto_repeat * 1000; timeout.tv_sec = 0; time_addr = &timeout; } else { int seconds; if ((seconds = vfs_timeouts ())){ /* the timeout could be improved and actually be * the number of seconds until the next vfs entry * timeouts in the stamp list. */ timeout.tv_sec = seconds; timeout.tv_usec = 0; time_addr = &timeout; } else time_addr = NULL; } if (!block){ time_addr = &timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; } enable_interrupt_key (); flag = select (FD_SETSIZE, &select_set, NULL, NULL, time_addr); disable_interrupt_key (); /* select timed out: it could be for any of the following reasons: * redo_event -> it was because of the MOU_REPEAT handler * !block -> we did not block in the select call * else -> 10 second timeout to check the vfs status. */ if (flag == 0){ if (redo_event) return EV_MOUSE; if (!block) return EV_NONE; vfs_timeout_handler (); } if (flag == -1 && errno == EINTR) return EV_NONE; check_selects (&select_set); if (FD_ISSET (input_fd, &select_set)) break; } #ifdef HAVE_LIBGPM if (gpm_flag && FD_ISSET (gpm_fd, &select_set)){ if (gpm_flag){ Gpm_GetEvent (&ev); Gpm_FitEvent (&ev); } *event = ev; return EV_MOUSE; } #endif } # ifndef HAVE_SLANG flag = is_wintouched(stdscr); untouchwin (stdscr); # endif c = block ? getch_with_delay () : get_key_code(1); # ifndef HAVE_SLANG if (flag) touchwin (stdscr); # endif if (c == MCKEY_MOUSE) { /* Mouse event */ xmouse_get_event (event); return EV_MOUSE; } return c; #else return EV_NONE; #endif /* HAVE_X */ } #ifndef PORT_HAS_GETCH /* Returns a key press, mouse events are discarded */ int mi_getch () { Gpm_Event ev; int key; while ((key = get_event (&ev, 0, 1)) == 0) ; return key; } #endif int xgetch_second (void) { fd_set Read_FD_Set; int c; struct timeval timeout; timeout.tv_sec = ESCMODE_TIMEOUT / 1000000; timeout.tv_usec = ESCMODE_TIMEOUT % 1000000; #ifdef BUGGY_CURSES wtimeout(stdscr, 500); #else nodelay (stdscr, TRUE); #endif FD_ZERO (&Read_FD_Set); FD_SET (input_fd, &Read_FD_Set); select (FD_SETSIZE, &Read_FD_Set, NULL, NULL, &timeout); c = xgetch (); #ifdef BUGGY_CURSES notimeout (stdscr, TRUE); #else nodelay (stdscr, FALSE); #endif return c; } #ifndef HAVE_X void learn_store_key (char *buffer, char **p, int c) { if (*p - buffer > 253) return; if (c == ESC_CHAR) { *(*p)++ = '\\'; *(*p)++ = 'e'; } else if (c < ' ') { *(*p)++ = '^'; *(*p)++ = c + 'a' - 1; } else if (c == '^') { *(*p)++ = '^'; *(*p)++ = '^'; } else *(*p)++ = (char) c; } char *learn_key (void) { /* LEARN_TIMEOUT in usec */ #define LEARN_TIMEOUT 200000 fd_set Read_FD_Set; struct timeval endtime; struct timeval timeout; int c = xgetch (); char buffer [256]; char *p = buffer; while (c == ERR) c = xgetch (); /* Sanity check, should be unnecessary */ learn_store_key (buffer, &p, c); GET_TIME (endtime); endtime.tv_usec += LEARN_TIMEOUT; if (endtime.tv_usec > 1000000) { endtime.tv_usec -= 1000000; endtime.tv_sec++; } #ifdef BUGGY_CURSES wtimeout(stdscr, 500); #else nodelay (stdscr, TRUE); #endif for (;;) { while ((c = xgetch ()) == ERR) { GET_TIME (timeout); timeout.tv_usec = endtime.tv_usec - timeout.tv_usec; if (timeout.tv_usec < 0) timeout.tv_sec++; timeout.tv_sec = endtime.tv_sec - timeout.tv_sec; if (timeout.tv_sec >= 0 && timeout.tv_usec > 0) { FD_ZERO (&Read_FD_Set); FD_SET (input_fd, &Read_FD_Set); select (FD_SETSIZE, &Read_FD_Set, NULL, NULL, &timeout); } else break; } if (c == ERR) break; learn_store_key (buffer, &p, c); } #ifdef BUGGY_CURSES notimeout (stdscr, TRUE); #else nodelay (stdscr, FALSE); #endif *p = 0; return strdup (buffer); } /* xterm and linux console only: set keypad to numeric or application mode. Only in application keypad mode it's possible to distinguish the '+' key and the '+' on the keypad ('*' and '-' ditto)*/ void numeric_keypad_mode (void) { if (console_flag || xterm_flag) { fprintf (stdout, "\033>"); fflush (stdout); } } void application_keypad_mode (void) { if (console_flag || xterm_flag) { fprintf (stdout, "\033="); fflush (stdout); } } #endif /* !HAVE_X */ /* A function to check if we're idle. Currently checks only for key presses. We could also check the mouse. */ int is_idle (void) { /* Check for incoming key presses * * If there are any we say we're busy */ fd_set select_set; struct timeval timeout; FD_ZERO (&select_set); FD_SET (0, &select_set); timeout.tv_sec = 0; timeout.tv_usec = 0; select (FD_SETSIZE, &select_set, 0, 0, &timeout); return ! FD_ISSET (0, &select_set); } int get_modifier () { #ifdef __linux__ unsigned char modifiers; modifiers = 6; if (ioctl (0, TIOCLINUX, &modifiers) < 0) return 0; return (int) modifiers; #else return 0; #endif } int ctrl_pressed () { #ifdef __linux__ if (get_modifier () & CONTROL_PRESSED) return 1; #endif return 0; } #ifdef HAVE_MAD #ifndef HAVE_X void k_dispose (key_def *k) { if (!k) return; k_dispose (k->child); k_dispose (k->next); free (k); } void s_dispose (SelectList *sel) { if (!sel) return; s_dispose (sel->next); free (sel); } void done_key () { k_dispose (keys); s_dispose (select_list); } #else void done_key () { } #endif /* HAVE_X */ #endif /* HAVE_MAD */