// This may look like C code, but it is really -*- C++ -*- // ------------------------------------------------------------------ // The Goldware Library // Copyright (C) 1990-1999 Odinn Sorensen // ------------------------------------------------------------------ // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library 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 // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this program; if not, write to the Free // Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, // MA 02111-1307, USA // ------------------------------------------------------------------ // $Id$ // ------------------------------------------------------------------ // Unix keyboard functions. Based on SLang source code. // ------------------------------------------------------------------ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __BEOS__ //sz: some undocumented call that behaves in same manner as select ... //used in BEOS_BONE builds ... extern "C" int waiton( int, fd_set *, fd_set *, fd_set *, bigtime_t); #endif // ------------------------------------------------------------------ int gkbd_stdin = -1; // ------------------------------------------------------------------ static termios gkbd_oldtty; // ------------------------------------------------------------------ static bool gkbd_tty_inited = false; static bool gkbd_tty_open = false; // ------------------------------------------------------------------ int gkbd_tty_init() { gsig_block_signals(); gkbd_tty_open = false; if((gkbd_stdin == -1) or (isatty(gkbd_stdin) != 1)) { gkbd_stdin = open("/dev/tty", O_RDWR); if(gkbd_stdin >= 0) gkbd_tty_open = true; if(not gkbd_tty_open) { gkbd_stdin = fileno(stderr); if(isatty(gkbd_stdin) != 1) { gkbd_stdin = fileno (stdin); if(isatty(gkbd_stdin) != 1) return -1; } } } while(GET_TERMIOS(gkbd_stdin, &gkbd_oldtty) == -1) { if(errno != EINTR) { gsig_unblock_signals(); return -1; } } termios newtty; while(GET_TERMIOS(gkbd_stdin, &newtty) == -1) { if(errno != EINTR) { gsig_unblock_signals(); return -1; } } newtty.c_oflag &= ~OPOST; newtty.c_lflag = ISIG | NOFLSH; newtty.c_iflag &= ~(ECHO | INLCR | ICRNL | IXON); newtty.c_cc[VMIN] = 1; newtty.c_cc[VEOF] = 1; newtty.c_cc[VTIME] = 0; newtty.c_cc[VINTR] = NULL_VALUE; newtty.c_cc[VQUIT] = NULL_VALUE; newtty.c_cc[VSUSP] = NULL_VALUE; while(SET_TERMIOS(gkbd_stdin, &newtty) == -1) { if(errno != EINTR) { gsig_unblock_signals(); return -1; } } gkbd_tty_inited = true; gsig_unblock_signals(); return 0; } // ------------------------------------------------------------------ void gkbd_tty_reset() { gsig_block_signals(); if(not gkbd_tty_inited) { gsig_unblock_signals(); return; } while((SET_TERMIOS(gkbd_stdin, &gkbd_oldtty) == -1) and (errno == EINTR)) ; if(gkbd_tty_open) { while((close(gkbd_stdin) == -1) and (errno == EINTR)) ; gkbd_tty_open = false; gkbd_stdin = -1; } gkbd_tty_inited = false; gsig_unblock_signals(); } // ------------------------------------------------------------------ int gkbd_sys_input_pending(int tsecs) { #if !defined(__BEOS__) static fd_set read_fd_set; struct timeval wait; long usecs, secs; if(tsecs >= 0) { secs = tsecs / 10; usecs = (tsecs % 10) * 100000; } else { tsecs = -tsecs; secs = tsecs / 1000; usecs = (tsecs % 1000) * 1000; } wait.tv_sec = secs; wait.tv_usec = usecs; FD_ZERO(&read_fd_set); FD_SET(gkbd_stdin, &read_fd_set); return select(gkbd_stdin+1, &read_fd_set, NULL, NULL, &wait); #else // BeOS input handling ... #if defined(BEOS_BONE_BUILD) static fd_set read_fd_set; FD_ZERO(&read_fd_set); FD_SET(gkbd_stdin, &read_fd_set); return waiton(gkbd_stdin+1, &read_fd_set, NULL, NULL, 0); #else // not a BEOS_BONE_BUILD - use classical input check scheme ... struct termios term, oterm; // get the terminal settings tcgetattr(gkbd_stdin, &oterm); // get a copy of the settings, which we modify memcpy(&term, &oterm, sizeof(term)); // put the terminal in non-canonical mode, any // reads timeout after 0.1 seconds or when a // single character is read term.c_lflag = term.c_lflag & (!ICANON); term.c_cc[VMIN] = 0; term.c_cc[VTIME] = 1; tcsetattr(gkbd_stdin, TCSANOW, &term); //check input int bytes = -1; ioctl(gkbd_stdin, TCWAITEVENT, &bytes); // reset the terminal to original state tcsetattr(gkbd_stdin, TCSANOW, &oterm); return bytes; #endif //defined(BEOS_BONE_BUILD) #endif } // ------------------------------------------------------------------ uint gkbd_sys_getkey() { while(1) { int ret = gkbd_sys_input_pending(100); if(ret == 0) continue; if(ret != -1) break; if(errno == EINTR) continue; break; } char c; while(read(gkbd_stdin, &c, 1) == -1) { if(errno == EINTR) continue; if(errno == EAGAIN) { sleep(1); continue; } #ifndef __DJGPP__ if(errno == EWOULDBLOCK) { sleep(1); continue; } #endif return (uint)-1; } return (uint)c; } // ------------------------------------------------------------------ uint gkbd_input_buffer_len = 0; char gkbd_input_buffer[GKBD_MAX_INPUT_BUFFER_LEN]; // ------------------------------------------------------------------ uint gkbd_getkey() { uint imax; uint ch; if(gkbd_input_buffer_len) { ch = (uint)*gkbd_input_buffer; gkbd_input_buffer_len--; imax = gkbd_input_buffer_len; memcpy(gkbd_input_buffer, gkbd_input_buffer+1, imax); } else { ch = gkbd_sys_getkey(); } return ch; } // ------------------------------------------------------------------ void gkbd_ungetkey_string(char *s, uint n) { char* bmax; char* b1; char* b; if(gkbd_input_buffer_len + n + 3 > GKBD_MAX_INPUT_BUFFER_LEN) return; b = gkbd_input_buffer; bmax = (b - 1) + gkbd_input_buffer_len; b1 = bmax + n; while(bmax >= b) *b1-- = *bmax--; bmax = b + n; while(b < bmax) *b++ = *s++; gkbd_input_buffer_len += n; } // ------------------------------------------------------------------ int gkbd_input_pending(int tsecs) { if(gkbd_input_buffer_len) return gkbd_input_buffer_len; int n = gkbd_sys_input_pending(tsecs); if(n <= 0) return 0; char c = (char)gkbd_getkey(); gkbd_ungetkey_string(&c, 1); return n; } // ------------------------------------------------------------------ void gkbd_flush_input() { gkbd_input_buffer_len = 0; while(gkbd_sys_input_pending(0) > 0) gkbd_sys_getkey(); } // ------------------------------------------------------------------ struct gkbd_key_type { char str[7]; bool active; uint keysym; gkbd_key_type* next; }; // ------------------------------------------------------------------ #define UPPER_CASE_KEY(x) (((x) >= 'a') and ((x) <= 'z') ? (x) - 32 : (x)) #define LOWER_CASE_KEY(x) (((x) >= 'A') and ((x) <= 'Z') ? (x) + 32 : (x)) // ------------------------------------------------------------------ gkbd_key_type* gkbd_keymap = NULL; // ------------------------------------------------------------------ void gkbd_keymap_init() { gkbd_keymap = (gkbd_key_type*)calloc(256, sizeof(gkbd_key_type)); } // ------------------------------------------------------------------ void gkbd_keymap_reset() { if(gkbd_keymap) { for(int n=0; n<256; n++) { gkbd_key_type* next = gkbd_keymap[n].next; while(next) { gkbd_key_type* current = next; next = current->next; free(current); } } free(gkbd_keymap); gkbd_keymap = NULL; } } // ------------------------------------------------------------------ static gkbd_key_type* malloc_key(char *str) { gkbd_key_type* key = (gkbd_key_type*)malloc(sizeof(gkbd_key_type)); memset(key, 0, sizeof(gkbd_key_type)); memcpy(key->str, str, *str); return key; } // ------------------------------------------------------------------ // Convert things like "^A" to 1 etc... The 0th char is the strlen // INCLUDING the length character itself. static char* gkbd_process_keystring(char* s) { static char str[32]; char ch; int i = 1; while(*s != 0) { ch = *s++; if(ch == '^') { ch = *s++; if(ch == 0) { if(i < 32) str[i++] = '^'; break; } ch = UPPER_CASE_KEY(ch); if(ch == '?') ch = 127; else ch = ch - 'A' + 1; } if(i >= 32) break; str[i++] = ch; } str[0] = i; return str; } // ------------------------------------------------------------------ static int key_string_compare(char* a, char* b, uint len) { char* amax = a + len; while(a < amax) { int cha = *a++; int chb = *b++; if(cha == chb) continue; int cha_up = UPPER_CASE_KEY(cha); int chb_up = UPPER_CASE_KEY(chb); if(cha_up == chb_up) { // Use case-sensitive result. return cha - chb; } // Use case-insensitive result. return cha_up - chb_up; } return 0; } // ------------------------------------------------------------------ // This function also performs an insertion in an ordered way. static int find_the_key(char* s, gkbd_key_type** keyp) { *keyp = NULL; char* str = gkbd_process_keystring(s); uint str_len = str[0]; if(str_len == 1) return 0; char ch = str[1]; gkbd_key_type* key = gkbd_keymap + ch; if(str_len == 2) { if(key->next) return -2; key->str[0] = str_len; key->str[1] = ch; *keyp = key; return 0; } // insert the key definition while(1) { gkbd_key_type* last = key; key = key->next; if(key and key->str) { uint key_len = key->str[0]; uint len = key_len; if(len > str_len) len = str_len; int cmp = key_string_compare(str+1, key->str+1, len-1); if(cmp > 0) continue; if(cmp == 0) { if(key_len != str_len) return -2; *keyp = key; return 0; } // Drop to cmp < 0 case } gkbd_key_type* neew = malloc_key(str); neew->next = key; last->next = neew; *keyp = neew; return 0; } } // ------------------------------------------------------------------ int gkbd_define_keysym(char* s, uint keysym) { gkbd_key_type* key; int ret = find_the_key(s, &key); if((ret != 0) or (key == NULL)) return ret; key->active = true; key->keysym = keysym; return 0; } // ------------------------------------------------------------------ uint gkbd_last_key_char = 0; static gkbd_key_type* gkbd_do_key() { gkbd_last_key_char = gkbd_getkey(); if(gkbd_last_key_char == (uint)-1) return NULL; char input_ch = (char)gkbd_last_key_char; gkbd_key_type* key = gkbd_keymap + input_ch; // if the next one is null, then we know this MAY be it. while(key->next == NULL) { if(key->active) return key; // Try its opposite case counterpart char chlow = LOWER_CASE_KEY(input_ch); if(input_ch == chlow) input_ch = UPPER_CASE_KEY(input_ch); key = gkbd_keymap + input_ch; if(not key->active) return NULL; } if(gkbd_input_pending(1) == 0) if(key->active) return key; // It appears to be a prefix character in a key sequence. uint len = 1; // already read one character key = key->next; // Now we are in the key list gkbd_key_type* kmax = NULL; // set to end of list gkbd_key_type* next; while(1) { gkbd_last_key_char = gkbd_getkey(); len++; if(gkbd_last_key_char == (uint)-1) break; input_ch = (char)gkbd_last_key_char; char key_ch = 0; char chup = UPPER_CASE_KEY(input_ch); while(key != kmax) { if(key->str[0] > len) { key_ch = key->str[len]; if(chup == UPPER_CASE_KEY(key_ch)) break; } key = key->next; } if(key == kmax) break; // If the input character is lowercase, check to see if there is // a lowercase match. If so, set key to it. Note: the // algorithm assumes the sorting performed by key_string_compare. if(input_ch != key_ch) { next = key->next; while(next != kmax) { if(next->str[0] > len) { char next_ch = next->str[len]; if(next_ch == input_ch) { key = next; break; } if(next_ch != chup) break; } next = next->next; } } // Ok, we found the first position of a possible match. If it // is exact, we are done. if(key->str[0] == len + 1) return key; // Apparently, there are some ambiguities. Read next key to // resolve the ambiguity. Adjust kmax to encompass ambiguities. next = key->next; while(next != kmax) { if(next->str[0] > len) { char key_ch = next->str[len]; if(chup != UPPER_CASE_KEY(key_ch)) break; } next = next->next; } kmax = next; } return NULL; } // ------------------------------------------------------------------ uint gkbd_getmappedkey() { gkbd_key_type* key = gkbd_do_key(); if((key == NULL) or (not key->active)) { gkbd_flush_input(); return (uint)-1; } return key->keysym; } // ------------------------------------------------------------------