diff --git a/configure b/configure index 38ee05e..35f84d9 100755 --- a/configure +++ b/configure @@ -213,6 +213,14 @@ fi HAVE_VSNPRINTF=`check_func vsnprintf` +# Check for uinput support (Linux only) +if [ "$sys" = "Linux" ]; then + HAVE_UINPUT_H=`check_header linux/uinput.h` + if [ -n "$HAVE_UINPUT_H" ]; then + echo "Found uinput support (keyboard emulation for Wayland)" + fi +fi + # create Makefile echo 'creating Makefile ...' echo "PREFIX = $PREFIX" >Makefile @@ -282,6 +290,7 @@ echo >>$cfgheader [ -n "$HAVE_INTTYPES_H" ] && echo $HAVE_INTTYPES_H >>$cfgheader [ -n "$HAVE_XINPUT2_H" ] && echo $HAVE_XINPUT2_H >>$cfgheader [ -n "$HAVE_XTEST_H" ] && echo $HAVE_XTEST_H >>$cfgheader +[ -n "$HAVE_UINPUT_H" ] && echo $HAVE_UINPUT_H >>$cfgheader [ -n "$HAVE_VSNPRINTF" ] && echo $HAVE_VSNPRINTF >>$cfgheader echo >>$cfgheader diff --git a/src/cfgfile.c b/src/cfgfile.c index 00038e8..1cd96b7 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -46,7 +46,7 @@ enum { CFG_SENS_ROT, CFG_SENS_RX, CFG_SENS_RY, CFG_SENS_RZ, CFG_INVROT, CFG_INVTRANS, CFG_SWAPYZ, CFG_AXISMAP_N, CFG_BNMAP_N, CFG_BNACT_N, CFG_KBMAP_N, - CFG_LED, CFG_GRAB, + CFG_LED, CFG_GRAB, CFG_KBMAP_USE_X11, CFG_SERIAL, CFG_DEVID, NUM_CFG_OPTIONS @@ -96,6 +96,7 @@ void default_cfg(struct cfg *cfg) cfg->led = LED_ON; cfg->grab_device = 1; + cfg->kbemu_use_x11 = 0; /* default to uinput when available */ for(i=0; i<6; i++) { cfg->map_axis[i] = i; @@ -426,6 +427,21 @@ int read_cfg(const char *fname, struct cfg *cfg) } } + } else if(strcmp(key_str, "kbmap_use_x11") == 0) { + lptr->opt = CFG_KBMAP_USE_X11; + if(isint) { + cfg->kbemu_use_x11 = ival; + } else { + if(strcmp(val_str, "true") == 0 || strcmp(val_str, "on") == 0 || strcmp(val_str, "yes") == 0) { + cfg->kbemu_use_x11 = 1; + } else if(strcmp(val_str, "false") == 0 || strcmp(val_str, "off") == 0 || strcmp(val_str, "no") == 0) { + cfg->kbemu_use_x11 = 0; + } else { + logmsg(LOG_WARNING, "invalid configuration value for %s, expected a boolean value.\n", key_str); + continue; + } + } + } else if(strcmp(key_str, "grab") == 0) { lptr->opt = CFG_GRAB; if(isint) { diff --git a/src/cfgfile.h b/src/cfgfile.h index ead825e..f0ddf1c 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -57,6 +57,7 @@ struct cfg { char *kbmap_str[MAX_BUTTONS]; int swapyz; int led, grab_device; + int kbemu_use_x11; char serial_dev[PATH_MAX]; int repeat_msec; diff --git a/src/kbemu.c b/src/kbemu.c index 115f78c..75badf7 100644 --- a/src/kbemu.c +++ b/src/kbemu.c @@ -15,108 +15,175 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ - #include "config.h" - -#ifdef USE_X11 #include +#include #include #include "logger.h" #include "kbemu.h" +#include "cfgfile.h" + +enum { + KBEMU_NONE, + KBEMU_X11, + KBEMU_UINPUT +}; -#ifdef HAVE_XTEST_H -#include -static int use_xtest; +static int backend = KBEMU_NONE; + +extern struct cfg cfg; + +/* Initialize keyboard emulation based on config, independent of X11 */ +void kbemu_init(void) +{ +#if defined(__linux__) && defined(HAVE_UINPUT_H) + /* For uinput builds, default to uinput unless kbmap_use_x11 is set */ + if(!cfg.kbemu_use_x11) { + if(backend == KBEMU_NONE) { + logmsg(LOG_INFO, "Initializing uinput keyboard emulation backend\n"); + if(kbemu_uinput_init() == 0) { + backend = KBEMU_UINPUT; + logmsg(LOG_INFO, "Using uinput keyboard emulation backend\n"); + } else { + logmsg(LOG_WARNING, "Failed to initialize uinput backend\n"); + } + } + } #endif +} -static Display *dpy; +#ifdef USE_X11 +#include -void kbemu_set_display(Display *d) +/* Called by proto_x11.c when X11 connection is established */ +void kbemu_set_display(Display *dpy) { - dpy = d; + if(!dpy) { + if(backend == KBEMU_X11) { + kbemu_x11_cleanup(); + backend = KBEMU_NONE; + } + return; + } + + /* If we already have a backend (from kbemu_init), don't override */ + if(backend != KBEMU_NONE) { + logmsg(LOG_DEBUG, "kbemu backend already initialized\n"); + return; + } - if(d) { -#ifdef HAVE_XTEST_H - int tmp; - use_xtest = XTestQueryExtension(dpy, &tmp, &tmp, &tmp, &tmp); + /* Use X11 backend as fallback if uinput wasn't initialized + * (either because HAVE_UINPUT_H not defined, or kbmap_use_x11 was set, + * or uinput initialization failed) + */ + if(kbemu_x11_init(dpy) == 0) { + backend = KBEMU_X11; + logmsg(LOG_INFO, "Using X11 keyboard emulation backend\n"); + return; + } - if(use_xtest) - logmsg(LOG_DEBUG, "Using XTEST to send key events\n"); - else -#endif - logmsg(LOG_DEBUG, "Using XSendEvent to send key events\n"); + logmsg(LOG_WARNING, "Failed to initialize X11 keyboard emulation\n"); +} + +#else /* !USE_X11 */ + +/* When X11 is not available, try uinput */ +static void kbemu_init_fallback(void) +{ + if(backend != KBEMU_NONE) { + return; /* already initialized */ + } + +#if defined(__linux__) && defined(HAVE_UINPUT_H) + logmsg(LOG_INFO, "X11 not available, trying uinput keyboard emulation\n"); + if(kbemu_uinput_init() == 0) { + backend = KBEMU_UINPUT; + logmsg(LOG_INFO, "Using uinput keyboard emulation backend\n"); + return; } + logmsg(LOG_WARNING, "Failed to initialize uinput keyboard emulation\n"); +#else + logmsg(LOG_WARNING, "No keyboard emulation backend available\n"); +#endif } +#endif /* USE_X11 */ + KeySym kbemu_keysym(const char *str) { +#ifdef USE_X11 return XStringToKeysym(str); +#else + /* Without X11, we can't parse key names properly. + * This function is mainly used during config parsing. */ + logmsg(LOG_WARNING, "kbemu_keysym: X11 not available, cannot parse key name: %s\n", str); + return 0; +#endif } const char *kbemu_keyname(KeySym sym) { +#ifdef USE_X11 return XKeysymToString(sym); +#else + static char buf[32]; + snprintf(buf, sizeof buf, "0x%lx", sym); + return buf; +#endif } void send_kbevent(KeySym key, int press) { - XEvent xevent; - Window win; - int rev_state; - KeyCode kc; - - if(!dpy) return; - - if(!(kc = XKeysymToKeycode(dpy, key))) { - logmsg(LOG_WARNING, "failed to convert keysym %lu to keycode\n", key); - return; +#ifndef USE_X11 + /* Auto-initialize if not already done */ + if(backend == KBEMU_NONE) { + kbemu_init_fallback(); } +#endif -#ifdef HAVE_XTEST_H - if(use_xtest) { - XTestFakeKeyEvent(dpy, kc, press, 0); - XFlush(dpy); - return; - } + switch(backend) { +#ifdef USE_X11 + case KBEMU_X11: + kbemu_x11_send_key(key, press); + break; #endif - XGetInputFocus(dpy, &win, &rev_state); - - xevent.type = press ? KeyPress : KeyRelease; - xevent.xkey.display = dpy; - xevent.xkey.root = DefaultRootWindow(dpy); - xevent.xkey.window = win; - xevent.xkey.subwindow = None; - xevent.xkey.keycode = kc; - xevent.xkey.state = 0; - xevent.xkey.time = CurrentTime; - xevent.xkey.x = xevent.xkey.y = 1; - xevent.xkey.x_root = xevent.xkey.y_root = 1; - - XSendEvent(dpy, win, True, press ? KeyPressMask : KeyReleaseMask, &xevent); - XFlush(dpy); +#if defined(__linux__) && defined(HAVE_UINPUT_H) + case KBEMU_UINPUT: + kbemu_uinput_send_key(key, press); + break; +#endif + + default: + /* No backend available or not initialized */ + break; + } } void send_kbevent_combo(KeySym *keys, int count, int press) { - int i; - - if(!dpy || count <= 0) return; - - if(press) { - return; +#ifndef USE_X11 + /* Auto-initialize if not already done */ + if(backend == KBEMU_NONE) { + kbemu_init_fallback(); } +#endif - /* send press events for all keys */ - for(i=0; i. #ifndef KBEMU_H_ #define KBEMU_H_ +#ifdef USE_X11 #include #include void kbemu_set_display(Display *dpy); +#else +/* When X11 is not available, define KeySym for API compatibility */ +typedef unsigned long KeySym; +#endif + +/* Initialize keyboard emulation based on config */ +void kbemu_init(void); + KeySym kbemu_keysym(const char *str); const char *kbemu_keyname(KeySym sym); void send_kbevent(KeySym key, int press); void send_kbevent_combo(KeySym *keys, int count, int press); +/* Backend-specific functions */ +#ifdef USE_X11 +int kbemu_x11_init(Display *dpy); +void kbemu_x11_cleanup(void); +void kbemu_x11_send_key(KeySym key, int press); +void kbemu_x11_send_key_combo(KeySym *keys, int count, int press); +#endif + +#if defined(__linux__) && defined(HAVE_UINPUT_H) +int kbemu_uinput_init(void); +void kbemu_uinput_cleanup(void); +void kbemu_uinput_send_key(KeySym key, int press); +void kbemu_uinput_send_key_combo(KeySym *keys, int count, int press); +#endif + #endif /* KBEMU_H_ */ diff --git a/src/kbemu_uinput.c b/src/kbemu_uinput.c new file mode 100644 index 0000000..9a1f236 --- /dev/null +++ b/src/kbemu_uinput.c @@ -0,0 +1,169 @@ +/* +spacenavd - a free software replacement driver for 6dof space-mice. +Copyright (C) 2025 Allin Demopolis + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include "config.h" + +#if defined(__linux__) && defined(HAVE_UINPUT_H) +#include +#include +#include +#include +#include +#include +#include +#include "logger.h" +#include "kbemu.h" +#include "keymap.h" + +static int uinput_fd = -1; + +static void emit_event(int type, int code, int val); + +int kbemu_uinput_init(void) +{ + int i; + struct uinput_setup usetup; + + if(uinput_fd >= 0) { + return 0; /* already initialized */ + } + + /* Open uinput device */ + if((uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) == -1) { + logmsg(LOG_ERR, "failed to open /dev/uinput: %s\n", strerror(errno)); + logmsg(LOG_ERR, "Make sure you have permissions to access /dev/uinput\n"); + return -1; + } + + /* Enable key events */ + if(ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY) == -1) { + logmsg(LOG_ERR, "UI_SET_EVBIT failed: %s\n", strerror(errno)); + goto err_close; + } + + /* Enable all keyboard keys */ + for(i = KEY_ESC; i < KEY_MAX; i++) { + if(ioctl(uinput_fd, UI_SET_KEYBIT, i) == -1) { + /* Some keys may not be supported, continue anyway */ + } + } + + /* Set up the virtual device */ + memset(&usetup, 0, sizeof usetup); + usetup.id.bustype = BUS_VIRTUAL; + usetup.id.vendor = 0x1234; /* arbitrary vendor ID */ + usetup.id.product = 0x5678; /* arbitrary product ID */ + snprintf(usetup.name, UINPUT_MAX_NAME_SIZE, "spacenavd virtual keyboard"); + + if(ioctl(uinput_fd, UI_DEV_SETUP, &usetup) == -1) { + logmsg(LOG_ERR, "UI_DEV_SETUP failed: %s\n", strerror(errno)); + goto err_close; + } + + /* Create the device */ + if(ioctl(uinput_fd, UI_DEV_CREATE) == -1) { + logmsg(LOG_ERR, "UI_DEV_CREATE failed: %s\n", strerror(errno)); + goto err_close; + } + + logmsg(LOG_INFO, "uinput virtual keyboard created successfully\n"); + return 0; + +err_close: + close(uinput_fd); + uinput_fd = -1; + return -1; +} + +void kbemu_uinput_cleanup(void) +{ + if(uinput_fd >= 0) { + ioctl(uinput_fd, UI_DEV_DESTROY); + close(uinput_fd); + uinput_fd = -1; + logmsg(LOG_DEBUG, "uinput virtual keyboard destroyed\n"); + } +} + +void kbemu_uinput_send_key(KeySym keysym, int press) +{ + unsigned int keycode; + + if(uinput_fd < 0) { + return; + } + + /* Convert X11 KeySym to Linux keycode */ + keycode = keysym_to_linux_keycode(keysym); + if(!keycode) { + logmsg(LOG_WARNING, "failed to convert keysym %lu to Linux keycode\n", keysym); + return; + } + + /* Emit key event */ + emit_event(EV_KEY, keycode, press ? 1 : 0); + emit_event(EV_SYN, SYN_REPORT, 0); +} + +void kbemu_uinput_send_key_combo(KeySym *keys, int count, int press) +{ + int i; + + if(uinput_fd < 0 || count <= 0) { + return; + } + + if(press) { + return; + } + + /* Send press events for all keys */ + for(i = 0; i < count; i++) { + unsigned int keycode = keysym_to_linux_keycode(keys[i]); + if(keycode) { + emit_event(EV_KEY, keycode, 1); + } + } + emit_event(EV_SYN, SYN_REPORT, 0); + + /* Send release events in reverse order */ + for(i = count - 1; i >= 0; i--) { + unsigned int keycode = keysym_to_linux_keycode(keys[i]); + if(keycode) { + emit_event(EV_KEY, keycode, 0); + } + } + emit_event(EV_SYN, SYN_REPORT, 0); +} + +static void emit_event(int type, int code, int val) +{ + struct input_event ev; + + memset(&ev, 0, sizeof ev); + ev.type = type; + ev.code = code; + ev.value = val; + + if(write(uinput_fd, &ev, sizeof ev) != sizeof ev) { + logmsg(LOG_WARNING, "failed to write uinput event: %s\n", strerror(errno)); + } +} + +#else +int spacenavd_kbemu_uinput_shut_up_empty_source_warning; +#endif /* __linux__ && HAVE_UINPUT_H */ diff --git a/src/kbemu_x11.c b/src/kbemu_x11.c new file mode 100644 index 0000000..f93884c --- /dev/null +++ b/src/kbemu_x11.c @@ -0,0 +1,123 @@ +/* +spacenavd - a free software replacement driver for 6dof space-mice. +Copyright (C) 2025 Allin Demopolis + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "config.h" + +#ifdef USE_X11 +#include +#include +#include "logger.h" +#include "kbemu.h" + +#ifdef HAVE_XTEST_H +#include +static int use_xtest; +#endif + +static Display *dpy; + +int kbemu_x11_init(Display *d) +{ + if(!d) { + return -1; + } + + dpy = d; + +#ifdef HAVE_XTEST_H + int tmp; + use_xtest = XTestQueryExtension(dpy, &tmp, &tmp, &tmp, &tmp); + + if(use_xtest) { + logmsg(LOG_DEBUG, "Using XTEST to send key events\n"); + } else +#endif + logmsg(LOG_DEBUG, "Using XSendEvent to send key events\n"); + + return 0; +} + +void kbemu_x11_cleanup(void) +{ + dpy = 0; +} + +void kbemu_x11_send_key(KeySym key, int press) +{ + XEvent xevent; + Window win; + int rev_state; + KeyCode kc; + + if(!dpy) return; + + kc = XKeysymToKeycode(dpy, key); + if(!kc) { + logmsg(LOG_WARNING, "failed to convert keysym %lu to keycode\n", key); + return; + } + +#ifdef HAVE_XTEST_H + if(use_xtest) { + XTestFakeKeyEvent(dpy, kc, press, 0); + XFlush(dpy); + return; + } +#endif + + XGetInputFocus(dpy, &win, &rev_state); + + xevent.type = press ? KeyPress : KeyRelease; + xevent.xkey.display = dpy; + xevent.xkey.root = DefaultRootWindow(dpy); + xevent.xkey.window = win; + xevent.xkey.subwindow = None; + xevent.xkey.keycode = kc; + xevent.xkey.state = 0; + xevent.xkey.time = CurrentTime; + xevent.xkey.x = xevent.xkey.y = 1; + xevent.xkey.x_root = xevent.xkey.y_root = 1; + + XSendEvent(dpy, win, True, press ? KeyPressMask : KeyReleaseMask, &xevent); + XFlush(dpy); +} + +void kbemu_x11_send_key_combo(KeySym *keys, int count, int press) +{ + int i; + + if(!dpy || count <= 0) return; + + if(press) { + return; + } + + /* send press events for all keys */ + for(i=0; i=0; i--) { + kbemu_x11_send_key(keys[i], 0); + } +} + +#else +int spacenavd_kbemu_shut_up_empty_source_warning; +#endif /* USE_X11 */ diff --git a/src/keymap.c b/src/keymap.c new file mode 100644 index 0000000..b3a2a7a --- /dev/null +++ b/src/keymap.c @@ -0,0 +1,146 @@ +/* +spacenavd - a free software replacement driver for 6dof space-mice. +Copyright (C) 2025 Allin Demopolis + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include "config.h" + +#ifdef __linux__ +#include +#include +#include "keymap.h" + +struct keymap_entry { + unsigned long xkey; + unsigned int linkey; +}; + +static struct keymap_entry keymap[] = { + /* Alphanumeric keys */ + {0x0061, KEY_A}, /* XK_a */ + {0x0062, KEY_B}, /* XK_b */ + {0x0063, KEY_C}, /* XK_c */ + {0x0064, KEY_D}, /* XK_d */ + {0x0065, KEY_E}, /* XK_e */ + {0x0066, KEY_F}, /* XK_f */ + {0x0067, KEY_G}, /* XK_g */ + {0x0068, KEY_H}, /* XK_h */ + {0x0069, KEY_I}, /* XK_i */ + {0x006a, KEY_J}, /* XK_j */ + {0x006b, KEY_K}, /* XK_k */ + {0x006c, KEY_L}, /* XK_l */ + {0x006d, KEY_M}, /* XK_m */ + {0x006e, KEY_N}, /* XK_n */ + {0x006f, KEY_O}, /* XK_o */ + {0x0070, KEY_P}, /* XK_p */ + {0x0071, KEY_Q}, /* XK_q */ + {0x0072, KEY_R}, /* XK_r */ + {0x0073, KEY_S}, /* XK_s */ + {0x0074, KEY_T}, /* XK_t */ + {0x0075, KEY_U}, /* XK_u */ + {0x0076, KEY_V}, /* XK_v */ + {0x0077, KEY_W}, /* XK_w */ + {0x0078, KEY_X}, /* XK_x */ + {0x0079, KEY_Y}, /* XK_y */ + {0x007a, KEY_Z}, /* XK_z */ + + /* Number keys */ + {0x0030, KEY_0}, /* XK_0 */ + {0x0031, KEY_1}, /* XK_1 */ + {0x0032, KEY_2}, /* XK_2 */ + {0x0033, KEY_3}, /* XK_3 */ + {0x0034, KEY_4}, /* XK_4 */ + {0x0035, KEY_5}, /* XK_5 */ + {0x0036, KEY_6}, /* XK_6 */ + {0x0037, KEY_7}, /* XK_7 */ + {0x0038, KEY_8}, /* XK_8 */ + {0x0039, KEY_9}, /* XK_9 */ + + /* Function keys */ + {0xffbe, KEY_F1}, /* XK_F1 */ + {0xffbf, KEY_F2}, /* XK_F2 */ + {0xffc0, KEY_F3}, /* XK_F3 */ + {0xffc1, KEY_F4}, /* XK_F4 */ + {0xffc2, KEY_F5}, /* XK_F5 */ + {0xffc3, KEY_F6}, /* XK_F6 */ + {0xffc4, KEY_F7}, /* XK_F7 */ + {0xffc5, KEY_F8}, /* XK_F8 */ + {0xffc6, KEY_F9}, /* XK_F9 */ + {0xffc7, KEY_F10}, /* XK_F10 */ + {0xffc8, KEY_F11}, /* XK_F11 */ + {0xffc9, KEY_F12}, /* XK_F12 */ + + /* Modifier keys */ + {0xffe1, KEY_LEFTSHIFT}, /* XK_Shift_L */ + {0xffe2, KEY_RIGHTSHIFT}, /* XK_Shift_R */ + {0xffe3, KEY_LEFTCTRL}, /* XK_Control_L */ + {0xffe4, KEY_RIGHTCTRL}, /* XK_Control_R */ + {0xffe5, KEY_CAPSLOCK}, /* XK_Caps_Lock */ + {0xffe7, KEY_LEFTMETA}, /* XK_Meta_L */ + {0xffe8, KEY_RIGHTMETA}, /* XK_Meta_R */ + {0xffe9, KEY_LEFTALT}, /* XK_Alt_L */ + {0xffea, KEY_RIGHTALT}, /* XK_Alt_R */ + {0xffeb, KEY_LEFTMETA}, /* XK_Super_L */ + {0xffec, KEY_RIGHTMETA}, /* XK_Super_R */ + + /* Special keys */ + {0xff1b, KEY_ESC}, /* XK_Escape */ + {0xff08, KEY_BACKSPACE}, /* XK_BackSpace */ + {0xff09, KEY_TAB}, /* XK_Tab */ + {0xff0d, KEY_ENTER}, /* XK_Return */ + {0x0020, KEY_SPACE}, /* XK_space */ + {0xffff, KEY_DELETE}, /* XK_Delete */ + {0xff63, KEY_INSERT}, /* XK_Insert */ + {0xff50, KEY_HOME}, /* XK_Home */ + {0xff57, KEY_END}, /* XK_End */ + {0xff55, KEY_PAGEUP}, /* XK_Page_Up */ + {0xff56, KEY_PAGEDOWN}, /* XK_Page_Down */ + + /* Arrow keys */ + {0xff51, KEY_LEFT}, /* XK_Left */ + {0xff52, KEY_UP}, /* XK_Up */ + {0xff53, KEY_RIGHT}, /* XK_Right */ + {0xff54, KEY_DOWN}, /* XK_Down */ + + /* Punctuation and symbols */ + {0x0027, KEY_APOSTROPHE}, /* XK_apostrophe ' */ + {0x002c, KEY_COMMA}, /* XK_comma , */ + {0x002d, KEY_MINUS}, /* XK_minus - */ + {0x002e, KEY_DOT}, /* XK_period . */ + {0x002f, KEY_SLASH}, /* XK_slash / */ + {0x003b, KEY_SEMICOLON}, /* XK_semicolon ; */ + {0x003d, KEY_EQUAL}, /* XK_equal = */ + {0x005b, KEY_LEFTBRACE}, /* XK_bracketleft [ */ + {0x005c, KEY_BACKSLASH}, /* XK_backslash \ */ + {0x005d, KEY_RIGHTBRACE}, /* XK_bracketright ] */ + {0x0060, KEY_GRAVE}, /* XK_grave ` */ + + {0, 0} /* terminator */ +}; + +unsigned int keysym_to_linux_keycode(unsigned long sym) +{ + int i; + + for(i = 0; keymap[i].xkey; i++) { + if(keymap[i].xkey == sym) { + return keymap[i].linkey; + } + } + + return 0; /* not found */ +} + +#endif /* __linux__ */ diff --git a/src/keymap.h b/src/keymap.h new file mode 100644 index 0000000..f7d442d --- /dev/null +++ b/src/keymap.h @@ -0,0 +1,37 @@ +/* +spacenavd - a free software replacement driver for 6dof space-mice. +Copyright (C) 2025 Allin Demopolis + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef KEYMAP_H_ +#define KEYMAP_H_ + +#ifdef USE_X11 +#include +#else +/* Define minimal X11 KeySym values when X11 is not available */ +typedef unsigned long KeySym; +#define XK_VoidSymbol 0xffffff +#endif + +#ifdef __linux__ + +/* Map X11 KeySym to Linux KEY_* code + * Note: KeySym is defined above, either from X11 or as typedef */ +unsigned int keysym_to_linux_keycode(unsigned long sym); + +#endif /* __linux__ */ + +#endif /* KEYMAP_H_ */ diff --git a/src/spnavd.c b/src/spnavd.c index aae783a..fdb58fe 100644 --- a/src/spnavd.c +++ b/src/spnavd.c @@ -34,6 +34,7 @@ along with this program. If not, see . #include "hotplug.h" #include "client.h" #include "proto_unix.h" +#include "kbemu.h" #ifdef USE_X11 #include "proto_x11.h" #endif @@ -162,6 +163,10 @@ opt_pidfile: if(!argv[++i]) { read_cfg(cfgfile, &cfg); prev_cfg = cfg; + + /* Initialize keyboard emulation backend based on config */ + kbemu_init(); + pipe(pfd); signal(SIGINT, sig_handler);