diff --git a/doc/example-spnavrc b/doc/example-spnavrc index 765c7b2..6a1ad79 100644 --- a/doc/example-spnavrc +++ b/doc/example-spnavrc @@ -76,6 +76,17 @@ #... #bnmapN = N (N < 64) +# Button hold remapping (zero-based) +# +#bnhold0 = 0 +#bnhold1 = 1 +#... +#bnholdN = N (N < 64) + +# Time before hold mapping activates (milliseconds) +# +#hold-timeout = 350 + # Button action mapping # Use the following syntax: # bnactN = diff --git a/src/cfgfile.c b/src/cfgfile.c index 3f8347a..750adfd 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -34,7 +34,7 @@ struct cfg cfg, prev_cfg; /* all parsable config options... some of them might map to the same cfg field */ enum { - CFG_REPEAT, + CFG_REPEAT, CFG_HOLD, CFG_DEADZONE, CFG_DEADZONE_N, CFG_DEADZONE_TX, CFG_DEADZONE_TY, CFG_DEADZONE_TZ, CFG_DEADZONE_RX, CFG_DEADZONE_RY, CFG_DEADZONE_RZ, @@ -42,7 +42,7 @@ enum { CFG_SENS_TRANS, CFG_SENS_TX, CFG_SENS_TY, CFG_SENS_TZ, 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_AXISMAP_N, CFG_BNMAP_N, CFG_BNHOLD_N, CFG_BNACT_N, CFG_KBMAP_N, CFG_LED, CFG_GRAB, CFG_SERIAL, CFG_DEVID, @@ -59,7 +59,7 @@ enum { RMCFG_ALL, RMCFG_OWN }; /* number of lines to add to the cfglines allocation, in order to allow for * adding any number of additional options if necessary */ -#define NUM_EXTRA_LINES (NUM_CFG_OPTIONS + MAX_CUSTOM + MAX_BUTTONS * 3 + MAX_AXES + 16) +#define NUM_EXTRA_LINES (NUM_CFG_OPTIONS + MAX_CUSTOM + MAX_BUTTONS * 4 + MAX_AXES + 16) static int parse_bnact(const char *s); static const char *bnact_name(int bnact); @@ -106,6 +106,7 @@ void default_cfg(struct cfg *cfg) for(i=0; imap_button[i] = i; + cfg->map_hold[i] = -1; cfg->kbmap_str[i] = 0; cfg->kbmap_count[i] = 0; for(j=0; jrepeat_msec = -1; + cfg->hold_timeout = 350; for(i=0; idevname[i] = 0; @@ -475,6 +477,33 @@ int read_cfg(const char *fname, struct cfg *cfg) lptr->opt = CFG_SOCKPATH; strncpy(cfg->sockpath, val_str, PATH_MAX - 1); + } else if(strcmp(key_str, "hold-timeout") == 0) { + EXPECT(isint); + + if(ival < 0) { + logmsg(LOG_WARNING, "invalid configuration value for %s, expected a non-negative integer\n", key_str); + continue; + } + + lptr->opt = CFG_HOLD; + cfg->hold_timeout = ival; + + } else if(sscanf(key_str, "bnhold%d", &bnidx) == 1) { + EXPECT(isint); + + if(bnidx < 0 || bnidx >= MAX_BUTTONS || ival < 0 || ival >= MAX_BUTTONS) { + logmsg(LOG_WARNING, "invalid configuration value for %s, expected button numbers from 0 to %d\n", key_str, MAX_BUTTONS - 1); + continue; + } + + if(cfg->map_hold[bnidx] != -1) { + logmsg(LOG_WARNING, "multiple secondary mappings for button %d\n", bnidx); + } + + lptr->opt = CFG_BNHOLD_N; + lptr->idx = bnidx; + cfg->map_hold[bnidx] = ival; + } else { logmsg(LOG_WARNING, "unrecognized config option: %s\n", key_str); } @@ -617,6 +646,12 @@ int write_cfg(const char *fname, struct cfg *cfg) rm_cfgopt("repeat-interval", RMCFG_ALL); } + if(cfg->hold_timeout != def.hold_timeout) { + add_cfgopt(CFG_HOLD, 0, "hold-timeout = %d", cfg->hold_timeout); + } else { + rm_cfgopt("hold-timeout", RMCFG_OWN); + } + if(cfg->invert[0] || cfg->invert[1] || cfg->invert[2]) { char flags[4] = {0}, *p = flags; if(cfg->invert[0]) *p++ = 'x'; @@ -661,6 +696,15 @@ int write_cfg(const char *fname, struct cfg *cfg) } } + for(i = 0; i < MAX_BUTTONS; i++) { + if(cfg->map_hold[i] >= 0) { + add_cfgopt(CFG_BNHOLD_N, i, "bnhold%d = %d", i, cfg->map_hold[i]); + } else { + sprintf(buf, "bnhold%d", i); + rm_cfgopt(buf, RMCFG_ALL); + } + } + for(i=0; ibnact[i] != BNACT_NONE) { add_cfgopt(CFG_BNACT_N, i, "bnact%d = %s", i, bnact_name(cfg->bnact[i])); diff --git a/src/cfgfile.h b/src/cfgfile.h index f74eb0d..46aa671 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -51,6 +51,8 @@ struct cfg { int invert[MAX_AXES]; int map_axis[MAX_AXES]; int map_button[MAX_BUTTONS]; + int map_hold[MAX_BUTTONS]; + int hold_timeout; int bnact[MAX_BUTTONS]; unsigned int kbmap[MAX_BUTTONS][MAX_KEYS_PER_BUTTON]; int kbmap_count[MAX_BUTTONS]; diff --git a/src/event.c b/src/event.c index 7b48b3a..de5a501 100644 --- a/src/event.c +++ b/src/event.c @@ -46,12 +46,13 @@ struct dev_event { struct device *dev; int pending; struct dev_event *next; + struct timeval hold_timeouts[MAX_BUTTONS]; }; static struct dev_event *add_dev_event(struct device *dev); static struct dev_event *device_event_in_use(struct device *dev); static void handle_button_action(int act, int val); -static void dispatch_event(struct dev_event *dev); +static void dispatch_event(struct device *dev, spnav_event *ev); static void send_event(spnav_event *ev, struct client *c); static unsigned int msec_dif(struct timeval tv1, struct timeval tv2); @@ -64,15 +65,12 @@ static int cur_axis_mag[6], cur_dom_axis; static struct dev_event *add_dev_event(struct device *dev) { struct dev_event *dev_ev, *iter; - int i; - if((dev_ev = malloc(sizeof *dev_ev)) == NULL) { + if((dev_ev = calloc(1, sizeof *dev_ev)) == NULL) { return NULL; } dev_ev->event.motion.data = (int*)&dev_ev->event.motion.x; - for(i=0; i<6; i++) - dev_ev->event.motion.data[i] = 0; gettimeofday(&dev_ev->timeval, 0); dev_ev->dev = dev; dev_ev->next = NULL; @@ -142,6 +140,53 @@ static INLINE int map_axis(int devaxis) return axis; } +static void update_motion_period(struct dev_event *dev_ev) +{ + struct timeval now; + gettimeofday(&now, 0); + + dev_ev->event.motion.period = msec_dif(now, dev_ev->timeval); + dev_ev->timeval = now; +} + +static void emit_button(struct device *dev, int idx, int val, int hold) +{ + spnav_event ev; + + /* check to see if the button has been bound to an action */ + if(cfg.bnact[idx] > 0) { + handle_button_action(cfg.bnact[idx], val); + return; + } + + /* check to see if we must emulate a keyboard event instead of a + * regular button event for this button + */ + if(cfg.kbmap_count[idx] == 1) { + /* single key */ + unsigned int key = cfg.kbmap[idx][0]; + kbemu_send_key(key, val); + return; + } + + if(cfg.kbmap_count[idx] > 1) { + /* multi-key combo */ + unsigned int *keys = cfg.kbmap[idx]; + kbemu_send_combo(keys, cfg.kbmap_count[idx], val); + return; + } + + if (hold) + idx = cfg.map_hold[idx]; + else + idx = cfg.map_button[idx]; + + ev.button.type = EVENT_BUTTON; + ev.button.bnum = idx; + ev.button.press = val; + dispatch_event(dev, &ev); +} + /* process_input processes an device input event, and dispatches * spacenav events to the clients by calling dispatch_event. * relative inputs (INP_MOTION) are accumulated, and dispatched when @@ -154,6 +199,15 @@ void process_input(struct device *dev, struct dev_input *inp) struct dev_event *dev_ev; float sens_rot, sens_trans, axis_sens; spnav_event ev; + struct timeval now, *timeout; + + dev_ev = device_event_in_use(dev); + if(verbose && dev_ev == NULL) + logmsg(LOG_INFO, "adding dev event for device: %s\n", dev->path); + if(dev_ev == NULL && (dev_ev = add_dev_event(dev)) == NULL) { + logmsg(LOG_ERR, "failed to get dev_event\n"); + return; + } switch(inp->type) { case INP_MOTION: @@ -186,13 +240,6 @@ void process_input(struct device *dev, struct dev_input *inp) } inp->val = (int)((float)inp->val * cfg.sensitivity * axis_sens); - dev_ev = device_event_in_use(dev); - if(verbose && dev_ev == NULL) - logmsg(LOG_INFO, "adding dev event for device: %s\n", dev->path); - if(dev_ev == NULL && (dev_ev = add_dev_event(dev)) == NULL) { - logmsg(LOG_ERR, "failed to get dev_event\n"); - break; - } dev_ev->event.type = EVENT_MOTION; dev_ev->event.motion.data = (int*)&dev_ev->event.motion.x; dev_ev->event.motion.data[axis] = sign * inp->val; @@ -205,58 +252,48 @@ void process_input(struct device *dev, struct dev_input *inp) ev.button.bnum = inp->idx; broadcast_event(&ev); - /* check to see if the button has been bound to an action */ - if(cfg.bnact[inp->idx] > 0) { - handle_button_action(cfg.bnact[inp->idx], inp->val); - break; + if(dev_ev && dev_ev->pending) { + update_motion_period(dev_ev); + dispatch_event(dev_ev->dev, &dev_ev->event); + dev_ev->pending = 0; } - /* check to see if we must emulate a keyboard event instead of a - * regular button event for this button - */ - if(cfg.kbmap_count[inp->idx] == 1) { - /* single key */ - unsigned int key = cfg.kbmap[inp->idx][0]; - kbemu_send_key(key, inp->val); - break; - } - if(cfg.kbmap_count[inp->idx] > 1) { - /* multi-key combo */ - unsigned int *keys = cfg.kbmap[inp->idx]; - kbemu_send_combo(keys, cfg.kbmap_count[inp->idx], inp->val); + if (cfg.map_hold[inp->idx] == -1) { + emit_button(dev_ev->dev, inp->idx, inp->val, 0); break; } - dev_ev = device_event_in_use(dev); - if(dev_ev && dev_ev->pending) { - dispatch_event(dev_ev); - dev_ev->pending = 0; - } - inp->idx = cfg.map_button[inp->idx]; - - /* button events are not queued */ - { - struct dev_event dev_button_event; - dev_button_event.dev = dev; - dev_button_event.event.type = EVENT_BUTTON; - dev_button_event.event.button.press = inp->val; - dev_button_event.event.button.bnum = inp->idx; - dispatch_event(&dev_button_event); - } + gettimeofday(&now, NULL); + if (inp->val) { + dev_ev->hold_timeouts[inp->idx] = now; + timeout = &dev_ev->hold_timeouts[inp->idx]; - /* to have them replace motion events in the queue uncomment next section */ - /* dev_ev = add_dev_event(dev); - * dev_ev->event.type = EVENT_BUTTON; - * dev_ev->event.button.press = inp->val; - * dev_ev->event.button.bnum = inp->idx; - * dispatch_event(dev_ev); - */ + timeout->tv_sec += cfg.hold_timeout / 1000; + timeout->tv_usec += cfg.hold_timeout % 1000 * 1000; + + timeout->tv_sec += timeout->tv_usec / 1000000; + timeout->tv_usec = timeout->tv_usec % 1000000; + } else if (dev_ev->hold_timeouts[inp->idx].tv_sec || + dev_ev->hold_timeouts[inp->idx].tv_usec) { + int hold = 0; + + timeout = &dev_ev->hold_timeouts[inp->idx]; + if (TIMERCMP(timeout, <=, &now)) + hold = 1; + + emit_button(dev_ev->dev, inp->idx, 1, hold); + emit_button(dev_ev->dev, inp->idx, 0, hold); + + timeout->tv_sec = 0; + timeout->tv_usec = 0; + } break; case INP_FLUSH: dev_ev = device_event_in_use(dev); if(dev_ev && dev_ev->pending) { - dispatch_event(dev_ev); + update_motion_period(dev_ev); + dispatch_event(dev, &dev_ev->event); dev_ev->pending = 0; } break; @@ -314,26 +351,101 @@ int in_deadzone(struct device *dev) return 1; } -void repeat_last_event(struct device *dev) +void repeat_last_motion_event(struct device *dev) { struct dev_event *dev_ev; + if((dev_ev = device_event_in_use(dev)) == NULL) return; - dispatch_event(dev_ev); + + update_motion_period(dev_ev); + dispatch_event(dev, &dev_ev->event); } -static void dispatch_event(struct dev_event *dev_ev) +int next_button_timeout(struct device *head, struct timeval *timeout) { - struct client *c, *client_iter; - struct device *client_dev; + struct dev_event *dev_ev; + struct timeval now, diff, *min = NULL; + + while (head) { + unsigned int i; + + if (!is_device_valid(head)) + goto next; + + dev_ev = device_event_in_use(head); + if (!dev_ev) + goto next; + + for (i = 0; i < MAX_BUTTONS; i++) { + struct timeval *timeout = &dev_ev->hold_timeouts[i]; + + if (!timeout->tv_sec && !timeout->tv_usec) + continue; + + if (!min || TIMERCMP(timeout, <, min)) + min = timeout; + } + +next: + head = head->next; + } + + if (!min) + return 1; - if(dev_ev->event.type == EVENT_MOTION) { - struct timeval tv; - gettimeofday(&tv, 0); + gettimeofday(&now, 0); + diff.tv_sec = min->tv_sec - now.tv_sec; + diff.tv_usec = min->tv_usec - now.tv_usec; + if (diff.tv_usec < 0) { + diff.tv_sec--; + diff.tv_usec += 1000000; + } + if (diff.tv_sec < 0) { + diff.tv_sec = 0; + diff.tv_usec = 0; + } + + *timeout = diff; + return 0; +} + +void emit_button_timeouts(struct device *head) +{ + struct dev_event *dev_ev; + struct timeval now; + unsigned int i; + + gettimeofday(&now, 0); + while (head) { + dev_ev = device_event_in_use(head); + if (!dev_ev) + goto next; + + for (i = 0; i < MAX_BUTTONS; i++) { + struct timeval *timeout = &dev_ev->hold_timeouts[i]; + + if (!timeout->tv_sec && !timeout->tv_usec) + continue; - dev_ev->event.motion.period = msec_dif(tv, dev_ev->timeval); - dev_ev->timeval = tv; + if (TIMERCMP(timeout, <=, &now)) { + emit_button(dev_ev->dev, i, 1, 1); + emit_button(dev_ev->dev, i, 0, 1); + + timeout->tv_sec = 0; + timeout->tv_usec = 0; + } + } + +next: + head = head->next; } +} + +static void dispatch_event(struct device *dev, spnav_event *ev) +{ + struct client *c, *client_iter; + struct device *client_dev; client_iter = first_client(); while(client_iter) { @@ -344,8 +456,8 @@ static void dispatch_event(struct dev_event *dev_ev) * don't send the event if it originates from a different device */ client_dev = get_client_device(c); - if(!client_dev || client_dev == dev_ev->dev) { - send_event(&dev_ev->event, c); + if(!client_dev || client_dev == dev) { + send_event(ev, c); } } } diff --git a/src/event.h b/src/event.h index 6436008..70272e5 100644 --- a/src/event.h +++ b/src/event.h @@ -100,7 +100,11 @@ void process_input(struct device *dev, struct dev_input *inp); int in_deadzone(struct device *dev); /* dispatches the last event */ -void repeat_last_event(struct device *dev); +void repeat_last_motion_event(struct device *dev); + +int next_button_timeout(struct device *dev, struct timeval *timeout); + +void emit_button_timeouts(struct device *dev); /* broadcasts an event to all clients */ void broadcast_event(spnav_event *ev); diff --git a/src/spnavd.c b/src/spnavd.c index 794f700..5ae5162 100644 --- a/src/spnavd.c +++ b/src/spnavd.c @@ -190,6 +190,7 @@ opt_pidfile: if(!argv[++i]) { int fd, max_fd = 0; struct client *client_iter; struct device *dev; + int repeat_on_timeout = 0; FD_ZERO(&rset); @@ -239,17 +240,26 @@ opt_pidfile: if(!argv[++i]) { if(pfd[0] > max_fd) max_fd = fd; do { - /* if there is at least one device out of the deadzone and repeat is enabled - * wait for only as long as specified in cfg.repeat_msec + struct timeval tv_repeat, tv_button, *timeout = 0; + + repeat_on_timeout = 0; + dev = get_devices(); + if (!next_button_timeout(dev, &tv_button)) + timeout = &tv_button; + + /* If there is at least one device out of the deadzone and repeat is enabled, + * use the repeat interval unless a button timeout occurs sooner. */ - struct timeval tv, *timeout = 0; if(cfg.repeat_msec >= 0) { - dev = get_devices(); while(dev) { if(is_device_valid(dev) && !in_deadzone(dev)) { - tv.tv_sec = cfg.repeat_msec / 1000; - tv.tv_usec = cfg.repeat_msec % 1000 * 1000; - timeout = &tv; + tv_repeat.tv_sec = cfg.repeat_msec / 1000; + tv_repeat.tv_usec = cfg.repeat_msec % 1000 * 1000; + + if (!timeout || TIMERCMP(&tv_repeat, <, &tv_button)) { + timeout = &tv_repeat; + repeat_on_timeout = 1; + } break; } dev = dev->next; @@ -261,17 +271,20 @@ opt_pidfile: if(!argv[++i]) { if(ret > 0) { handle_events(&rset); - } else { + } else if (repeat_on_timeout) { if(cfg.repeat_msec >= 0) { dev = get_devices(); while(dev) { if(!in_deadzone(dev)) { - repeat_last_event(dev); + repeat_last_motion_event(dev); } dev = dev->next; } } } + + dev = get_devices(); + emit_button_timeouts(dev); } return 0; /* unreachable */ } diff --git a/src/spnavd.h b/src/spnavd.h index 3c7861e..93ded82 100644 --- a/src/spnavd.h +++ b/src/spnavd.h @@ -47,6 +47,9 @@ along with this program. If not, see . #endif +#define TIMERCMP(a, cmp, b) ((a)->tv_sec == (b)->tv_sec ? \ + (a)->tv_usec cmp (b)->tv_usec : \ + (a)->tv_sec cmp (b)->tv_sec) /* defined in cfgfile.c */ extern struct cfg cfg, prev_cfg;