| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| |
| #include "../../SDL_internal.h" |
| |
| #include "../linux/SDL_evdev_kbd.h" |
| #include "SDL_hints.h" |
| |
| #ifdef SDL_INPUT_FBSDKBIO |
| |
| /* This logic is adapted from drivers/tty/vt/keyboard.c in the Linux kernel source, slightly modified to work with FreeBSD */ |
| |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/kbio.h> |
| #include <sys/consio.h> |
| |
| #include <signal.h> |
| |
| #include "../../events/SDL_events_c.h" |
| #include "SDL_evdev_kbd_default_keyaccmap.h" |
| |
| typedef void (fn_handler_fn)(SDL_EVDEV_keyboard_state *kbd); |
| |
| /* |
| * Keyboard State |
| */ |
| |
| struct SDL_EVDEV_keyboard_state |
| { |
| int console_fd; |
| int keyboard_fd; |
| unsigned long old_kbd_mode; |
| unsigned short **key_maps; |
| keymap_t* key_map; |
| keyboard_info_t* kbInfo; |
| unsigned char shift_down[4]; /* shift state counters.. */ |
| SDL_bool dead_key_next; |
| int npadch; /* -1 or number assembled on pad */ |
| accentmap_t *accents; |
| unsigned int diacr; |
| SDL_bool rep; /* flag telling character repeat */ |
| unsigned char lockstate; |
| unsigned char ledflagstate; |
| char shift_state; |
| char text[128]; |
| unsigned int text_len; |
| }; |
| |
| static int SDL_EVDEV_kbd_load_keymaps(SDL_EVDEV_keyboard_state *kbd) |
| { |
| return (ioctl(kbd->keyboard_fd, GIO_KEYMAP, kbd->key_map) >= 0); |
| } |
| |
| static SDL_EVDEV_keyboard_state * kbd_cleanup_state = NULL; |
| static int kbd_cleanup_sigactions_installed = 0; |
| static int kbd_cleanup_atexit_installed = 0; |
| |
| static struct sigaction old_sigaction[NSIG]; |
| |
| static int fatal_signals[] = |
| { |
| /* Handlers for SIGTERM and SIGINT are installed in SDL_QuitInit. */ |
| SIGHUP, SIGQUIT, SIGILL, SIGABRT, |
| SIGFPE, SIGSEGV, SIGPIPE, SIGBUS, |
| SIGSYS |
| }; |
| |
| static void kbd_cleanup(void) |
| { |
| struct mouse_info mData; |
| SDL_EVDEV_keyboard_state* kbd = kbd_cleanup_state; |
| if (kbd == NULL) { |
| return; |
| } |
| kbd_cleanup_state = NULL; |
| SDL_zero(mData); |
| mData.operation = MOUSE_SHOW; |
| ioctl(kbd->keyboard_fd, KDSKBMODE, kbd->old_kbd_mode); |
| if (kbd->keyboard_fd != kbd->console_fd) close(kbd->keyboard_fd); |
| ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index)); |
| ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); |
| } |
| |
| void |
| SDL_EVDEV_kbd_reraise_signal(int sig) |
| { |
| raise(sig); |
| } |
| |
| siginfo_t* SDL_EVDEV_kdb_cleanup_siginfo = NULL; |
| void* SDL_EVDEV_kdb_cleanup_ucontext = NULL; |
| |
| static void kbd_cleanup_signal_action(int signum, siginfo_t* info, void* ucontext) |
| { |
| struct sigaction* old_action_p = &(old_sigaction[signum]); |
| sigset_t sigset; |
| |
| /* Restore original signal handler before going any further. */ |
| sigaction(signum, old_action_p, NULL); |
| |
| /* Unmask current signal. */ |
| sigemptyset(&sigset); |
| sigaddset(&sigset, signum); |
| sigprocmask(SIG_UNBLOCK, &sigset, NULL); |
| |
| /* Save original signal info and context for archeologists. */ |
| SDL_EVDEV_kdb_cleanup_siginfo = info; |
| SDL_EVDEV_kdb_cleanup_ucontext = ucontext; |
| |
| /* Restore keyboard. */ |
| kbd_cleanup(); |
| |
| /* Reraise signal. */ |
| SDL_EVDEV_kbd_reraise_signal(signum); |
| } |
| |
| static void kbd_unregister_emerg_cleanup() |
| { |
| int tabidx, signum; |
| |
| kbd_cleanup_state = NULL; |
| |
| if (!kbd_cleanup_sigactions_installed) { |
| return; |
| } |
| kbd_cleanup_sigactions_installed = 0; |
| |
| for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { |
| struct sigaction* old_action_p; |
| struct sigaction cur_action; |
| signum = fatal_signals[tabidx]; |
| old_action_p = &(old_sigaction[signum]); |
| |
| /* Examine current signal action */ |
| if (sigaction(signum, NULL, &cur_action)) |
| continue; |
| |
| /* Check if action installed and not modifed */ |
| if (!(cur_action.sa_flags & SA_SIGINFO) |
| || cur_action.sa_sigaction != &kbd_cleanup_signal_action) |
| continue; |
| |
| /* Restore original action */ |
| sigaction(signum, old_action_p, NULL); |
| } |
| } |
| |
| static void kbd_cleanup_atexit(void) |
| { |
| /* Restore keyboard. */ |
| kbd_cleanup(); |
| |
| /* Try to restore signal handlers in case shared library is being unloaded */ |
| kbd_unregister_emerg_cleanup(); |
| } |
| |
| static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state * kbd) |
| { |
| int tabidx, signum; |
| |
| if (kbd_cleanup_state != NULL) { |
| return; |
| } |
| kbd_cleanup_state = kbd; |
| |
| if (!kbd_cleanup_atexit_installed) { |
| /* Since glibc 2.2.3, atexit() (and on_exit(3)) can be used within a shared library to establish |
| * functions that are called when the shared library is unloaded. |
| * -- man atexit(3) |
| */ |
| atexit(kbd_cleanup_atexit); |
| kbd_cleanup_atexit_installed = 1; |
| } |
| |
| if (kbd_cleanup_sigactions_installed) { |
| return; |
| } |
| kbd_cleanup_sigactions_installed = 1; |
| |
| for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) { |
| struct sigaction* old_action_p; |
| struct sigaction new_action; |
| signum = fatal_signals[tabidx]; |
| old_action_p = &(old_sigaction[signum]); |
| if (sigaction(signum, NULL, old_action_p)) |
| continue; |
| |
| /* Skip SIGHUP and SIGPIPE if handler is already installed |
| * - assume the handler will do the cleanup |
| */ |
| if ((signum == SIGHUP || signum == SIGPIPE) |
| && (old_action_p->sa_handler != SIG_DFL |
| || (void (*)(int))old_action_p->sa_sigaction != SIG_DFL)) |
| continue; |
| |
| new_action = *old_action_p; |
| new_action.sa_flags |= SA_SIGINFO; |
| new_action.sa_sigaction = &kbd_cleanup_signal_action; |
| sigaction(signum, &new_action, NULL); |
| } |
| } |
| |
| SDL_EVDEV_keyboard_state * |
| SDL_EVDEV_kbd_init(void) |
| { |
| SDL_EVDEV_keyboard_state *kbd; |
| struct mouse_info mData; |
| char flag_state; |
| char* devicePath; |
| |
| SDL_zero(mData); |
| mData.operation = MOUSE_HIDE; |
| kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(SDL_EVDEV_keyboard_state)); |
| if (!kbd) { |
| return NULL; |
| } |
| |
| kbd->npadch = -1; |
| |
| /* This might fail if we're not connected to a tty (e.g. on the Steam Link) */ |
| kbd->keyboard_fd = kbd->console_fd = open("/dev/tty", O_RDONLY); |
| |
| kbd->shift_state = 0; |
| |
| kbd->accents = SDL_calloc(sizeof(accentmap_t), 1); |
| kbd->key_map = SDL_calloc(sizeof(keymap_t), 1); |
| kbd->kbInfo = SDL_calloc(sizeof(keyboard_info_t), 1); |
| |
| ioctl(kbd->console_fd, KDGKBINFO, kbd->kbInfo); |
| ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); |
| |
| if (ioctl(kbd->console_fd, KDGKBSTATE, &flag_state) == 0) { |
| kbd->ledflagstate = flag_state; |
| } |
| |
| if (ioctl(kbd->console_fd, GIO_DEADKEYMAP, kbd->accents) < 0) |
| { |
| SDL_free(kbd->accents); |
| kbd->accents = &accentmap_default_us_acc; |
| } |
| |
| if (ioctl(kbd->console_fd, KDGKBMODE, &kbd->old_kbd_mode) == 0) { |
| /* Set the keyboard in XLATE mode and load the keymaps */ |
| ioctl(kbd->console_fd, KDSKBMODE, (unsigned long)(K_XLATE)); |
| if(!SDL_EVDEV_kbd_load_keymaps(kbd)) |
| { |
| SDL_free(kbd->key_map); |
| kbd->key_map = &keymap_default_us_acc; |
| } |
| /* Allow inhibiting keyboard mute with env. variable for debugging etc. */ |
| if (getenv("SDL_INPUT_FREEBSD_KEEP_KBD") == NULL) { |
| /* Take keyboard from console and open the actual keyboard device. |
| * Ensures that the keystrokes do not leak through to the console. |
| */ |
| ioctl(kbd->console_fd, CONS_RELKBD, 1ul); |
| asprintf(&devicePath, "/dev/kbd%d", kbd->kbInfo->kb_index); |
| kbd->keyboard_fd = open(devicePath, O_WRONLY); |
| if (kbd->keyboard_fd == -1) |
| { |
| // Give keyboard back. |
| ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index)); |
| kbd->keyboard_fd = kbd->console_fd; |
| } |
| |
| /* Make sure to restore keyboard if application fails to call |
| * SDL_Quit before exit or fatal signal is raised. |
| */ |
| if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, SDL_FALSE)) { |
| kbd_register_emerg_cleanup(kbd); |
| } |
| free(devicePath); |
| } |
| else kbd->keyboard_fd = kbd->console_fd; |
| } |
| |
| return kbd; |
| } |
| |
| void |
| SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd) |
| { |
| struct mouse_info mData; |
| |
| if (!kbd) { |
| return; |
| } |
| SDL_zero(mData); |
| mData.operation = MOUSE_SHOW; |
| ioctl(kbd->console_fd, CONS_MOUSECTL, &mData); |
| |
| kbd_unregister_emerg_cleanup(); |
| |
| if (kbd->keyboard_fd >= 0) { |
| /* Restore the original keyboard mode */ |
| ioctl(kbd->keyboard_fd, KDSKBMODE, kbd->old_kbd_mode); |
| |
| close(kbd->keyboard_fd); |
| if (kbd->console_fd != kbd->keyboard_fd && kbd->console_fd >= 0) |
| { |
| // Give back keyboard. |
| ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index)); |
| } |
| kbd->console_fd = kbd->keyboard_fd = -1; |
| } |
| |
| SDL_free(kbd); |
| } |
| |
| /* |
| * Helper Functions. |
| */ |
| static void put_queue(SDL_EVDEV_keyboard_state *kbd, uint c) |
| { |
| /* c is already part of a UTF-8 sequence and safe to add as a character */ |
| if (kbd->text_len < (sizeof(kbd->text)-1)) { |
| kbd->text[kbd->text_len++] = (char)c; |
| } |
| } |
| |
| static void put_utf8(SDL_EVDEV_keyboard_state *kbd, uint c) |
| { |
| if (c < 0x80) |
| /* 0******* */ |
| put_queue(kbd, c); |
| else if (c < 0x800) { |
| /* 110***** 10****** */ |
| put_queue(kbd, 0xc0 | (c >> 6)); |
| put_queue(kbd, 0x80 | (c & 0x3f)); |
| } else if (c < 0x10000) { |
| if (c >= 0xD800 && c < 0xE000) |
| return; |
| if (c == 0xFFFF) |
| return; |
| /* 1110**** 10****** 10****** */ |
| put_queue(kbd, 0xe0 | (c >> 12)); |
| put_queue(kbd, 0x80 | ((c >> 6) & 0x3f)); |
| put_queue(kbd, 0x80 | (c & 0x3f)); |
| } else if (c < 0x110000) { |
| /* 11110*** 10****** 10****** 10****** */ |
| put_queue(kbd, 0xf0 | (c >> 18)); |
| put_queue(kbd, 0x80 | ((c >> 12) & 0x3f)); |
| put_queue(kbd, 0x80 | ((c >> 6) & 0x3f)); |
| put_queue(kbd, 0x80 | (c & 0x3f)); |
| } |
| } |
| |
| /* |
| * We have a combining character DIACR here, followed by the character CH. |
| * If the combination occurs in the table, return the corresponding value. |
| * Otherwise, if CH is a space or equals DIACR, return DIACR. |
| * Otherwise, conclude that DIACR was not combining after all, |
| * queue it and return CH. |
| */ |
| static unsigned int handle_diacr(SDL_EVDEV_keyboard_state *kbd, unsigned int ch) |
| { |
| unsigned int d = kbd->diacr; |
| unsigned int i, j; |
| |
| kbd->diacr = 0; |
| |
| for (i = 0; i < kbd->accents->n_accs; i++) { |
| if (kbd->accents->acc[i].accchar == d) |
| { |
| for (j = 0; j < NUM_ACCENTCHARS; ++j) { |
| if (kbd->accents->acc[i].map[j][0] == 0) /* end of table */ |
| break; |
| if (kbd->accents->acc[i].map[j][0] == ch) |
| return kbd->accents->acc[i].map[j][1]; |
| } |
| } |
| } |
| |
| if (ch == ' ' || ch == d) { |
| put_utf8(kbd, d); |
| return 0; |
| } |
| put_utf8(kbd, d); |
| |
| return ch; |
| } |
| |
| static int vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag) |
| { |
| return (kbd->ledflagstate & flag) != 0; |
| } |
| |
| static void chg_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag) |
| { |
| kbd->ledflagstate ^= flag; |
| ioctl(kbd->keyboard_fd, KDSKBSTATE, (unsigned long)(kbd->ledflagstate)); |
| } |
| |
| /* |
| * Special function handlers |
| */ |
| |
| static void k_self(SDL_EVDEV_keyboard_state *kbd, unsigned int value, char up_flag) |
| { |
| if (up_flag) |
| return; /* no action, if this is a key release */ |
| |
| if (kbd->diacr) |
| value = handle_diacr(kbd, value); |
| |
| if (kbd->dead_key_next) { |
| kbd->dead_key_next = SDL_FALSE; |
| kbd->diacr = value; |
| return; |
| } |
| put_utf8(kbd, value); |
| } |
| |
| static void k_deadunicode(SDL_EVDEV_keyboard_state *kbd, unsigned int value, char up_flag) |
| { |
| if (up_flag) |
| return; |
| |
| kbd->diacr = (kbd->diacr ? handle_diacr(kbd, value) : value); |
| } |
| |
| static void k_shift(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag) |
| { |
| int old_state = kbd->shift_state; |
| |
| if (kbd->rep) |
| return; |
| |
| if (up_flag) { |
| /* |
| * handle the case that two shift or control |
| * keys are depressed simultaneously |
| */ |
| if (kbd->shift_down[value]) |
| kbd->shift_down[value]--; |
| } else |
| kbd->shift_down[value]++; |
| |
| if (kbd->shift_down[value]) |
| kbd->shift_state |= (1 << value); |
| else |
| kbd->shift_state &= ~(1 << value); |
| |
| /* kludge */ |
| if (up_flag && kbd->shift_state != old_state && kbd->npadch != -1) { |
| put_utf8(kbd, kbd->npadch); |
| kbd->npadch = -1; |
| } |
| } |
| |
| void |
| SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *kbd, unsigned int keycode, int down) |
| { |
| keymap_t key_map; |
| struct keyent_t keysym; |
| unsigned int final_key_state; |
| unsigned int map_from_key_sym; |
| |
| key_map = *kbd->key_map; |
| |
| if (!kbd) { |
| return; |
| } |
| |
| kbd->rep = (down == 2); |
| |
| if (keycode < NUM_KEYS) { |
| if (keycode >= 89 && keycode <= 95) { |
| /* These constitute unprintable language-related keys, so ignore them. */ |
| return; |
| } |
| if (keycode > 95) |
| keycode -= 7; |
| if (vc_kbd_led(kbd, ALKED) || (kbd->shift_state & 0x8)) |
| { |
| keycode += ALTGR_OFFSET; |
| } |
| keysym = key_map.key[keycode]; |
| } else { |
| return; |
| } |
| |
| final_key_state = kbd->shift_state & 0x7; |
| if ((keysym.flgs & FLAG_LOCK_C) && vc_kbd_led(kbd, LED_CAP)) |
| final_key_state ^= 0x1; |
| if ((keysym.flgs & FLAG_LOCK_N) && vc_kbd_led(kbd, LED_NUM)) |
| final_key_state ^= 0x1; |
| |
| map_from_key_sym = keysym.map[final_key_state]; |
| if ((keysym.spcl & (0x80 >> final_key_state)) || (map_from_key_sym & SPCLKEY)) { |
| /* Special function.*/ |
| if (map_from_key_sym == 0) |
| return; /* Nothing to do. */ |
| if (map_from_key_sym & SPCLKEY) |
| map_from_key_sym &= ~SPCLKEY; |
| if (map_from_key_sym >= F_ACC && map_from_key_sym <= L_ACC) { |
| /* Accent function.*/ |
| unsigned int accent_index = map_from_key_sym - F_ACC; |
| if (kbd->accents->acc[accent_index].accchar != 0) { |
| k_deadunicode(kbd, kbd->accents->acc[accent_index].accchar, !down); |
| } |
| } else { |
| switch(map_from_key_sym) { |
| case ASH: /* alt/meta shift */ |
| k_shift(kbd, 3, down == 0); |
| break; |
| case LSHA: /* left shift + alt lock */ |
| case RSHA: /* right shift + alt lock */ |
| if (down == 0) chg_vc_kbd_led(kbd, ALKED); |
| case LSH: /* left shift */ |
| case RSH: /* right shift */ |
| k_shift(kbd, 0, down == 0); |
| break; |
| case LCTRA: /* left ctrl + alt lock */ |
| case RCTRA: /* right ctrl + alt lock */ |
| if (down == 0) chg_vc_kbd_led(kbd, ALKED); |
| case LCTR: /* left ctrl */ |
| case RCTR: /* right ctrl */ |
| k_shift(kbd, 1, down == 0); |
| break; |
| case LALTA: /* left alt + alt lock */ |
| case RALTA: /* right alt + alt lock */ |
| if (down == 0) chg_vc_kbd_led(kbd, ALKED); |
| case LALT: /* left alt */ |
| case RALT: /* right alt */ |
| k_shift(kbd, 2, down == 0); |
| break; |
| case ALK: /* alt lock */ |
| if (down == 1) chg_vc_kbd_led(kbd, ALKED); |
| break; |
| case CLK: /* caps lock*/ |
| if (down == 1) chg_vc_kbd_led(kbd, CLKED); |
| break; |
| case NLK: /* num lock */ |
| if (down == 1) chg_vc_kbd_led(kbd, NLKED); |
| break; |
| case SLK: /* scroll lock */ |
| if (down == 1) chg_vc_kbd_led(kbd, SLKED); |
| break; |
| default: |
| return; |
| } |
| } |
| } else { |
| if (map_from_key_sym == '\n' || map_from_key_sym == '\r') { |
| if (kbd->diacr) { |
| kbd->diacr = 0; |
| return; |
| } |
| } |
| if (map_from_key_sym >= ' ' && map_from_key_sym != 127) { |
| k_self(kbd, map_from_key_sym, !down); |
| } |
| } |
| |
| if (kbd->text_len > 0) { |
| kbd->text[kbd->text_len] = '\0'; |
| SDL_SendKeyboardText(kbd->text); |
| kbd->text_len = 0; |
| } |
| } |
| |
| #endif /* SDL_INPUT_FBSDKBIO */ |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |