#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "shared.h" #include "rcm-server-main.h" #include "rcm-server-evdev.h" #include "rcm-server-keymap.h" struct rc_scancode { __u16 protocol; __u16 reserved[3]; __u64 scancode; }; #define INPUT_KEYMAP_BY_INDEX (1 << 0) struct rc_keymap_entry { __u8 flags; __u8 len; __u16 index; __u32 keycode; union { struct rc_scancode rc; __u8 scancode[32]; } u; char end[]; }; #define RKE_SIZE (sizeof(struct rc_scancode)) static const char * evdev_guess_protocol(struct device *device, uint64_t scancode, uint32_t keycode) { struct rc_keymap_entry rke; unsigned i; if (!device || device->evdev_fd < 0) return NULL; for (i = 0; ; i++) { memset(&rke, 0, sizeof(rke)); rke.len = RKE_SIZE; rke.index = i; rke.flags = INPUT_KEYMAP_BY_INDEX; if (ioctl(device->evdev_fd, EVIOCGKEYCODE_V2, &rke)) break; if (rke.u.rc.scancode != scancode) continue; if (rke.keycode != keycode) continue; if (rke.u.rc.protocol > ARRAY_SIZE(rc_protocols)) return NULL; return rc_protocols[rke.u.rc.protocol]; } return NULL; } static int evdev_clear_keymap(struct device *device) { struct rc_keymap_entry rke; unsigned count = 0; int r = 0; printf("Clearing keymap..."); while (true) { memset(&rke, 0, sizeof(rke)); rke.len = RKE_SIZE; rke.index = 0; rke.flags = INPUT_KEYMAP_BY_INDEX; if (ioctl(device->evdev_fd, EVIOCGKEYCODE_V2, &rke)) { printf("done (%u entries removed)\n", count); break; } /* printf(" Clearing 0x%08llX (0x%02X) to 0x%02X\n", rke.u.rc.scancode, rke.u.rc.protocol, rke.keycode); */ rke.keycode = KEY_RESERVED; if (ioctl(device->evdev_fd, EVIOCSKEYCODE_V2, &rke)) { r = -errno; printf("failed (%s)\n", strerror(errno)); break; } count++; } return r; } static void set_keycode_new(struct device *device, __u16 protocol, __u64 scancode, __u32 keycode) { struct rc_keymap_entry rke; memset(&rke, 0, sizeof(rke)); rke.len = RKE_SIZE; rke.u.rc.protocol = protocol; rke.u.rc.scancode = scancode; rke.keycode = keycode; printf("Setting keycode (new ioctl) 0x%08llX (0x%02X) to 0x%02X\n", rke.u.rc.scancode, rke.u.rc.protocol, rke.keycode); if (ioctl(device->evdev_fd, EVIOCSKEYCODE_V2, &rke)) { printf("Unable to call SETKEYCODE2 ioctl\n"); exit(EXIT_FAILURE); } } static int evdev_set_keymap(struct device *device, struct keymap *keymap) { unsigned i; printf("\tSetting up keymap: %s\n", keymap->name); for (i = 0; i < keymap->keycode_count; i++) set_keycode_new(device, keymap->keycodes[i].protocol, keymap->keycodes[i].scancode, keymap->keycodes[i].lik->value); printf("\tDone: %u entries added\n", i); return 0; } static int evdev_read(sd_event_source *s, int fd, uint32_t revents, void *userdata) { struct device *device = userdata; struct input_event ev; static struct linux_input_keycode *keycode = NULL; static bool pressed = false; static uint32_t scancode; static bool scancode_recv = false; unsigned i; int r; if (fd != device->evdev_fd) fprintf(stderr, "evdev fd mismatch: %i != %i\n", device->evdev_fd, fd); if (revents & EPOLLHUP) { fprintf(stderr, "evdev connection closed!\n"); close(fd); device->evdev_fd = -1; return 0; } if (!(revents & EPOLLIN)) { fprintf(stderr, "unexpected evdev event: %" PRIu32 "\n", revents); return 0; } do { r = libevdev_next_event(device->evdev_dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); if (r != LIBEVDEV_READ_STATUS_SUCCESS) continue; printf("Event: %s %s %u (0x%08x)\n", libevdev_event_type_get_name(ev.type), libevdev_event_code_get_name(ev.type, ev.code), ev.value, ev.value); switch (ev.type) { case EV_KEY: if (keycode) printf("Reading from evdev - multiple keycodes?\n"); for (i = 0; linux_input_keycodes[i].name; i++) { if (linux_input_keycodes[i].value == ev.code) break; } if (!linux_input_keycodes[i].name) { printf("evdev - unknown keycode (%u)\n", ev.code); break; } keycode = &linux_input_keycodes[i]; if (ev.value) pressed = true; break; case EV_MSC: if (ev.code == MSC_SCAN) { if (scancode_recv) printf("Reading from evdev - multiple scancodes?\n"); scancode_recv = true; scancode = ev.value; } break; case EV_SYN: if (keycode || scancode_recv) { const char *protocol; /* FIXME: protocol reporting needs kernel support */ if (scancode_recv) protocol = evdev_guess_protocol(device, scancode, keycode ? keycode->value : KEY_RESERVED); printf("evdev -"); if (scancode_recv) { printf(" protocol %s (guessed)", protocol); printf(" scancode 0x%08x", scancode); } if (keycode) { printf(" keycode %s (%u)", keycode->name, keycode->value); printf(" %s", pressed ? "pressed" : "released"); } printf("\n"); if (scancode_recv) sd_bus_emit_signal(device->mgr->bus, device->path, "org.gnome.RemoteControlManager.Device", "KeyPressed", "sts", protocol ? protocol : "NEC", scancode, keycode ? keycode->name : "KEY_RESERVED"); else if (keycode && !pressed) sd_bus_emit_signal(device->mgr->bus, device->path, "org.gnome.RemoteControlManager.Device", "KeyReleased", "s", keycode->name); } scancode_recv = false; pressed = false; keycode = NULL; break; default: break; } } while (r == LIBEVDEV_READ_STATUS_SUCCESS || r == LIBEVDEV_READ_STATUS_SYNC); return 0; } int evdev_setup(struct device *device, const char *path) { int r; struct keymap *keymap; /* FIXME: Fixup error handling */ if (!device) return -EINVAL; if (device->evdev_fd >= 0) { printf("Multiple evdev devices!?\n"); return 0; } device->evdev_fd = open(path, O_RDONLY | O_NONBLOCK); r = libevdev_new_from_fd(device->evdev_fd, &device->evdev_dev); if (r < 0) { printf("Failed to open evdev device %s: %s\n", path, strerror(-r)); close(device->evdev_fd); return r; } device->input_name = strdup(libevdev_get_name(device->evdev_dev)); printf("Performing evdev setup for device %s (%s)\n", device->path, libevdev_get_name(device->evdev_dev)); r = evdev_clear_keymap(device); if (r < 0) return r; list_for_each_entry(keymap, &device->keymaps, list) r = evdev_set_keymap(device, keymap); if (sd_event_add_io(device->mgr->event, &device->evdev_ev, device->evdev_fd, EPOLLIN, evdev_read, device) < 0) { printf("Failed to add event source for evdev device %s: %s\n", path, strerror(errno)); close(device->evdev_fd); device->evdev_fd = -1; return -errno; } return r; }