Implemented full evdev keyboard text support

This is based on the Linux kernel driver, and has fallback mapping tables in case we aren't connected to a virtual terminal.
This commit is contained in:
Sam Lantinga
2017-01-09 02:54:42 -08:00
parent 49292705a9
commit e0a40fb6a9
7 changed files with 5681 additions and 184 deletions

View File

@@ -30,27 +30,21 @@
*/
#include "SDL_evdev.h"
#include "SDL_evdev_kbd.h"
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <limits.h> /* For the definition of PATH_MAX */
#include <linux/input.h>
#ifdef SDL_INPUT_LINUXKD
#include <linux/kd.h>
#include <linux/keyboard.h>
#include <linux/vt.h>
#include <linux/tiocl.h> /* for TIOCL_GETSHIFTSTATE */
#endif
#include "SDL.h"
#include "SDL_assert.h"
#include "SDL_endian.h"
#include "../../core/linux/SDL_udev.h"
#include "SDL_scancode.h"
#include "../../events/SDL_events_c.h"
#include "../../events/scancodes_linux.h" /* adds linux_scancode_table */
#include "../../core/linux/SDL_udev.h"
/* These are not defined in older Linux kernel headers */
#ifndef SYN_DROPPED
@@ -62,28 +56,25 @@
#define ABS_MT_POSITION_Y 0x36
#define ABS_MT_TRACKING_ID 0x39
#endif
#ifndef K_OFF
#define K_OFF 0x04
#endif
typedef struct SDL_evdevlist_item
{
char *path;
int fd;
/* TODO: use this for every device, not just touchscreen */
int out_of_sync;
/* TODO: expand on this to have data for every possible class (mouse,
keyboard, touchpad, etc.). Also there's probably some things in here we
can pull out to the SDL_evdevlist_item i.e. name */
int is_touchscreen;
struct {
char* name;
int min_x, max_x, range_x;
int min_y, max_y, range_y;
int max_slots;
int current_slot;
struct {
@@ -97,18 +88,17 @@ typedef struct SDL_evdevlist_item
int x, y;
} * slots;
} * touchscreen_data;
struct SDL_evdevlist_item *next;
} SDL_evdevlist_item;
typedef struct SDL_EVDEV_PrivateData
{
int ref_count;
int num_devices;
SDL_evdevlist_item *first;
SDL_evdevlist_item *last;
int num_devices;
int ref_count;
int console_fd;
int old_kb_mode;
SDL_EVDEV_keyboard_state *kbd;
} SDL_EVDEV_PrivateData;
#define _THIS SDL_EVDEV_PrivateData *_this
@@ -135,28 +125,6 @@ static Uint8 EVDEV_MouseButtons[] = {
SDL_BUTTON_X2 + 3 /* BTN_TASK 0x117 */
};
/* Prevent keystrokes from reaching the tty */
static int SDL_EVDEV_mute_keyboard(int tty_fd, int* old_kb_mode)
{
if (ioctl(tty_fd, KDGKBMODE, old_kb_mode) < 0) {
return SDL_SetError("Failed to get keyboard mode during muting");
}
if (ioctl(tty_fd, KDSKBMODE, K_OFF) < 0) {
return SDL_SetError("Failed to set keyboard mode during muting");
}
return 0;
}
/* Restore the keyboard mode for given tty */
static void SDL_EVDEV_unmute_keyboard(int tty_fd, int old_kb_mode)
{
if (ioctl(tty_fd, KDSKBMODE, old_kb_mode) < 0) {
SDL_SetError("Failed to set keyboard mode during unmuting");
}
}
int
SDL_EVDEV_Init(void)
{
@@ -180,25 +148,18 @@ SDL_EVDEV_Init(void)
_this = NULL;
return -1;
}
/* Force a scan to build the initial device list */
SDL_UDEV_Scan();
#else
/* TODO: Scan the devices manually, like a caveman */
#endif /* SDL_USE_LIBUDEV */
/* This might fail if we're not connected to a tty (e.g. on the Steam Link) */
_this->console_fd = open("/dev/tty", O_RDONLY);
/* Mute the keyboard so keystrokes only generate evdev events and do not
leak through to the console */
if (_this->console_fd >= 0) {
SDL_EVDEV_mute_keyboard(_this->console_fd, &_this->old_kb_mode);
}
_this->kbd = SDL_EVDEV_kbd_init();
}
_this->ref_count += 1;
return 0;
}
@@ -208,29 +169,26 @@ SDL_EVDEV_Quit(void)
if (_this == NULL) {
return;
}
_this->ref_count -= 1;
if (_this->ref_count < 1) {
#if SDL_USE_LIBUDEV
SDL_UDEV_DelCallback(SDL_EVDEV_udev_callback);
SDL_UDEV_Quit();
#endif /* SDL_USE_LIBUDEV */
if (_this->console_fd >= 0) {
SDL_EVDEV_unmute_keyboard(_this->console_fd, _this->old_kb_mode);
close(_this->console_fd);
}
SDL_EVDEV_kbd_quit(_this->kbd);
/* Remove existing devices */
while(_this->first != NULL) {
SDL_EVDEV_device_removed(_this->first->path);
}
SDL_assert(_this->first == NULL);
SDL_assert(_this->last == NULL);
SDL_assert(_this->num_devices == 0);
SDL_free(_this);
_this = NULL;
}
@@ -243,7 +201,7 @@ void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_class,
if (dev_path == NULL) {
return;
}
switch(udev_event) {
case SDL_UDEV_DEVICEADDED:
if (!(udev_class & (SDL_UDEV_DEVICE_MOUSE | SDL_UDEV_DEVICE_KEYBOARD |
@@ -261,89 +219,6 @@ void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_class,
}
#endif /* SDL_USE_LIBUDEV */
#ifdef SDL_INPUT_LINUXKD
/* this logic is pulled from kbd_keycode() in drivers/tty/vt/keyboard.c in the
Linux kernel source */
static void SDL_EVDEV_do_text_input(unsigned short keycode)
{
char shift_state;
int locks_state;
struct kbentry kbe;
unsigned char type;
char text[2] = { 0 };
if (_this->console_fd < 0)
return;
shift_state = TIOCL_GETSHIFTSTATE;
if (ioctl(_this->console_fd, TIOCLINUX, &shift_state) < 0) {
/* TODO: error */
return;
}
kbe.kb_table = shift_state;
kbe.kb_index = keycode;
if (ioctl(_this->console_fd, KDGKBENT, &kbe) < 0) {
/* TODO: error */
return;
}
if (kbe.kb_value == K_HOLE || kbe.kb_value == K_NOSUCHMAP) {
return;
}
kbe.kb_value ^= 0xf000;
type = KTYP(kbe.kb_value);
if (type < 0xf0) {
/*
* FIXME: keysyms with a type below 0xf0 represent a unicode character
* which requires special handling due to dead characters, diacritics,
* etc. For perfect input a proper way to deal with such characters
* should be implemented.
*
* For reference, the only place I was able to find out about this
* special 0xf0 value was in an unused? couple of patches listed below.
*
* http://ftp.tc.edu.tw/pub/docs/Unicode/utf8/linux-2.3.12-keyboard.diff
* http://ftp.tc.edu.tw/pub/docs/Unicode/utf8/linux-2.3.12-console.diff
*/
return;
}
type -= 0xf0;
/* if type is KT_LETTER then it can be affected by Caps Lock */
if (type == KT_LETTER) {
type = KT_LATIN;
if (ioctl(_this->console_fd, KDGKBLED, &locks_state) < 0) {
/* TODO: error */
return;
}
if (locks_state & K_CAPSLOCK) {
kbe.kb_table = shift_state ^ (1 << KG_SHIFT);
if (ioctl(_this->console_fd, KDGKBENT, &kbe) < 0) {
/* TODO: error */
return;
}
}
}
/* TODO: convert values >= 0x80 from ISO-8859-1? to UTF-8 */
if (type != KT_LATIN || KVAL(kbe.kb_value) >= 0x80)
return;
*text = KVAL(kbe.kb_value);
SDL_SendKeyboardText(text);
}
#endif /* SDL_INPUT_LINUXKD */
void
SDL_EVDEV_Poll(void)
{
@@ -375,7 +250,7 @@ SDL_EVDEV_Poll(void)
events[i].type == EV_SYN && events[i].code != SYN_REPORT) {
break;
}
switch (events[i].type) {
case EV_KEY:
if (events[i].code >= BTN_MOUSE && events[i].code < BTN_MOUSE + SDL_arraysize(EVDEV_MouseButtons)) {
@@ -395,11 +270,9 @@ SDL_EVDEV_Poll(void)
SDL_SendKeyboardKey(SDL_RELEASED, scan_code);
} else if (events[i].value == 1 || events[i].value == 2 /* key repeated */) {
SDL_SendKeyboardKey(SDL_PRESSED, scan_code);
#ifdef SDL_INPUT_LINUXKD
SDL_EVDEV_do_text_input(events[i].code);
#endif /* SDL_INPUT_LINUXKD */
}
}
SDL_EVDEV_kbd_keycode(_this->kbd, events[i].code, events[i].value);
break;
case EV_ABS:
switch(events[i].code) {
@@ -471,13 +344,13 @@ SDL_EVDEV_Poll(void)
case SYN_REPORT:
if (!item->is_touchscreen) /* FIXME: temp hack */
break;
for(j = 0; j < item->touchscreen_data->max_slots; j++) {
norm_x = (float)(item->touchscreen_data->slots[j].x - item->touchscreen_data->min_x) /
(float)item->touchscreen_data->range_x;
norm_y = (float)(item->touchscreen_data->slots[j].y - item->touchscreen_data->min_y) /
(float)item->touchscreen_data->range_y;
switch(item->touchscreen_data->slots[j].delta) {
case EVDEV_TOUCH_SLOTDELTA_DOWN:
SDL_SendTouch(item->fd, item->touchscreen_data->slots[j].tracking_id, SDL_TRUE, norm_x, norm_y, 1.0f);
@@ -496,7 +369,7 @@ SDL_EVDEV_Poll(void)
break;
}
}
if (item->out_of_sync)
item->out_of_sync = 0;
break;
@@ -539,26 +412,26 @@ SDL_EVDEV_init_touchscreen(SDL_evdevlist_item* item)
int ret, i;
char name[64];
struct input_absinfo abs_info;
if (!item->is_touchscreen)
return 0;
item->touchscreen_data = SDL_calloc(1, sizeof(*item->touchscreen_data));
if (item->touchscreen_data == NULL)
return SDL_OutOfMemory();
ret = ioctl(item->fd, EVIOCGNAME(sizeof(name)), name);
if (ret < 0) {
SDL_free(item->touchscreen_data);
return SDL_SetError("Failed to get evdev touchscreen name");
}
item->touchscreen_data->name = SDL_strdup(name);
if (item->touchscreen_data->name == NULL) {
SDL_free(item->touchscreen_data);
return SDL_OutOfMemory();
}
ret = ioctl(item->fd, EVIOCGABS(ABS_MT_POSITION_X), &abs_info);
if (ret < 0) {
SDL_free(item->touchscreen_data->name);
@@ -568,7 +441,7 @@ SDL_EVDEV_init_touchscreen(SDL_evdevlist_item* item)
item->touchscreen_data->min_x = abs_info.minimum;
item->touchscreen_data->max_x = abs_info.maximum;
item->touchscreen_data->range_x = abs_info.maximum - abs_info.minimum;
ret = ioctl(item->fd, EVIOCGABS(ABS_MT_POSITION_Y), &abs_info);
if (ret < 0) {
SDL_free(item->touchscreen_data->name);
@@ -578,7 +451,7 @@ SDL_EVDEV_init_touchscreen(SDL_evdevlist_item* item)
item->touchscreen_data->min_y = abs_info.minimum;
item->touchscreen_data->max_y = abs_info.maximum;
item->touchscreen_data->range_y = abs_info.maximum - abs_info.minimum;
ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info);
if (ret < 0) {
SDL_free(item->touchscreen_data->name);
@@ -586,7 +459,7 @@ SDL_EVDEV_init_touchscreen(SDL_evdevlist_item* item)
return SDL_SetError("Failed to get evdev touchscreen limits");
}
item->touchscreen_data->max_slots = abs_info.maximum + 1;
item->touchscreen_data->slots = SDL_calloc(
item->touchscreen_data->max_slots,
sizeof(*item->touchscreen_data->slots));
@@ -595,11 +468,11 @@ SDL_EVDEV_init_touchscreen(SDL_evdevlist_item* item)
SDL_free(item->touchscreen_data);
return SDL_OutOfMemory();
}
for(i = 0; i < item->touchscreen_data->max_slots; i++) {
item->touchscreen_data->slots[i].tracking_id = -1;
}
ret = SDL_AddTouch(item->fd, /* I guess our fd is unique enough */
item->touchscreen_data->name);
if (ret < 0) {
@@ -608,7 +481,7 @@ SDL_EVDEV_init_touchscreen(SDL_evdevlist_item* item)
SDL_free(item->touchscreen_data);
return ret;
}
return 0;
}
#endif /* SDL_USE_LIBUDEV */
@@ -617,7 +490,7 @@ static void
SDL_EVDEV_destroy_touchscreen(SDL_evdevlist_item* item) {
if (!item->is_touchscreen)
return;
SDL_DelTouch(item->fd);
SDL_free(item->touchscreen_data->slots);
SDL_free(item->touchscreen_data->name);
@@ -641,21 +514,21 @@ SDL_EVDEV_sync_device(SDL_evdevlist_item *item)
__u32* mt_req_code;
__s32* mt_req_values;
size_t mt_req_size;
/* TODO: sync devices other than touchscreen */
if (!item->is_touchscreen)
return;
mt_req_size = sizeof(*mt_req_code) +
sizeof(*mt_req_values) * item->touchscreen_data->max_slots;
mt_req_code = SDL_calloc(1, mt_req_size);
if (mt_req_code == NULL) {
return;
}
mt_req_values = (__s32*)mt_req_code + 1;
*mt_req_code = ABS_MT_TRACKING_ID;
ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);
if (ret < 0) {
@@ -682,7 +555,7 @@ SDL_EVDEV_sync_device(SDL_evdevlist_item *item)
item->touchscreen_data->slots[i].delta = EVDEV_TOUCH_SLOTDELTA_UP;
}
}
*mt_req_code = ABS_MT_POSITION_X;
ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);
if (ret < 0) {
@@ -700,7 +573,7 @@ SDL_EVDEV_sync_device(SDL_evdevlist_item *item)
}
}
}
*mt_req_code = ABS_MT_POSITION_Y;
ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);
if (ret < 0) {
@@ -718,14 +591,14 @@ SDL_EVDEV_sync_device(SDL_evdevlist_item *item)
}
}
}
ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info);
if (ret < 0) {
SDL_free(mt_req_code);
return;
}
item->touchscreen_data->current_slot = abs_info.value;
SDL_free(mt_req_code);
#endif /* EVIOCGMTSLOTS */
@@ -744,7 +617,7 @@ SDL_EVDEV_device_added(const char *dev_path, int udev_class)
return -1; /* already have this one */
}
}
item = (SDL_evdevlist_item *) SDL_calloc(1, sizeof (SDL_evdevlist_item));
if (item == NULL) {
return SDL_OutOfMemory();
@@ -755,33 +628,33 @@ SDL_EVDEV_device_added(const char *dev_path, int udev_class)
SDL_free(item);
return SDL_SetError("Unable to open %s", dev_path);
}
item->path = SDL_strdup(dev_path);
if (item->path == NULL) {
close(item->fd);
SDL_free(item);
return SDL_OutOfMemory();
}
if (udev_class & SDL_UDEV_DEVICE_TOUCHSCREEN) {
item->is_touchscreen = 1;
if ((ret = SDL_EVDEV_init_touchscreen(item)) < 0) {
close(item->fd);
SDL_free(item);
return ret;
}
}
if (_this->last == NULL) {
_this->first = _this->last = item;
} else {
_this->last->next = item;
_this->last = item;
}
SDL_EVDEV_sync_device(item);
return _this->num_devices++;
}
#endif /* SDL_USE_LIBUDEV */