You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
539 lines
10 KiB
539 lines
10 KiB
#include "keyd.h" |
|
|
|
#define VKBD_NAME "keyd virtual keyboard" |
|
|
|
struct config_ent { |
|
struct config config; |
|
struct keyboard *kbd; |
|
struct config_ent *next; |
|
}; |
|
|
|
static int ipcfd = -1; |
|
static struct vkbd *vkbd = NULL; |
|
static struct config_ent *configs; |
|
|
|
static uint8_t keystate[256]; |
|
|
|
static int listeners[32]; |
|
static size_t nr_listeners = 0; |
|
|
|
static void free_configs() |
|
{ |
|
struct config_ent *ent = configs; |
|
while (ent) { |
|
struct config_ent *tmp = ent; |
|
ent = ent->next; |
|
free(tmp->kbd); |
|
free(tmp); |
|
} |
|
|
|
configs = NULL; |
|
} |
|
|
|
static void cleanup() |
|
{ |
|
free_configs(); |
|
free_vkbd(vkbd); |
|
} |
|
|
|
static void clear_vkbd() |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < 256; i++) |
|
if (keystate[i]) { |
|
vkbd_send_key(vkbd, i, 0); |
|
keystate[i] = 0; |
|
} |
|
} |
|
|
|
static void send_key(uint8_t code, uint8_t state) |
|
{ |
|
keystate[code] = state; |
|
vkbd_send_key(vkbd, code, state); |
|
} |
|
|
|
static void add_listener(int con) |
|
{ |
|
struct timeval tv; |
|
|
|
/* |
|
* In order to avoid blocking the main event loop, allow up to 50ms for |
|
* slow clients to relieve back pressure before dropping them. |
|
*/ |
|
tv.tv_usec = 50000; |
|
tv.tv_sec = 0; |
|
|
|
if (nr_listeners == ARRAY_SIZE(listeners)) { |
|
char s[] = "Max listeners exceeded\n"; |
|
xwrite(con, &s, sizeof s); |
|
|
|
close(con); |
|
return; |
|
} |
|
|
|
setsockopt(con, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); |
|
|
|
listeners[nr_listeners++] = con; |
|
} |
|
|
|
static void on_layer_change(const struct keyboard *kbd, const char *name, uint8_t state) |
|
{ |
|
size_t i; |
|
char buf[MAX_LAYER_NAME_LEN+2]; |
|
ssize_t bufsz; |
|
|
|
int keep[ARRAY_SIZE(listeners)]; |
|
size_t n = 0; |
|
|
|
if (kbd->config.layer_indicator) { |
|
for (i = 0; i < device_table_sz; i++) |
|
if (device_table[i].data == kbd) |
|
device_set_led(&device_table[i], 1, state); |
|
} |
|
|
|
if (!nr_listeners) |
|
return; |
|
|
|
bufsz = snprintf(buf, sizeof(buf), "%c%s\n", state ? '+' : '-', name); |
|
for (i = 0; i < nr_listeners; i++) { |
|
ssize_t nw = write(listeners[i], buf, bufsz); |
|
|
|
if (nw == bufsz) |
|
keep[n++] = listeners[i]; |
|
else |
|
close(listeners[i]); |
|
} |
|
|
|
if (n != nr_listeners) { |
|
nr_listeners = n; |
|
memcpy(listeners, keep, n * sizeof(int)); |
|
} |
|
} |
|
|
|
static void load_configs() |
|
{ |
|
DIR *dh = opendir(CONFIG_DIR); |
|
struct dirent *dirent; |
|
|
|
if (!dh) { |
|
perror("opendir"); |
|
exit(-1); |
|
} |
|
|
|
configs = NULL; |
|
|
|
while ((dirent = readdir(dh))) { |
|
char path[1024]; |
|
int len; |
|
|
|
if (dirent->d_type == DT_DIR) |
|
continue; |
|
|
|
len = snprintf(path, sizeof path, "%s/%s", CONFIG_DIR, dirent->d_name); |
|
|
|
if (len >= 5 && !strcmp(path + len - 5, ".conf")) { |
|
struct config_ent *ent = calloc(1, sizeof(struct config_ent)); |
|
|
|
keyd_log("CONFIG: parsing b{%s}\n", path); |
|
|
|
if (!config_parse(&ent->config, path)) { |
|
struct output output = { |
|
.send_key = send_key, |
|
.on_layer_change = on_layer_change, |
|
}; |
|
ent->kbd = new_keyboard(&ent->config, &output); |
|
|
|
ent->next = configs; |
|
configs = ent; |
|
} else { |
|
free(ent); |
|
keyd_log("DEVICE: y{WARNING} failed to parse %s\n", path); |
|
} |
|
|
|
} |
|
} |
|
|
|
closedir(dh); |
|
} |
|
|
|
static struct config_ent *lookup_config_ent(uint16_t vendor, |
|
uint16_t product, |
|
uint8_t flags) |
|
{ |
|
struct config_ent *ent = configs; |
|
struct config_ent *match = NULL; |
|
int rank = 0; |
|
|
|
while (ent) { |
|
int r = config_check_match(&ent->config, vendor, product, flags); |
|
|
|
if (r > rank) { |
|
match = ent; |
|
rank = r; |
|
} |
|
|
|
ent = ent->next; |
|
} |
|
|
|
/* The wildcard should not match mice. */ |
|
if (rank == 1 && (flags == ID_MOUSE)) |
|
return NULL; |
|
else |
|
return match; |
|
} |
|
|
|
static void manage_device(struct device *dev) |
|
{ |
|
uint8_t flags = 0; |
|
struct config_ent *ent; |
|
|
|
if (!strcmp(dev->name, VKBD_NAME)) |
|
return; |
|
|
|
if (dev->capabilities & CAP_KEYBOARD) |
|
flags |= ID_KEYBOARD; |
|
if (dev->capabilities & (CAP_MOUSE|CAP_MOUSE_ABS)) |
|
flags |= ID_MOUSE; |
|
|
|
if ((ent = lookup_config_ent(dev->vendor_id, dev->product_id, flags))) { |
|
if (device_grab(dev)) { |
|
keyd_log("DEVICE: y{WARNING} Failed to grab %s\n", dev->path); |
|
dev->data = NULL; |
|
return; |
|
} |
|
|
|
keyd_log("DEVICE: g{match} %04hx:%04hx %s\t(%s)\n", |
|
dev->vendor_id, dev->product_id, |
|
ent->config.path, |
|
dev->name); |
|
|
|
dev->data = ent->kbd; |
|
} else { |
|
dev->data = NULL; |
|
device_ungrab(dev); |
|
keyd_log("DEVICE: r{ignoring} %04hx:%04hx (%s)\n", |
|
dev->vendor_id, dev->product_id, dev->name); |
|
} |
|
} |
|
|
|
static void reload() |
|
{ |
|
size_t i; |
|
|
|
free_configs(); |
|
load_configs(); |
|
|
|
for (i = 0; i < device_table_sz; i++) |
|
manage_device(&device_table[i]); |
|
|
|
clear_vkbd(); |
|
} |
|
|
|
static void send_success(int con) |
|
{ |
|
struct ipc_message msg = {0}; |
|
|
|
msg.type = IPC_SUCCESS;; |
|
msg.sz = 0; |
|
|
|
xwrite(con, &msg, sizeof msg); |
|
close(con); |
|
} |
|
|
|
static void send_fail(int con, const char *fmt, ...) |
|
{ |
|
struct ipc_message msg = {0}; |
|
va_list args; |
|
|
|
va_start(args, fmt); |
|
|
|
msg.type = IPC_FAIL; |
|
msg.sz = vsnprintf(msg.data, sizeof(msg.data), fmt, args); |
|
|
|
xwrite(con, &msg, sizeof msg); |
|
close(con); |
|
|
|
va_end(args); |
|
} |
|
|
|
static int input(char *buf, size_t sz, uint32_t timeout) |
|
{ |
|
size_t i; |
|
uint32_t codepoint; |
|
uint8_t codes[4]; |
|
|
|
int csz; |
|
|
|
while ((csz = utf8_read_char(buf, &codepoint))) { |
|
int found = 0; |
|
char s[2]; |
|
|
|
if (csz == 1) { |
|
uint8_t code, mods; |
|
s[0] = (char)codepoint; |
|
s[1] = 0; |
|
|
|
found = 1; |
|
if (!parse_key_sequence(s, &code, &mods)) { |
|
if (mods & MOD_SHIFT) { |
|
vkbd_send_key(vkbd, KEYD_LEFTSHIFT, 1); |
|
vkbd_send_key(vkbd, code, 1); |
|
vkbd_send_key(vkbd, code, 0); |
|
vkbd_send_key(vkbd, KEYD_LEFTSHIFT, 0); |
|
} else { |
|
vkbd_send_key(vkbd, code, 1); |
|
vkbd_send_key(vkbd, code, 0); |
|
} |
|
} else if ((char)codepoint == ' ') { |
|
vkbd_send_key(vkbd, KEYD_SPACE, 1); |
|
vkbd_send_key(vkbd, KEYD_SPACE, 0); |
|
} else if ((char)codepoint == '\n') { |
|
vkbd_send_key(vkbd, KEYD_ENTER, 1); |
|
vkbd_send_key(vkbd, KEYD_ENTER, 0); |
|
} else if ((char)codepoint == '\t') { |
|
vkbd_send_key(vkbd, KEYD_TAB, 1); |
|
vkbd_send_key(vkbd, KEYD_TAB, 0); |
|
} else { |
|
found = 0; |
|
} |
|
} |
|
|
|
if (!found) { |
|
int idx = unicode_lookup_index(codepoint); |
|
if (idx < 0) { |
|
err("ERROR: could not find code for \"%.*s\"", csz, buf); |
|
return -1; |
|
} |
|
|
|
unicode_get_sequence(idx, codes); |
|
|
|
for (i = 0; i < 4; i++) { |
|
vkbd_send_key(vkbd, codes[i], 1); |
|
vkbd_send_key(vkbd, codes[i], 0); |
|
} |
|
} |
|
buf+=csz; |
|
|
|
if (timeout) |
|
usleep(timeout); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void handle_client(int con) |
|
{ |
|
struct ipc_message msg; |
|
|
|
xread(con, &msg, sizeof msg); |
|
|
|
if (msg.sz >= sizeof(msg.data)) { |
|
send_fail(con, "maximum message size exceeded"); |
|
return; |
|
} |
|
msg.data[msg.sz] = 0; |
|
|
|
if (msg.timeout > 1000000) { |
|
send_fail(con, "timeout cannot exceed 1000 ms"); |
|
return; |
|
} |
|
|
|
switch (msg.type) { |
|
struct config_ent *ent; |
|
int success; |
|
struct macro macro; |
|
|
|
case IPC_MACRO: |
|
while (msg.sz && msg.data[msg.sz-1] == '\n') |
|
msg.data[--msg.sz] = 0; |
|
|
|
if (macro_parse(msg.data, ¯o)) { |
|
send_fail(con, "%s", errstr); |
|
return; |
|
} |
|
|
|
macro_execute(send_key, ¯o, msg.timeout); |
|
send_success(con); |
|
|
|
break; |
|
case IPC_INPUT: |
|
if (input(msg.data, msg.sz, msg.timeout)) |
|
send_fail(con, "%s", errstr); |
|
else |
|
send_success(con); |
|
break; |
|
case IPC_RELOAD: |
|
reload(); |
|
send_success(con); |
|
break; |
|
case IPC_LAYER_LISTEN: |
|
add_listener(con); |
|
break; |
|
case IPC_BIND: |
|
success = 0; |
|
|
|
if (msg.sz == sizeof(msg.data)) { |
|
send_fail(con, "bind expression size exceeded"); |
|
return; |
|
} |
|
|
|
msg.data[msg.sz] = 0; |
|
|
|
for (ent = configs; ent; ent = ent->next) { |
|
if (!kbd_eval(ent->kbd, msg.data)) |
|
success = 1; |
|
} |
|
|
|
if (success) |
|
send_success(con); |
|
else |
|
send_fail(con, "%s", errstr); |
|
|
|
|
|
break; |
|
default: |
|
send_fail(con, "Unknown command"); |
|
break; |
|
} |
|
} |
|
|
|
static int event_handler(struct event *ev) |
|
{ |
|
static int last_time = 0; |
|
static int timeout = 0; |
|
static struct keyboard *timeout_kbd = NULL; |
|
struct key_event kev = {0}; |
|
|
|
timeout -= ev->timestamp - last_time; |
|
last_time = ev->timestamp; |
|
|
|
timeout = timeout < 0 ? 0 : timeout; |
|
|
|
switch (ev->type) { |
|
case EV_TIMEOUT: |
|
if (!timeout_kbd) |
|
return 0; |
|
|
|
kev.code = 0; |
|
kev.timestamp = ev->timestamp; |
|
|
|
timeout = kbd_process_events(timeout_kbd, &kev, 1); |
|
break; |
|
case EV_DEV_EVENT: |
|
if (ev->dev->data) { |
|
struct keyboard *kbd = ev->dev->data; |
|
timeout_kbd = ev->dev->data; |
|
switch (ev->devev->type) { |
|
case DEV_KEY: |
|
dbg("input %s %s", KEY_NAME(ev->devev->code), ev->devev->pressed ? "down" : "up"); |
|
|
|
kev.code = ev->devev->code; |
|
kev.pressed = ev->devev->pressed; |
|
kev.timestamp = ev->timestamp; |
|
|
|
timeout = kbd_process_events(kbd, &kev, 1); |
|
break; |
|
case DEV_MOUSE_MOVE: |
|
if (kbd->scroll.active) { |
|
if (kbd->scroll.sensitivity == 0) |
|
break; |
|
int xticks, yticks; |
|
|
|
kbd->scroll.y += ev->devev->y; |
|
kbd->scroll.x += ev->devev->x; |
|
|
|
yticks = kbd->scroll.y / kbd->scroll.sensitivity; |
|
kbd->scroll.y %= kbd->scroll.sensitivity; |
|
|
|
xticks = kbd->scroll.x / kbd->scroll.sensitivity; |
|
kbd->scroll.x %= kbd->scroll.sensitivity; |
|
|
|
vkbd_mouse_scroll(vkbd, 0, -1*yticks); |
|
vkbd_mouse_scroll(vkbd, 0, xticks); |
|
} else { |
|
vkbd_mouse_move(vkbd, ev->devev->x, ev->devev->y); |
|
} |
|
break; |
|
case DEV_MOUSE_MOVE_ABS: |
|
vkbd_mouse_move_abs(vkbd, ev->devev->x, ev->devev->y); |
|
break; |
|
default: |
|
break; |
|
case DEV_MOUSE_SCROLL: |
|
/* |
|
* Treat scroll events as mouse buttons so oneshot and the like get |
|
* cleared. |
|
*/ |
|
if (timeout_kbd) { |
|
kev.code = KEYD_EXTERNAL_MOUSE_BUTTON; |
|
kev.pressed = 1; |
|
kev.timestamp = ev->timestamp; |
|
|
|
kbd_process_events(ev->dev->data, &kev, 1); |
|
|
|
kev.pressed = 0; |
|
timeout = kbd_process_events(ev->dev->data, &kev, 1); |
|
} |
|
|
|
vkbd_mouse_scroll(vkbd, ev->devev->x, ev->devev->y); |
|
break; |
|
} |
|
} |
|
|
|
break; |
|
case EV_DEV_ADD: |
|
manage_device(ev->dev); |
|
break; |
|
case EV_DEV_REMOVE: |
|
keyd_log("DEVICE: r{removed}\t%04hx:%04hx %s\n", |
|
ev->dev->vendor_id, |
|
ev->dev->product_id, |
|
ev->dev->name); |
|
|
|
break; |
|
case EV_FD_ACTIVITY: |
|
if (ev->fd == ipcfd) { |
|
int con = accept(ipcfd, NULL, 0); |
|
if (con < 0) { |
|
perror("accept"); |
|
exit(-1); |
|
} |
|
|
|
handle_client(con); |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return timeout; |
|
} |
|
|
|
int run_daemon(int argc, char *argv[]) |
|
{ |
|
ipcfd = ipc_create_server(SOCKET_PATH); |
|
if (ipcfd < 0) |
|
die("failed to create %s (another instance already running?)", SOCKET_PATH); |
|
|
|
vkbd = vkbd_init(VKBD_NAME); |
|
|
|
setvbuf(stdout, NULL, _IOLBF, 0); |
|
setvbuf(stderr, NULL, _IOLBF, 0); |
|
|
|
if (nice(-20) == -1) { |
|
perror("nice"); |
|
exit(-1); |
|
} |
|
|
|
evloop_add_fd(ipcfd); |
|
|
|
reload(); |
|
|
|
atexit(cleanup); |
|
|
|
keyd_log("Starting keyd "VERSION"\n"); |
|
evloop(event_handler); |
|
|
|
return 0; |
|
}
|
|
|