From e9caeadfebef08b8bd72e3d25bd03244687176fd Mon Sep 17 00:00:00 2001 From: Allin Demopolis Date: Thu, 20 Nov 2025 17:07:53 -0500 Subject: [PATCH 1/2] add support for multi-key button presses --- src/cfgfile.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++- src/cfgfile.h | 4 +++- src/event.c | 11 +++------- src/kbemu.c | 32 +++++++++++++++++++++++++-- src/kbemu.h | 1 + src/proto_unix.c | 10 ++++----- 6 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/cfgfile.c b/src/cfgfile.c index 04abb9e..5ff203d 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -31,6 +31,10 @@ along with this program. If not, see . struct cfg cfg, prev_cfg; +#ifdef USE_X11 +extern unsigned long kbemu_keysym(const char *str); +#endif + /* all parsable config options... some of them might map to the same cfg field */ enum { CFG_REPEAT, @@ -57,6 +61,7 @@ enum { RMCFG_ALL, RMCFG_OWN }; static int parse_bnact(const char *s); static const char *bnact_name(int bnact); +static int parse_kbmap(const char *str, unsigned long *kbmap, int max_keys); static int add_cfgopt(int opt, int idx, const char *fmt, ...); static int add_cfgopt_devid(int vid, int pid); static int rm_cfgopt(const char *name, int mode); @@ -97,9 +102,13 @@ void default_cfg(struct cfg *cfg) } for(i=0; imap_button[i] = i; cfg->kbmap_str[i] = 0; - cfg->kbmap[i] = 0; + cfg->kbmap_count[i] = 0; + for(j=0; jkbmap[i][j] = 0; + } } cfg->repeat_msec = -1; @@ -399,6 +408,7 @@ int read_cfg(const char *fname, struct cfg *cfg) free(cfg->kbmap_str[bnidx]); } cfg->kbmap_str[bnidx] = strdup(val_str); + cfg->kbmap_count[bnidx] = parse_kbmap(val_str, cfg->kbmap[bnidx], MAX_KEYS_PER_BUTTON); } else if(strcmp(key_str, "led") == 0) { lptr->opt = CFG_LED; @@ -726,6 +736,51 @@ static const char *bnact_name(int bnact) return "none"; } +static int parse_kbmap(const char *str, unsigned long *kbmap, int max_keys) +{ +#ifdef USE_X11 + char buf[256], *ptr, *start; + int count = 0; + + if(!str || !*str) return 0; + + strncpy(buf, str, sizeof buf - 1); + buf[sizeof buf - 1] = 0; + + start = buf; + while(*start && count < max_keys) { + ptr = strchr(start, '+'); + if(ptr) *ptr = 0; + + while(*start && isspace(*start)) start++; + if(*start) { + char *end = start + strlen(start) - 1; + while(end > start && isspace(*end)) *end-- = 0; + } + + if(*start) { + unsigned long ksym = (unsigned long)kbemu_keysym(start); + if(ksym == 0) { + logmsg(LOG_WARNING, "invalid key name in keyboard mapping: \"%s\"\n", start); + } else { + kbmap[count++] = ksym; + } + } + + if(!ptr) break; + start = ptr + 1; + } + + if(count >= max_keys && *start) { + logmsg(LOG_WARNING, "keyboard mapping truncated, max %d keys supported\n", max_keys); + } + + return count; +#else + return 0; +#endif +} + static struct cfgline *find_cfgopt(int opt, int idx) { int i; diff --git a/src/cfgfile.h b/src/cfgfile.h index 049e6a9..ead825e 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -24,6 +24,7 @@ along with this program. If not, see . #define MAX_AXES 64 #define MAX_BUTTONS 64 #define MAX_CUSTOM 64 +#define MAX_KEYS_PER_BUTTON 8 enum { LED_OFF = 0, @@ -51,7 +52,8 @@ struct cfg { int map_axis[MAX_AXES]; int map_button[MAX_BUTTONS]; int bnact[MAX_BUTTONS]; - int kbmap[MAX_BUTTONS]; + unsigned long kbmap[MAX_BUTTONS][MAX_KEYS_PER_BUTTON]; + int kbmap_count[MAX_BUTTONS]; char *kbmap_str[MAX_BUTTONS]; int swapyz; int led, grab_device; diff --git a/src/event.c b/src/event.c index a129420..bd6f0e5 100644 --- a/src/event.c +++ b/src/event.c @@ -215,14 +215,9 @@ void process_input(struct device *dev, struct dev_input *inp) /* check to see if we must emulate a keyboard event instead of a * retular button event for this button */ - if(cfg.kbmap_str[inp->idx]) { - if(!cfg.kbmap[inp->idx]) { - cfg.kbmap[inp->idx] = kbemu_keysym(cfg.kbmap_str[inp->idx]); - if(verbose) { - logmsg(LOG_DEBUG, "mapping ``%s'' to keysym %d\n", cfg.kbmap_str[inp->idx], (int)cfg.kbmap[inp->idx]); - } - } - send_kbevent(cfg.kbmap[inp->idx], inp->val); + if(cfg.kbmap_count[inp->idx] > 0) { + KeySym *keys = (KeySym*)cfg.kbmap[inp->idx]; + send_kbevent_combo(keys, cfg.kbmap_count[inp->idx], inp->val); break; } #endif diff --git a/src/kbemu.c b/src/kbemu.c index 6b67c03..df7c61e 100644 --- a/src/kbemu.c +++ b/src/kbemu.c @@ -63,12 +63,19 @@ void send_kbevent(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, XKeysymToKeycode(dpy, key), press, 0); + XTestFakeKeyEvent(dpy, kc, press, 0); XFlush(dpy); return; } @@ -81,7 +88,7 @@ void send_kbevent(KeySym key, int press) xevent.xkey.root = DefaultRootWindow(dpy); xevent.xkey.window = win; xevent.xkey.subwindow = None; - xevent.xkey.keycode = XKeysymToKeycode(dpy, key); + xevent.xkey.keycode = kc; xevent.xkey.state = 0; xevent.xkey.time = CurrentTime; xevent.xkey.x = xevent.xkey.y = 1; @@ -90,6 +97,27 @@ void send_kbevent(KeySym key, int press) XSendEvent(dpy, win, True, press ? KeyPressMask : KeyReleaseMask, &xevent); XFlush(dpy); } + +void send_kbevent_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--) { + send_kbevent(keys[i], 0); + } +} #else int spacenavd_kbemu_shut_up_empty_source_warning; #endif /* USE_X11 */ diff --git a/src/kbemu.h b/src/kbemu.h index cd5370b..79df548 100644 --- a/src/kbemu.h +++ b/src/kbemu.h @@ -26,5 +26,6 @@ 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); #endif /* KBEMU_H_ */ diff --git a/src/proto_unix.c b/src/proto_unix.c index 0c0c121..4c32cfd 100644 --- a/src/proto_unix.c +++ b/src/proto_unix.c @@ -532,7 +532,8 @@ static int handle_request(struct client *c, struct reqresp *req) sendresp(c, req, -1); return 0; } - cfg.kbmap[idx] = req->data[1]; + cfg.kbmap[idx][0] = req->data[1]; + cfg.kbmap_count[idx] = req->data[1] > 0 ? 1 : 0; free(cfg.kbmap_str[idx]); cfg.kbmap_str[idx] = req->data[1] > 0 ? strdup(str) : 0; sendresp(c, req, 0); @@ -550,11 +551,8 @@ static int handle_request(struct client *c, struct reqresp *req) sendresp(c, req, -1); return 0; } - if(cfg.kbmap_str[idx]) { - if(!cfg.kbmap[idx]) { - cfg.kbmap[idx] = kbemu_keysym(cfg.kbmap_str[idx]); - } - req->data[1] = cfg.kbmap[idx]; + if(cfg.kbmap_count[idx] > 0) { + req->data[1] = cfg.kbmap[idx][0]; } else { req->data[1] = 0; } From 65d1525d078f6b34946240c097c620d675147050 Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Fri, 21 Nov 2025 01:49:09 +0200 Subject: [PATCH 2/2] trivial style changes --- src/cfgfile.c | 5 ++--- src/kbemu.c | 7 +++---- src/proto_unix.c | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cfgfile.c b/src/cfgfile.c index 5ff203d..00038e8 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -32,7 +32,7 @@ along with this program. If not, see . struct cfg cfg, prev_cfg; #ifdef USE_X11 -extern unsigned long kbemu_keysym(const char *str); +unsigned long kbemu_keysym(const char *str); #endif /* all parsable config options... some of them might map to the same cfg field */ @@ -81,7 +81,7 @@ static int num_lines; void default_cfg(struct cfg *cfg) { - int i; + int i, j; memset(cfg, 0, sizeof *cfg); @@ -102,7 +102,6 @@ void default_cfg(struct cfg *cfg) } for(i=0; imap_button[i] = i; cfg->kbmap_str[i] = 0; cfg->kbmap_count[i] = 0; diff --git a/src/kbemu.c b/src/kbemu.c index df7c61e..115f78c 100644 --- a/src/kbemu.c +++ b/src/kbemu.c @@ -67,8 +67,7 @@ void send_kbevent(KeySym key, int press) if(!dpy) return; - kc = XKeysymToKeycode(dpy, key); - if(!kc) { + if(!(kc = XKeysymToKeycode(dpy, key))) { logmsg(LOG_WARNING, "failed to convert keysym %lu to keycode\n", key); return; } @@ -114,8 +113,8 @@ void send_kbevent_combo(KeySym *keys, int count, int press) } /* send release events in reverse order */ - for(i=count-1; i>=0; i--) { - send_kbevent(keys[i], 0); + for(i=0; idata[1]; cfg.kbmap_count[idx] = req->data[1] > 0 ? 1 : 0; free(cfg.kbmap_str[idx]); @@ -551,6 +552,7 @@ static int handle_request(struct client *c, struct reqresp *req) sendresp(c, req, -1); return 0; } + /* TODO handle multi-key sequences in a backwards compatible way */ if(cfg.kbmap_count[idx] > 0) { req->data[1] = cfg.kbmap[idx][0]; } else {