#include #include #include #include #include #include #include #include #include #include "sys-queue.h" #include "client.h" #include "display.h" #include "input.h" #include "server.h" #define MAX_DEVICES 16 #define DEVICE_PATH_LEN 32 #define DEVICE_NAME_LEN 64 #define DEVICE_LEN (DEVICE_PATH_LEN + DEVICE_NAME_LEN) #define DEVICE_STEM "/dev/input/event" #define ABSOLUTE_INITIAL -5000 struct device { int fd; int x, y; char *path; char *name; TAILQ_ENTRY(device) next; }; TAILQ_HEAD(device_list, device); static struct device_list devices = TAILQ_HEAD_INITIALIZER(devices); static struct udev_monitor *udev_monitor; /* Key tracker */ /* XXX combine this with the set in dclient.c */ static fd_set keys_down; static int nkeys; /* Absolute mouse coordinates */ static int absx; static int absy; static int is_sak_press(struct input_event *e) { return (e->code == LINPICK_SWITCH_SAK || e->code == LINPICK_LABEL_SAK) && e->value; } static void key_event(struct input_event *e) { static int secure; static int ctrl_down, alt_down; /* General state tracking goes here */ if (e->value && !FD_ISSET(e->code, &keys_down)) { FD_SET(e->code, &keys_down); nkeys++; } if (!e->value && FD_ISSET(e->code, &keys_down)) { FD_CLR(e->code, &keys_down); nkeys--; } /* Specific keys we track for convenience */ if (e->code == KEY_LEFTCTRL) ctrl_down = e->value; else if (e->code == KEY_LEFTALT) alt_down = e->value; /* Check if SAK combination has been entered */ if (!secure && is_sak_press(e) && ctrl_down && alt_down) { /* SAK entered */ active_display->send_key(active_display, 0, KEY_LEFTCTRL, absx, absy); active_display->send_key(active_display, 0, KEY_LEFTALT, absx, absy); display_secure_enter(e->code); secure = 1; } /* Check if keys have been released, causing return to guest */ else if (secure && nkeys == 0) { secure = 0; display_secure_leave(); } /* Send event to server in secure mode */ else if (secure) display_key_event(e); /* Otherwise, send event to the active display */ else active_display->send_key(active_display, e->value, e->code, absx, absy); } static void motion_event(struct input_event *e) { struct mouse_event m = { absx, absy, 0, 0, 0 }; switch(e->code) { case REL_X: m.x = absx = clamp_value(absx + e->value, display_get_width()); m.dx = e->value; break; case REL_Y: m.y = absy = clamp_value(absy + e->value, display_get_height()); m.dy = e->value; break; case REL_WHEEL: m.dz = -e->value; break; default: printf("Unhandled relative axis %d\n", e->code); } display_position_event(absx, absy); active_display->send_mouse(active_display, &m); } static void absolute_event(struct device *d, struct input_event *e) { struct mouse_event m = { absx, absy, 0, 0, 0 }; switch(e->code) { case ABS_X: m.dx = e->value - absx; m.x = absx = e->value; break; case ABS_Y: m.dy = e->value - absy; m.y = absy = e->value; break; default: break; } display_position_event(absx, absy); active_display->send_mouse(active_display, &m); } static void input_event(void *closure) { struct device *d = closure; struct input_event levt[64]; int i, readlen; readlen = read(d->fd, levt, sizeof(levt)); if (readlen <= 0) return; for (i =0; i < readlen / sizeof(*levt); i++) { switch (levt[i].type) { case EV_SYN: switch(levt[i].code) { case SYN_REPORT: break; default: printf("SYN: %d\n", levt[i].code); } break; case EV_KEY: key_event(levt + i); break; case EV_REL: motion_event(levt + i); break; case EV_ABS: absolute_event(d, levt + i); break; case EV_MSC: break; default: printf("%d\n", levt[i].type); } } } static int configure_absolute_device(int fd) { struct input_absinfo ai; if (ioctl(fd, EVIOCGABS(ABS_X), &ai) < 0) return -1; ai.minimum = 0; ai.maximum = display_get_width() - 1; if (ioctl(fd, EVIOCSABS(ABS_X), &ai) < 0) { perror("EVIOCSABS"); return -1; } if (ioctl(fd, EVIOCGABS(ABS_Y), &ai) < 0) return -1; ai.minimum = 0; ai.maximum = display_get_height() - 1; if (ioctl(fd, EVIOCSABS(ABS_Y), &ai) < 0) { perror("EVIOCSABS"); return -1; } return 0; } static int open_device(const char *path) { struct device *d; int fd = -1; char *name = "(unknown)"; unsigned int bits; d = calloc(DEVICE_LEN + sizeof(struct device), 1); if (!d) goto err; fd = open(path, O_RDWR); if (fd < 0) goto err; d->fd = fd; d->x = d->y = ABSOLUTE_INITIAL; d->path = (char *)(d + 1); d->name = (char *)(d + 1) + DEVICE_PATH_LEN; strncpy(d->path, path, DEVICE_PATH_LEN - 1); if (ioctl(fd, EVIOCGRAB, 1) < 0) goto err; if (ioctl(fd, EVIOCGNAME(DEVICE_NAME_LEN - 1), d->name) < 0) goto err; name = d->name; if (ioctl(fd, EVIOCGBIT(0, 4), &bits) < 0) goto err; if ((bits & (1 << EV_ABS)) && configure_absolute_device(fd) < 0) goto err; if (server_set_fd_handler(fd, input_event, d) < 0) goto err; TAILQ_INSERT_TAIL(&devices, d, next); return 0; err: fprintf(stderr, "Warning: failed to open device '%s' on %s\n", name, path); free(d); if (fd >= 0) { ioctl(fd, EVIOCGRAB, 0); close(fd); } return -1; } static void close_device(struct device *d) { ioctl(d->fd, EVIOCGRAB, 0); server_set_fd_handler(d->fd, NULL, NULL); close(d->fd); TAILQ_REMOVE(&devices, d, next); free(d); } static void device_added(struct udev_device *udev_device) { const char *path, *key, *value, *class; struct udev_list_entry *set, *entry; path = udev_device_get_devnode(udev_device); if (!path) return; if (strncmp(path, DEVICE_STEM, strlen(DEVICE_STEM))) return; class = udev_device_get_property_value(udev_device, "ID_CLASS"); if (class && (!strcmp(class, "kbd") || !strcmp(class, "mouse"))) { open_device(path); return; } set = udev_device_get_properties_list_entry(udev_device); udev_list_entry_foreach(entry, set) { key = udev_list_entry_get_name(entry); if (!key) continue; value = udev_list_entry_get_value(entry); if (!strcmp(key, "ID_INPUT_KEY")) { open_device(path); } else if (!strcmp(key, "ID_INPUT_MOUSE")) { open_device(path); } else if (!strcmp(key, "ID_INPUT_JOYSTICK")) { ; } else if (!strcmp(key, "ID_INPUT_TABLET")) { ; } else if (!strcmp(key, "ID_INPUT_TOUCHPAD")) { open_device(path); } else if (!strcmp(key, "ID_INPUT_TOUCHSCREEN")) { ; } } } static void device_removed(struct udev_device *udev_device) { const char *path; struct device *d; path = udev_device_get_devnode(udev_device); if (!path) return; if (strncmp(path, DEVICE_STEM, strlen(DEVICE_STEM))) return; TAILQ_FOREACH(d, &devices, next) if (!strcmp(d->path, path)) { close_device(d); return; } fprintf(stderr, "Warning: failed to close device %s\n", path); } static void udev_event(void *closure) { struct udev_device *udev_device; const char *action; udev_device = udev_monitor_receive_device(udev_monitor); if (udev_device) { action = udev_device_get_action(udev_device); if (action) { if (!strcmp(action, "add")) device_added(udev_device); else if (!strcmp(action, "remove")) device_removed(udev_device); } udev_device_unref(udev_device); } } int input_initialize(int argc, char *argv[]) { struct udev *udev; struct udev_enumerate *enumerate; struct udev_list_entry *devices, *device; int fd; if (!(udev = udev_new())) goto err; if (!(udev_monitor = udev_monitor_new_from_netlink(udev, "udev"))) goto err; if (udev_monitor_enable_receiving(udev_monitor)) goto err; if (!(enumerate = udev_enumerate_new(udev))) goto err; udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(device, devices) { const char *syspath = udev_list_entry_get_name(device); struct udev_device *udev_device = udev_device_new_from_syspath(udev, syspath); device_added(udev_device); udev_device_unref(udev_device); } udev_enumerate_unref(enumerate); fd = udev_monitor_get_fd(udev_monitor); if (server_set_fd_handler(fd, udev_event, NULL) < 0) goto err; absx = display_get_width() / 2; absy = display_get_height() / 2; return 0; err: fprintf(stderr, "Failed to set up udev.\n"); return -1; } void input_shutdown(void) { struct udev *udev; int fd; udev = udev_monitor_get_udev(udev_monitor); fd = udev_monitor_get_fd(udev_monitor); server_set_fd_handler(fd, NULL, NULL); udev_monitor_unref(udev_monitor); udev_unref(udev); while (!TAILQ_EMPTY(&devices)) close_device(TAILQ_FIRST(&devices)); }