641 lines
13 KiB
C++
641 lines
13 KiB
C++
|
// 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 <unistd.h>
|
||
|
#include <errno.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <signal.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <termios.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/file.h>
|
||
|
#include <sys/ioctl.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <gutlunix.h>
|
||
|
#include <gsigunix.h>
|
||
|
#include <gkbdunix.h>
|
||
|
|
||
|
|
||
|
// ------------------------------------------------------------------
|
||
|
|
||
|
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) {
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
|
||
|
// ------------------------------------------------------------------
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
|
||
|
// ------------------------------------------------------------------
|