blob: fa4b30862510347ec843718b5d84409237e83863 [file] [log] [blame]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "parameters.h"
#include "log.h"
#include "strfmt.h"
#include "file.h"
#include "system_linux.h"
#include "kbd.h"
#include "kbd_internal.h"
#ifdef HAVE_LINUX_UINPUT_H
#include <limits.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include "bitmask.h"
#include "async_handle.h"
#include "async_alarm.h"
#include "async_io.h"
struct KeyboardMonitorExtensionStruct {
struct {
int socket;
AsyncHandle monitor;
} uevent;
};
struct KeyboardInstanceExtensionStruct {
UinputObject *uinput;
AsyncHandle udevDelay;
struct {
int descriptor;
AsyncHandle monitor;
} file;
struct {
char *path;
int major;
int minor;
} device;
};
BEGIN_KEY_CODE_MAP
[KEY_ESC] = KBD_KEY_ACTION(Escape),
[KEY_1] = KBD_KEY_NUMBER(One),
[KEY_2] = KBD_KEY_NUMBER(Two),
[KEY_3] = KBD_KEY_NUMBER(Three),
[KEY_4] = KBD_KEY_NUMBER(Four),
[KEY_5] = KBD_KEY_NUMBER(Five),
[KEY_6] = KBD_KEY_NUMBER(Six),
[KEY_7] = KBD_KEY_NUMBER(Seven),
[KEY_8] = KBD_KEY_NUMBER(Eight),
[KEY_9] = KBD_KEY_NUMBER(Nine),
[KEY_0] = KBD_KEY_NUMBER(Zero),
[KEY_MINUS] = KBD_KEY_SYMBOL(Minus),
[KEY_EQUAL] = KBD_KEY_SYMBOL(Equals),
[KEY_BACKSPACE] = KBD_KEY_ACTION(DeleteBackward),
[KEY_TAB] = KBD_KEY_ACTION(Tab),
[KEY_Q] = KBD_KEY_LETTER(Q),
[KEY_W] = KBD_KEY_LETTER(W),
[KEY_E] = KBD_KEY_LETTER(E),
[KEY_R] = KBD_KEY_LETTER(R),
[KEY_T] = KBD_KEY_LETTER(T),
[KEY_Y] = KBD_KEY_LETTER(Y),
[KEY_U] = KBD_KEY_LETTER(U),
[KEY_I] = KBD_KEY_LETTER(I),
[KEY_O] = KBD_KEY_LETTER(O),
[KEY_P] = KBD_KEY_LETTER(P),
[KEY_LEFTBRACE] = KBD_KEY_SYMBOL(LeftBracket),
[KEY_RIGHTBRACE] = KBD_KEY_SYMBOL(RightBracket),
[KEY_ENTER] = KBD_KEY_ACTION(Enter),
[KEY_LEFTCTRL] = KBD_KEY_MODIFIER(ControlLeft),
[KEY_A] = KBD_KEY_LETTER(A),
[KEY_S] = KBD_KEY_LETTER(S),
[KEY_D] = KBD_KEY_LETTER(D),
[KEY_F] = KBD_KEY_LETTER(F),
[KEY_G] = KBD_KEY_LETTER(G),
[KEY_H] = KBD_KEY_LETTER(H),
[KEY_J] = KBD_KEY_LETTER(J),
[KEY_K] = KBD_KEY_LETTER(K),
[KEY_L] = KBD_KEY_LETTER(L),
[KEY_SEMICOLON] = KBD_KEY_SYMBOL(Semicolon),
[KEY_APOSTROPHE] = KBD_KEY_SYMBOL(Apostrophe),
[KEY_GRAVE] = KBD_KEY_SYMBOL(Grave),
[KEY_LEFTSHIFT] = KBD_KEY_MODIFIER(ShiftLeft),
[KEY_BACKSLASH] = KBD_KEY_SYMBOL(Backslash),
[KEY_Z] = KBD_KEY_LETTER(Z),
[KEY_X] = KBD_KEY_LETTER(X),
[KEY_C] = KBD_KEY_LETTER(C),
[KEY_V] = KBD_KEY_LETTER(V),
[KEY_B] = KBD_KEY_LETTER(B),
[KEY_N] = KBD_KEY_LETTER(N),
[KEY_M] = KBD_KEY_LETTER(M),
[KEY_COMMA] = KBD_KEY_SYMBOL(Comma),
[KEY_DOT] = KBD_KEY_SYMBOL(Period),
[KEY_SLASH] = KBD_KEY_SYMBOL(Slash),
[KEY_RIGHTSHIFT] = KBD_KEY_MODIFIER(ShiftRight),
[KEY_KPASTERISK] = KBD_KEY_KPSYMBOL(Multiply),
[KEY_LEFTALT] = KBD_KEY_MODIFIER(AltLeft),
[KEY_SPACE] = KBD_KEY_SYMBOL(Space),
[KEY_CAPSLOCK] = KBD_KEY_LOCK(Capitals),
[KEY_F1] = KBD_KEY_FUNCTION(F1),
[KEY_F2] = KBD_KEY_FUNCTION(F2),
[KEY_F3] = KBD_KEY_FUNCTION(F3),
[KEY_F4] = KBD_KEY_FUNCTION(F4),
[KEY_F5] = KBD_KEY_FUNCTION(F5),
[KEY_F6] = KBD_KEY_FUNCTION(F6),
[KEY_F7] = KBD_KEY_FUNCTION(F7),
[KEY_F8] = KBD_KEY_FUNCTION(F8),
[KEY_F9] = KBD_KEY_FUNCTION(F9),
[KEY_F10] = KBD_KEY_FUNCTION(F10),
[KEY_NUMLOCK] = KBD_KEY_LOCK(Numbers),
[KEY_SCROLLLOCK] = KBD_KEY_LOCK(Scroll),
[KEY_KP7] = KBD_KEY_KPNUMBER(Seven),
[KEY_KP8] = KBD_KEY_KPNUMBER(Eight),
[KEY_KP9] = KBD_KEY_KPNUMBER(Nine),
[KEY_KPMINUS] = KBD_KEY_KPSYMBOL(Minus),
[KEY_KP4] = KBD_KEY_KPNUMBER(Four),
[KEY_KP5] = KBD_KEY_KPNUMBER(Five),
[KEY_KP6] = KBD_KEY_KPNUMBER(Six),
[KEY_KPPLUS] = KBD_KEY_KPSYMBOL(Plus),
[KEY_KP1] = KBD_KEY_KPNUMBER(One),
[KEY_KP2] = KBD_KEY_KPNUMBER(Two),
[KEY_KP3] = KBD_KEY_KPNUMBER(Three),
[KEY_KP0] = KBD_KEY_KPNUMBER(Zero),
[KEY_KPDOT] = KBD_KEY_KPSYMBOL(Period),
[KEY_ZENKAKUHANKAKU] = KBD_KEY_UNMAPPED,
[KEY_102ND] = KBD_KEY_SYMBOL(Europe2),
[KEY_F11] = KBD_KEY_FUNCTION(F11),
[KEY_F12] = KBD_KEY_FUNCTION(F12),
[KEY_RO] = KBD_KEY_UNMAPPED,
[KEY_KATAKANA] = KBD_KEY_UNMAPPED,
[KEY_HIRAGANA] = KBD_KEY_UNMAPPED,
[KEY_HENKAN] = KBD_KEY_UNMAPPED,
[KEY_KATAKANAHIRAGANA] = KBD_KEY_UNMAPPED,
[KEY_MUHENKAN] = KBD_KEY_UNMAPPED,
[KEY_KPJPCOMMA] = KBD_KEY_UNMAPPED,
[KEY_KPENTER] = KBD_KEY_KPACTION(Enter),
[KEY_RIGHTCTRL] = KBD_KEY_MODIFIER(ControlRight),
[KEY_KPSLASH] = KBD_KEY_KPSYMBOL(Divide),
[KEY_SYSRQ] = KBD_KEY_ACTION(SystemRequest),
[KEY_RIGHTALT] = KBD_KEY_MODIFIER(AltRight),
[KEY_LINEFEED] = KBD_KEY_UNMAPPED,
[KEY_HOME] = KBD_KEY_ACTION(Home),
[KEY_UP] = KBD_KEY_ACTION(ArrowUp),
[KEY_PAGEUP] = KBD_KEY_ACTION(PageUp),
[KEY_LEFT] = KBD_KEY_ACTION(ArrowLeft),
[KEY_RIGHT] = KBD_KEY_ACTION(ArrowRight),
[KEY_END] = KBD_KEY_ACTION(End),
[KEY_DOWN] = KBD_KEY_ACTION(ArrowDown),
[KEY_PAGEDOWN] = KBD_KEY_ACTION(PageDown),
[KEY_INSERT] = KBD_KEY_ACTION(Insert),
[KEY_DELETE] = KBD_KEY_ACTION(DeleteForward),
[KEY_MACRO] = KBD_KEY_UNMAPPED,
[KEY_MUTE] = KBD_KEY_MEDIA(Mute),
[KEY_VOLUMEDOWN] = KBD_KEY_MEDIA(VolumeDown),
[KEY_VOLUMEUP] = KBD_KEY_MEDIA(VolumeUp),
[KEY_POWER] = KBD_KEY_ACTION(Power),
[KEY_KPEQUAL] = KBD_KEY_KPSYMBOL(Equals),
[KEY_KPPLUSMINUS] = KBD_KEY_KPSYMBOL(PlusMinus),
[KEY_LEFTMETA] = KBD_KEY_ACTION(GuiLeft),
[KEY_RIGHTMETA] = KBD_KEY_ACTION(GuiRight),
[KEY_COMPOSE] = KBD_KEY_ACTION(Context),
[KEY_PAUSE] = KBD_KEY_ACTION(Pause),
[KEY_KPCOMMA] = KBD_KEY_KPSYMBOL(Comma),
[KEY_HANGEUL] = KBD_KEY_UNMAPPED,
[KEY_HANGUEL] = KBD_KEY_UNMAPPED,
[KEY_HANJA] = KBD_KEY_UNMAPPED,
[KEY_YEN] = KBD_KEY_UNMAPPED,
[KEY_LEFTMETA] = KBD_KEY_UNMAPPED,
[KEY_RIGHTMETA] = KBD_KEY_UNMAPPED,
[KEY_COMPOSE] = KBD_KEY_UNMAPPED,
[KEY_STOP] = KBD_KEY_ACTION(Stop),
[KEY_AGAIN] = KBD_KEY_ACTION(Again),
[KEY_PROPS] = KBD_KEY_ACTION(Props),
[KEY_UNDO] = KBD_KEY_ACTION(Undo),
[KEY_FRONT] = KBD_KEY_ACTION(Front),
[KEY_COPY] = KBD_KEY_ACTION(Copy),
[KEY_OPEN] = KBD_KEY_ACTION(Open),
[KEY_PASTE] = KBD_KEY_ACTION(Paste),
[KEY_FIND] = KBD_KEY_ACTION(Find),
[KEY_CUT] = KBD_KEY_ACTION(Cut),
[KEY_HELP] = KBD_KEY_ACTION(Help),
[KEY_MENU] = KBD_KEY_ACTION(Menu),
[KEY_CALC] = KBD_KEY_UNMAPPED,
[KEY_SETUP] = KBD_KEY_UNMAPPED,
[KEY_SLEEP] = KBD_KEY_UNMAPPED,
[KEY_WAKEUP] = KBD_KEY_UNMAPPED,
[KEY_FILE] = KBD_KEY_UNMAPPED,
[KEY_SENDFILE] = KBD_KEY_UNMAPPED,
[KEY_DELETEFILE] = KBD_KEY_UNMAPPED,
[KEY_XFER] = KBD_KEY_UNMAPPED,
[KEY_PROG1] = KBD_KEY_UNMAPPED,
[KEY_PROG2] = KBD_KEY_UNMAPPED,
[KEY_WWW] = KBD_KEY_UNMAPPED,
[KEY_MSDOS] = KBD_KEY_UNMAPPED,
[KEY_COFFEE] = KBD_KEY_UNMAPPED,
[KEY_SCREENLOCK] = KBD_KEY_UNMAPPED,
[KEY_DIRECTION] = KBD_KEY_UNMAPPED,
[KEY_CYCLEWINDOWS] = KBD_KEY_UNMAPPED,
[KEY_MAIL] = KBD_KEY_UNMAPPED,
[KEY_BOOKMARKS] = KBD_KEY_UNMAPPED,
[KEY_COMPUTER] = KBD_KEY_UNMAPPED,
[KEY_BACK] = KBD_KEY_UNMAPPED,
[KEY_FORWARD] = KBD_KEY_UNMAPPED,
[KEY_CLOSECD] = KBD_KEY_MEDIA(Close),
[KEY_EJECTCD] = KBD_KEY_MEDIA(Eject),
[KEY_EJECTCLOSECD] = KBD_KEY_MEDIA(EjectClose),
[KEY_NEXTSONG] = KBD_KEY_MEDIA(Next),
[KEY_PLAYPAUSE] = KBD_KEY_MEDIA(PlayPause),
[KEY_PREVIOUSSONG] = KBD_KEY_MEDIA(Previous),
[KEY_STOPCD] = KBD_KEY_MEDIA(Stop),
[KEY_RECORD] = KBD_KEY_MEDIA(Record),
[KEY_REWIND] = KBD_KEY_MEDIA(Backward),
[KEY_PHONE] = KBD_KEY_UNMAPPED,
[KEY_ISO] = KBD_KEY_UNMAPPED,
[KEY_CONFIG] = KBD_KEY_UNMAPPED,
[KEY_HOMEPAGE] = KBD_KEY_UNMAPPED,
[KEY_REFRESH] = KBD_KEY_UNMAPPED,
[KEY_EXIT] = KBD_KEY_UNMAPPED,
[KEY_MOVE] = KBD_KEY_UNMAPPED,
[KEY_EDIT] = KBD_KEY_UNMAPPED,
[KEY_SCROLLUP] = KBD_KEY_UNMAPPED,
[KEY_SCROLLDOWN] = KBD_KEY_UNMAPPED,
[KEY_KPLEFTPAREN] = KBD_KEY_KPSYMBOL(LeftParenthesis),
[KEY_KPRIGHTPAREN] = KBD_KEY_KPSYMBOL(RightParenthesis),
[KEY_NEW] = KBD_KEY_UNMAPPED,
[KEY_REDO] = KBD_KEY_UNMAPPED,
[KEY_F13] = KBD_KEY_FUNCTION(F13),
[KEY_F14] = KBD_KEY_FUNCTION(F14),
[KEY_F15] = KBD_KEY_FUNCTION(F15),
[KEY_F16] = KBD_KEY_FUNCTION(F16),
[KEY_F17] = KBD_KEY_FUNCTION(F17),
[KEY_F18] = KBD_KEY_FUNCTION(F18),
[KEY_F19] = KBD_KEY_FUNCTION(F19),
[KEY_F20] = KBD_KEY_FUNCTION(F20),
[KEY_F21] = KBD_KEY_FUNCTION(F21),
[KEY_F22] = KBD_KEY_FUNCTION(F22),
[KEY_F23] = KBD_KEY_FUNCTION(F23),
[KEY_F24] = KBD_KEY_FUNCTION(F24),
[KEY_PLAYCD] = KBD_KEY_MEDIA(Play),
[KEY_PAUSECD] = KBD_KEY_MEDIA(Pause),
[KEY_PROG3] = KBD_KEY_UNMAPPED,
[KEY_PROG4] = KBD_KEY_UNMAPPED,
[KEY_DASHBOARD] = KBD_KEY_UNMAPPED,
[KEY_SUSPEND] = KBD_KEY_UNMAPPED,
[KEY_CLOSE] = KBD_KEY_UNMAPPED,
[KEY_PLAY] = KBD_KEY_UNMAPPED,
[KEY_FASTFORWARD] = KBD_KEY_MEDIA(Forward),
[KEY_BASSBOOST] = KBD_KEY_UNMAPPED,
[KEY_PRINT] = KBD_KEY_UNMAPPED,
[KEY_HP] = KBD_KEY_UNMAPPED,
[KEY_CAMERA] = KBD_KEY_UNMAPPED,
[KEY_SOUND] = KBD_KEY_UNMAPPED,
[KEY_QUESTION] = KBD_KEY_UNMAPPED,
[KEY_EMAIL] = KBD_KEY_UNMAPPED,
[KEY_CHAT] = KBD_KEY_UNMAPPED,
[KEY_SEARCH] = KBD_KEY_UNMAPPED,
[KEY_CONNECT] = KBD_KEY_UNMAPPED,
[KEY_FINANCE] = KBD_KEY_UNMAPPED,
[KEY_SPORT] = KBD_KEY_UNMAPPED,
[KEY_SHOP] = KBD_KEY_UNMAPPED,
[KEY_ALTERASE] = KBD_KEY_UNMAPPED,
[KEY_CANCEL] = KBD_KEY_UNMAPPED,
[KEY_BRIGHTNESSDOWN] = KBD_KEY_UNMAPPED,
[KEY_BRIGHTNESSUP] = KBD_KEY_UNMAPPED,
[KEY_MEDIA] = KBD_KEY_UNMAPPED,
[KEY_SWITCHVIDEOMODE] = KBD_KEY_UNMAPPED,
[KEY_KBDILLUMTOGGLE] = KBD_KEY_UNMAPPED,
[KEY_KBDILLUMDOWN] = KBD_KEY_UNMAPPED,
[KEY_KBDILLUMUP] = KBD_KEY_UNMAPPED,
[KEY_SEND] = KBD_KEY_UNMAPPED,
[KEY_REPLY] = KBD_KEY_UNMAPPED,
[KEY_FORWARDMAIL] = KBD_KEY_UNMAPPED,
[KEY_SAVE] = KBD_KEY_UNMAPPED,
[KEY_DOCUMENTS] = KBD_KEY_UNMAPPED,
[KEY_BATTERY] = KBD_KEY_UNMAPPED,
[KEY_BLUETOOTH] = KBD_KEY_UNMAPPED,
[KEY_WLAN] = KBD_KEY_UNMAPPED,
[KEY_UWB] = KBD_KEY_UNMAPPED,
[KEY_UNKNOWN] = KBD_KEY_UNMAPPED,
[KEY_VIDEO_NEXT] = KBD_KEY_UNMAPPED,
[KEY_VIDEO_PREV] = KBD_KEY_UNMAPPED,
[KEY_BRIGHTNESS_CYCLE] = KBD_KEY_UNMAPPED,
[KEY_BRIGHTNESS_ZERO] = KBD_KEY_UNMAPPED,
[KEY_DISPLAY_OFF] = KBD_KEY_UNMAPPED,
[KEY_WIMAX] = KBD_KEY_UNMAPPED,
[KEY_OK] = KBD_KEY_UNMAPPED,
[KEY_SELECT] = KBD_KEY_ACTION(Select),
[KEY_GOTO] = KBD_KEY_UNMAPPED,
[KEY_CLEAR] = KBD_KEY_ACTION(Clear),
[KEY_POWER2] = KBD_KEY_UNMAPPED,
[KEY_OPTION] = KBD_KEY_UNMAPPED,
[KEY_INFO] = KBD_KEY_UNMAPPED,
[KEY_TIME] = KBD_KEY_UNMAPPED,
[KEY_VENDOR] = KBD_KEY_UNMAPPED,
[KEY_ARCHIVE] = KBD_KEY_UNMAPPED,
[KEY_PROGRAM] = KBD_KEY_UNMAPPED,
[KEY_CHANNEL] = KBD_KEY_UNMAPPED,
[KEY_FAVORITES] = KBD_KEY_UNMAPPED,
[KEY_EPG] = KBD_KEY_UNMAPPED,
[KEY_PVR] = KBD_KEY_UNMAPPED,
[KEY_MHP] = KBD_KEY_UNMAPPED,
[KEY_LANGUAGE] = KBD_KEY_UNMAPPED,
[KEY_TITLE] = KBD_KEY_UNMAPPED,
[KEY_SUBTITLE] = KBD_KEY_UNMAPPED,
[KEY_ANGLE] = KBD_KEY_UNMAPPED,
[KEY_ZOOM] = KBD_KEY_UNMAPPED,
[KEY_MODE] = KBD_KEY_UNMAPPED,
[KEY_KEYBOARD] = KBD_KEY_UNMAPPED,
[KEY_SCREEN] = KBD_KEY_UNMAPPED,
[KEY_PC] = KBD_KEY_UNMAPPED,
[KEY_TV] = KBD_KEY_UNMAPPED,
[KEY_TV2] = KBD_KEY_UNMAPPED,
[KEY_VCR] = KBD_KEY_UNMAPPED,
[KEY_VCR2] = KBD_KEY_UNMAPPED,
[KEY_SAT] = KBD_KEY_UNMAPPED,
[KEY_SAT2] = KBD_KEY_UNMAPPED,
[KEY_CD] = KBD_KEY_UNMAPPED,
[KEY_TAPE] = KBD_KEY_UNMAPPED,
[KEY_RADIO] = KBD_KEY_UNMAPPED,
[KEY_TUNER] = KBD_KEY_UNMAPPED,
[KEY_PLAYER] = KBD_KEY_UNMAPPED,
[KEY_TEXT] = KBD_KEY_UNMAPPED,
[KEY_DVD] = KBD_KEY_UNMAPPED,
[KEY_AUX] = KBD_KEY_UNMAPPED,
[KEY_MP3] = KBD_KEY_UNMAPPED,
[KEY_AUDIO] = KBD_KEY_UNMAPPED,
[KEY_VIDEO] = KBD_KEY_UNMAPPED,
[KEY_DIRECTORY] = KBD_KEY_UNMAPPED,
[KEY_LIST] = KBD_KEY_UNMAPPED,
[KEY_MEMO] = KBD_KEY_UNMAPPED,
[KEY_CALENDAR] = KBD_KEY_UNMAPPED,
[KEY_RED] = KBD_KEY_UNMAPPED,
[KEY_GREEN] = KBD_KEY_UNMAPPED,
[KEY_YELLOW] = KBD_KEY_UNMAPPED,
[KEY_BLUE] = KBD_KEY_UNMAPPED,
[KEY_CHANNELUP] = KBD_KEY_UNMAPPED,
[KEY_CHANNELDOWN] = KBD_KEY_UNMAPPED,
[KEY_FIRST] = KBD_KEY_UNMAPPED,
[KEY_LAST] = KBD_KEY_UNMAPPED,
[KEY_AB] = KBD_KEY_UNMAPPED,
[KEY_NEXT] = KBD_KEY_UNMAPPED,
[KEY_RESTART] = KBD_KEY_UNMAPPED,
[KEY_SLOW] = KBD_KEY_UNMAPPED,
[KEY_SHUFFLE] = KBD_KEY_UNMAPPED,
[KEY_BREAK] = KBD_KEY_UNMAPPED,
[KEY_PREVIOUS] = KBD_KEY_UNMAPPED,
[KEY_DIGITS] = KBD_KEY_UNMAPPED,
[KEY_TEEN] = KBD_KEY_UNMAPPED,
[KEY_TWEN] = KBD_KEY_UNMAPPED,
[KEY_VIDEOPHONE] = KBD_KEY_UNMAPPED,
[KEY_GAMES] = KBD_KEY_UNMAPPED,
[KEY_ZOOMIN] = KBD_KEY_UNMAPPED,
[KEY_ZOOMOUT] = KBD_KEY_UNMAPPED,
[KEY_ZOOMRESET] = KBD_KEY_UNMAPPED,
[KEY_WORDPROCESSOR] = KBD_KEY_UNMAPPED,
[KEY_EDITOR] = KBD_KEY_UNMAPPED,
[KEY_SPREADSHEET] = KBD_KEY_UNMAPPED,
[KEY_GRAPHICSEDITOR] = KBD_KEY_UNMAPPED,
[KEY_PRESENTATION] = KBD_KEY_UNMAPPED,
[KEY_DATABASE] = KBD_KEY_UNMAPPED,
[KEY_NEWS] = KBD_KEY_UNMAPPED,
[KEY_VOICEMAIL] = KBD_KEY_UNMAPPED,
[KEY_ADDRESSBOOK] = KBD_KEY_UNMAPPED,
[KEY_MESSENGER] = KBD_KEY_UNMAPPED,
[KEY_DISPLAYTOGGLE] = KBD_KEY_UNMAPPED,
[KEY_SPELLCHECK] = KBD_KEY_UNMAPPED,
[KEY_LOGOFF] = KBD_KEY_UNMAPPED,
[KEY_DOLLAR] = KBD_KEY_UNMAPPED,
[KEY_EURO] = KBD_KEY_UNMAPPED,
[KEY_FRAMEBACK] = KBD_KEY_UNMAPPED,
[KEY_FRAMEFORWARD] = KBD_KEY_UNMAPPED,
[KEY_CONTEXT_MENU] = KBD_KEY_UNMAPPED,
[KEY_MEDIA_REPEAT] = KBD_KEY_UNMAPPED,
[KEY_DEL_EOL] = KBD_KEY_UNMAPPED,
[KEY_DEL_EOS] = KBD_KEY_UNMAPPED,
[KEY_INS_LINE] = KBD_KEY_UNMAPPED,
[KEY_DEL_LINE] = KBD_KEY_UNMAPPED,
[KEY_FN] = KBD_KEY_UNMAPPED,
[KEY_FN_ESC] = KBD_KEY_UNMAPPED,
[KEY_FN_F1] = KBD_KEY_UNMAPPED,
[KEY_FN_F2] = KBD_KEY_UNMAPPED,
[KEY_FN_F3] = KBD_KEY_UNMAPPED,
[KEY_FN_F4] = KBD_KEY_UNMAPPED,
[KEY_FN_F5] = KBD_KEY_UNMAPPED,
[KEY_FN_F6] = KBD_KEY_UNMAPPED,
[KEY_FN_F7] = KBD_KEY_UNMAPPED,
[KEY_FN_F8] = KBD_KEY_UNMAPPED,
[KEY_FN_F9] = KBD_KEY_UNMAPPED,
[KEY_FN_F10] = KBD_KEY_UNMAPPED,
[KEY_FN_F11] = KBD_KEY_UNMAPPED,
[KEY_FN_F12] = KBD_KEY_UNMAPPED,
[KEY_FN_1] = KBD_KEY_UNMAPPED,
[KEY_FN_2] = KBD_KEY_UNMAPPED,
[KEY_FN_D] = KBD_KEY_UNMAPPED,
[KEY_FN_E] = KBD_KEY_UNMAPPED,
[KEY_FN_F] = KBD_KEY_UNMAPPED,
[KEY_FN_S] = KBD_KEY_UNMAPPED,
[KEY_FN_B] = KBD_KEY_UNMAPPED,
[KEY_BRL_DOT1] = KBD_KEY_BRAILLE(Dot1),
[KEY_BRL_DOT2] = KBD_KEY_BRAILLE(Dot2),
[KEY_BRL_DOT3] = KBD_KEY_BRAILLE(Dot3),
[KEY_BRL_DOT4] = KBD_KEY_BRAILLE(Dot4),
[KEY_BRL_DOT5] = KBD_KEY_BRAILLE(Dot5),
[KEY_BRL_DOT6] = KBD_KEY_BRAILLE(Dot6),
[KEY_BRL_DOT7] = KBD_KEY_BRAILLE(Dot7),
[KEY_BRL_DOT8] = KBD_KEY_BRAILLE(Dot8),
[KEY_BRL_DOT9] = KBD_KEY_BRAILLE(Backward),
[KEY_BRL_DOT10] = KBD_KEY_BRAILLE(Forward),
END_KEY_CODE_MAP
int
newKeyboardMonitorExtension (KeyboardMonitorExtension **kmx) {
if ((*kmx = malloc(sizeof(**kmx)))) {
memset(*kmx, 0, sizeof(**kmx));
(*kmx)->uevent.socket = -1;
(*kmx)->uevent.monitor = NULL;
return 1;
} else {
logMallocError();
}
return 0;
}
void
destroyKeyboardMonitorExtension (KeyboardMonitorExtension *kmx) {
if (kmx->uevent.monitor) asyncCancelRequest(kmx->uevent.monitor);
if (kmx->uevent.socket != -1) close(kmx->uevent.socket);
free(kmx);
}
int
newKeyboardInstanceExtension (KeyboardInstanceExtension **kix) {
if ((*kix = malloc(sizeof(**kix)))) {
memset(*kix, 0, sizeof(**kix));
(*kix)->uinput = NULL;
(*kix)->udevDelay = NULL;
(*kix)->file.descriptor = -1;
(*kix)->file.monitor = NULL;
(*kix)->device.path = NULL;
(*kix)->device.major = 0;
(*kix)->device.minor = 0;
return 1;
} else {
logMallocError();
}
*kix = NULL;
return 0;
}
void
destroyKeyboardInstanceExtension (KeyboardInstanceExtension *kix) {
if (kix->file.monitor) {
asyncCancelRequest(kix->file.monitor);
logMessage(LOG_DEBUG, "closing keyboard: %s: fd=%d",
kix->device.path, kix->file.descriptor);
}
if (kix->file.descriptor != -1) close(kix->file.descriptor);
if (kix->udevDelay) asyncCancelRequest(kix->udevDelay);
if (kix->uinput) destroyUinputObject(kix->uinput);
if (kix->device.path) free(kix->device.path);
free(kix);
}
int
forwardKeyEvent (KeyboardInstanceObject *kio, int code, int press) {
return writeKeyEvent(kio->kix->uinput, code, (press? 1: 0));
}
ASYNC_INPUT_CALLBACK(handleLinuxKeyboardEvent) {
KeyboardInstanceObject *kio = parameters->data;
static const char label[] = "keyboard";
if (parameters->error) {
logMessage(LOG_DEBUG, "%s read error: fd=%d: %s",
label, kio->kix->file.descriptor, strerror(parameters->error));
destroyKeyboardInstanceObject(kio);
} else if (parameters->end) {
logMessage(LOG_DEBUG, "%s end-of-file: fd=%d",
label, kio->kix->file.descriptor);
destroyKeyboardInstanceObject(kio);
} else {
const struct input_event *event = parameters->buffer;
if (parameters->length >= sizeof(*event)) {
switch (event->type) {
case EV_KEY: {
int release = event->value == 0;
int press = event->value == 1;
if (release || press) handleKeyEvent(kio, event->code, press);
break;
}
case EV_REP: {
switch (event->code) {
case REP_DELAY: {
writeRepeatDelay(kio->kix->uinput, event->value);
break;
}
case REP_PERIOD: {
writeRepeatPeriod(kio->kix->uinput, event->value);
break;
}
default:
break;
}
break;
}
default:
break;
}
return sizeof(*event);
}
}
return 0;
}
static UinputObject *
newUinputInstance (const char *device) {
char name[0X40];
snprintf(name, sizeof(name), "Keyboard Instance - %s", locatePathName(device));
return newUinputObject(name);
}
static int
prepareUinputInstance (UinputObject *uinput, int keyboard) {
{
int type = EV_KEY;
BITMASK(mask, KEY_MAX+1, char);
int size = ioctl(keyboard, EVIOCGBIT(type, sizeof(mask)), mask);
if (size == -1) {
logSystemError("ioctl[EVIOCGBIT]");
return 0;
}
{
int count = size * 8;
{
static const int keys[] = {KEY_ENTER, KEY_SPACE, KEY_BRL_DOT1};
const int *key = keys;
const int *end = key + ARRAY_COUNT(keys);
while (key < end) {
if (*key >= count) continue;
if (BITMASK_TEST(mask, *key)) break;
key += 1;
}
if (key == end) return 0;
}
if (!enableUinputEventType(uinput, type)) return 0;
for (int key=0; key<count; key+=1) {
if (BITMASK_TEST(mask, key)) {
if (!enableUinputKey(uinput, key)) {
return 0;
}
}
}
}
}
if (!enableUinputEventType(uinput, EV_REP)) return 0;
if (!createUinputDevice(uinput)) return 0;
{
int properties[2];
if (ioctl(keyboard, EVIOCGREP, properties) != -1) {
if (!writeRepeatDelay(uinput, properties[0])) return 0;
if (!writeRepeatPeriod(uinput, properties[1])) return 0;
}
}
{
BITMASK(mask, KEY_MAX+1, char);
int size = ioctl(keyboard, EVIOCGKEY(sizeof(mask)), mask);
if (size != -1) {
int count = size * 8;
for (int key=0; key<count; key+=1) {
if (BITMASK_TEST(mask, key)) {
logMessage(LOG_WARNING, "key already pressed: %d", key);
}
}
}
}
return 1;
}
static int
monitorKeyboard (KeyboardInstanceObject *kio) {
const char *deviceName = locatePathName(kio->kix->device.path);
if ((kio->kix->file.descriptor = open(kio->kix->device.path, O_RDONLY)) != -1) {
struct stat status;
if (fstat(kio->kix->file.descriptor, &status) != -1) {
if (S_ISCHR(status.st_mode)) {
{
char description[0X100];
STR_BEGIN(description, sizeof(description));
STR_PRINTF("%s:", deviceName);
{
struct input_id identity;
if (ioctl(kio->kix->file.descriptor, EVIOCGID, &identity) != -1) {
STR_PRINTF(" bus=%04X vnd=%04X prd=%04X ver=%04X",
identity.bustype, identity.vendor, identity.product, identity.version);
{
static const KeyboardType typeTable[] = {
#ifdef BUS_I8042
[BUS_I8042] = KBD_TYPE_PS2,
#endif /* BUS_I8042 */
#ifdef BUS_USB
[BUS_USB] = KBD_TYPE_USB,
#endif /* BUS_USB */
#ifdef BUS_BLUETOOTH
[BUS_BLUETOOTH] = KBD_TYPE_BLUETOOTH,
#endif /* BUS_BLUETOOTH */
#ifdef BUS_HOST
[BUS_HOST] = KBD_TYPE_INTERNAL,
#endif /* BUS_HOST */
};
if (identity.bustype < ARRAY_COUNT(typeTable)) {
kio->actualProperties.type = typeTable[identity.bustype];
}
}
kio->actualProperties.vendor = identity.vendor;
kio->actualProperties.product = identity.product;
} else if (errno != ENOTTY) {
logMessage(LOG_WARNING, "cannot get input device identity: %s: %s",
deviceName, strerror(errno));
}
}
{
char topology[0X100];
if (ioctl(kio->kix->file.descriptor, EVIOCGPHYS(sizeof(topology)), topology) != -1) {
if (*topology) {
STR_PRINTF(" tpl=%s", topology);
}
}
}
{
char identifier[0X100];
if (ioctl(kio->kix->file.descriptor, EVIOCGUNIQ(sizeof(identifier)), identifier) != -1) {
if (*identifier) {
STR_PRINTF(" id=%s", identifier);
}
}
}
{
char name[0X100];
if (ioctl(kio->kix->file.descriptor, EVIOCGNAME(sizeof(name)), name) != -1) {
if (*name) {
STR_PRINTF(" nam=%s", name);
}
}
}
STR_END;
logMessage(LOG_DEBUG, "checking input device: %s", description);
}
if (kio->actualProperties.type) {
if (checkKeyboardProperties(&kio->actualProperties, &kio->kmo->requiredProperties)) {
if (ioctl(kio->kix->file.descriptor, EVIOCGRAB, 1) != -1) {
if ((kio->kix->uinput = newUinputInstance(kio->kix->device.path))) {
if (prepareUinputInstance(kio->kix->uinput, kio->kix->file.descriptor)) {
if (asyncReadFile(&kio->kix->file.monitor,
kio->kix->file.descriptor, sizeof(struct input_event),
handleLinuxKeyboardEvent, kio)) {
logMessage(LOG_DEBUG, "keyboard opened: %s: fd=%d",
kio->kix->device.path, kio->kix->file.descriptor);
return 1;
}
}
}
} else {
logSystemError("ioctl[EVIOCGRAB]");
}
}
}
}
} else {
logMessage(LOG_WARNING, "cannot stat input device: %s: %s",
deviceName, strerror(errno));
}
} else {
logMessage(LOG_WARNING, "cannot open input device: %s: %s",
deviceName, strerror(errno));
}
return 0;
}
static void
monitorCurrentKeyboards (KeyboardMonitorObject *kmo) {
const char *root = "/dev/input";
const size_t rootLength = strlen(root);
DIR *directory;
logMessage(LOG_DEBUG, "searching for keyboards");
if ((directory = opendir(root))) {
struct dirent *entry;
while ((entry = readdir(directory))) {
KeyboardInstanceObject *kio;
if ((kio = newKeyboardInstanceObject(kmo))) {
const size_t pathSize = rootLength + 1 + strlen(entry->d_name) + 1;
if ((kio->kix->device.path = malloc(pathSize))) {
snprintf(kio->kix->device.path, pathSize, "%s/%s", root, entry->d_name);
if (monitorKeyboard(kio)) continue;
} else {
logMallocError();
}
destroyKeyboardInstanceObject(kio);
}
}
closedir(directory);
} else {
logMessage(LOG_DEBUG, "cannot open directory: %s: %s", root, strerror(errno));
}
logMessage(LOG_DEBUG, "keyboard search complete");
}
#ifdef NETLINK_KOBJECT_UEVENT
ASYNC_ALARM_CALLBACK(openLinuxInputDevice) {
KeyboardInstanceObject *kio = parameters->data;
asyncDiscardHandle(kio->kix->udevDelay);
kio->kix->udevDelay = NULL;
if (!monitorKeyboard(kio)) destroyKeyboardInstanceObject(kio);
}
static int
getDeviceNumbers (const char *device, int *major, int *minor) {
int ok = 0;
static const char prefix[] = "/sys";
static const char suffix[] = "/dev";
char path[strlen(prefix) + strlen(device) + sizeof(suffix)];
int descriptor;
snprintf(path, sizeof(path), "%s%s%s", prefix, device, suffix);
if ((descriptor = open(path, O_RDONLY)) != -1) {
char buffer[0X10];
ssize_t length;
if ((length = read(descriptor, buffer, sizeof(buffer))) > 0) {
if (sscanf(buffer, "%d:%d", major, minor) == 2) {
ok = 1;
}
}
close(descriptor);
} else {
logMessage(LOG_DEBUG, "cannot open sysfs dev file: %s: %s",
path, strerror(errno));
}
return ok;
}
ASYNC_INPUT_CALLBACK(handleKobjectUeventString) {
KeyboardMonitorObject *kmo = parameters->data;
static const char label[] = "kobject uevent";
if (parameters->error) {
logMessage(LOG_DEBUG, "%s read error: %s", label, strerror(parameters->error));
} else if (parameters->end) {
logMessage(LOG_DEBUG, "%s end-of-file", label);
} else {
const char *string = parameters->buffer;
const char *end = memchr(string, 0, parameters->length);
if (end) {
size_t length = end - string;
static const char delimiters[] = {'@', '=', '\0'};
const char *delimiter = strpbrk(string, delimiters);
if (!delimiter) {
const char *data = end + 1;
size_t size;
if (strcmp(string, "libudev") == 0) {
size = 32;
} else {
logMessage(LOG_WARNING, "unrecognized %s segment: %s", label, string);
size = 0;
}
length += size;
if (parameters->length < length) return 0;
logBytes(LOG_DEBUG, "%s data: %s", data, size, label, string);
} else if (*delimiter == '@') {
const char *action = string;
const char *device = delimiter + 1;
int actionLength = delimiter - action;
logMessage(LOG_DEBUG, "%s action: %.*s %s", label, actionLength, action, device);
if (strncmp(action, "add", actionLength) == 0) {
const char *suffix = device;
while ((suffix = strstr(suffix, "/input"))) {
int input;
int event;
if (sscanf(++suffix, "input%d/event%d", &input, &event) == 2) {
KeyboardInstanceObject *kio;
if ((kio = newKeyboardInstanceObject(kmo))) {
if (getDeviceNumbers(device, &kio->kix->device.major, &kio->kix->device.minor)) {
char path[0X40];
snprintf(path, sizeof(path), "/dev/input/event%d", event);
if ((kio->kix->device.path = strdup(path))) {
if (asyncNewRelativeAlarm(&kio->kix->udevDelay,
LINUX_INPUT_DEVICE_OPEN_DELAY,
openLinuxInputDevice, kio)) {
break;
}
} else {
logMallocError();
}
}
destroyKeyboardInstanceObject(kio);
}
}
}
}
} else if (*delimiter == '=') {
const char *name = string;
const char *value = delimiter + 1;
int nameLength = delimiter - name;
logMessage(LOG_DEBUG, "%s property: %.*s %s", label, nameLength, name, value);
}
return length + 1;
}
}
return 0;
}
static int
getKobjectUeventSocket (void) {
static int socketDescriptor = -1;
if (socketDescriptor == -1) {
const struct sockaddr_nl socketAddress = {
.nl_family = AF_NETLINK,
.nl_pid = getpid(),
.nl_groups = 0XFFFFFFFF
};
if ((socketDescriptor = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) != -1) {
if (bind(socketDescriptor, (const struct sockaddr *)&socketAddress, sizeof(socketAddress)) != -1) {
logMessage(LOG_DEBUG,
"netlink kobject uevent socket opened: fd=%d", socketDescriptor
);
} else {
logSystemError("netlink kobject uevent socket bind");
close(socketDescriptor);
socketDescriptor = -1;
}
} else {
logSystemError("netlink kobject uevent socket creation");
}
}
return socketDescriptor;
}
#endif /* NETLINK_KOBJECT_UEVENT */
static int
monitorNewKeyboards (KeyboardMonitorObject *kmo) {
#ifdef NETLINK_KOBJECT_UEVENT
if ((kmo->kmx->uevent.socket = getKobjectUeventSocket()) != -1) {
if (asyncReadSocket(&kmo->kmx->uevent.monitor,
kmo->kmx->uevent.socket, 6+1+PATH_MAX+1,
handleKobjectUeventString, kmo)) {
return 1;
}
close(kmo->kmx->uevent.socket);
kmo->kmx->uevent.socket = -1;
}
#endif /* NETLINK_KOBJECT_UEVENT */
return 0;
}
#endif /* HAVE_LINUX_UINPUT_H */
int
monitorKeyboards (KeyboardMonitorObject *kmo) {
#ifdef HAVE_LINUX_UINPUT_H
monitorCurrentKeyboards(kmo);
monitorNewKeyboards(kmo);
#endif /* HAVE_LINUX_UINPUT_H */
return 1;
}