diff --git a/include/keyboard.h b/include/keyboard.h new file mode 100644 index 0000000..739b520 --- /dev/null +++ b/include/keyboard.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Andri Yngvason + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +struct zwp_virtual_keyboard_v1; +struct table_entry; + +struct keyboard { + struct zwp_virtual_keyboard_v1* virtual_keyboard; + + struct xkb_context* context; + struct xkb_keymap* keymap; + struct xkb_state* state; + + size_t lookup_table_size; + size_t lookup_table_length; + struct table_entry* lookup_table; +}; + +int keyboard_init(struct keyboard* self); +void keyboard_destroy(struct keyboard* self); +void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed); diff --git a/meson.build b/meson.build index d70c714..037d7a0 100644 --- a/meson.build +++ b/meson.build @@ -21,6 +21,7 @@ pixman = dependency('pixman-1') libuv = dependency('libuv') egl = dependency('egl') glesv2 = dependency('glesv2') +xkbcommon = dependency('xkbcommon') wayland_client = dependency('wayland-client') neatvnc_project = subproject( @@ -47,6 +48,7 @@ sources = [ 'src/screencopy.c', 'src/output.c', 'src/pointer.c', + 'src/keyboard.c', ] dependencies = [ @@ -57,6 +59,7 @@ dependencies = [ glesv2, wayland_client, neatvnc, + xkbcommon, client_protos, ] diff --git a/src/keyboard.c b/src/keyboard.c new file mode 100644 index 0000000..d80f947 --- /dev/null +++ b/src/keyboard.c @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2019 Andri Yngvason + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "virtual-keyboard-unstable-v1.h" +#include "keyboard.h" +#include "shm.h" + +struct table_entry { + xkb_keysym_t symbol; + xkb_keycode_t code; +}; + +static void append_entry(struct keyboard* self, xkb_keysym_t symbol, + xkb_keycode_t code) +{ + if (self->lookup_table_size <= self->lookup_table_length) { + size_t new_size = self->lookup_table_size * 2; + struct table_entry* table = + realloc(self->lookup_table, new_size * sizeof(*table)); + if (!table) + return; // TODO: Report this + + self->lookup_table_size = new_size; + self->lookup_table = table; + } + + struct table_entry* entry = + &self->lookup_table[self->lookup_table_length++]; + + entry->symbol = symbol; + entry->code = code; +} + +static void key_iter(struct xkb_keymap* map, xkb_keycode_t code, void* userdata) +{ + struct keyboard* self = userdata; + + size_t n_levels = xkb_keymap_num_levels_for_key(map, code, 0); + + for (size_t level = 0; level < n_levels; level++) { + const xkb_keysym_t* symbols; + size_t n_syms = xkb_keymap_key_get_syms_by_level(map, code, 0, + level, + &symbols); + + for (size_t sym_idx = 0; sym_idx < n_syms; sym_idx++) + append_entry(self, symbols[sym_idx], code); + } +} + +static int compare_symbols(const void* a, const void* b) +{ + const struct table_entry* x = a; + const struct table_entry* y = b; + + return x->symbol < y->symbol ? -1 : x->symbol > y->symbol; +} + +static int create_lookup_table(struct keyboard* self) +{ + self->lookup_table_length = 0; + self->lookup_table_size = 128; + + self->lookup_table = + malloc(self->lookup_table_size * sizeof(*self->lookup_table)); + if (!self->lookup_table) + return -1; + + xkb_keymap_key_for_each(self->keymap, key_iter, self); + + qsort(self->lookup_table, self->lookup_table_length, + sizeof(*self->lookup_table), compare_symbols); + + return 0; +} + +void keyboard_dump_lookup_table(const struct keyboard* self) +{ + for (size_t i = 0; i < self->lookup_table_length; i++) { + struct table_entry* entry = &self->lookup_table[i]; + + const char* code_name = + xkb_keymap_key_get_name(self->keymap, entry->code); + + printf("%x\t%s\n", entry->symbol, code_name); + } +} + +int keyboard_init(struct keyboard* self) +{ + self->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!self->context) + return -1; + + // TODO: Allow the user to specify the layout + self->keymap = xkb_keymap_new_from_names(self->context, NULL, 0); + if (!self->keymap) + goto keymap_failure; + + self->state = xkb_state_new(self->keymap); + if (!self->state) + goto state_failure; + + if (create_lookup_table(self) < 0) + goto table_failure; + + char* keymap_string = + xkb_keymap_get_as_string(self->keymap, + XKB_KEYMAP_FORMAT_TEXT_V1); + if (!keymap_string) + goto keymap_string_failure; + + size_t keymap_len = strlen(keymap_string); + + int keymap_fd = shm_alloc_fd(0); + if (keymap_fd < 0) + goto fd_failure; + + // TODO: Check that write finished writing everything + write(keymap_fd, keymap_string, keymap_len); + + free(keymap_string); + + zwp_virtual_keyboard_v1_keymap(self->virtual_keyboard, + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + keymap_fd, keymap_len); + + close(keymap_fd); + + return 0; + +fd_failure: + free(keymap_string); +keymap_string_failure: + free(self->lookup_table); +table_failure: + xkb_state_unref(self->state); +state_failure: + xkb_keymap_unref(self->keymap); +keymap_failure: + xkb_context_unref(self->context); + return -1; +} + +void keyboard_destroy(struct keyboard* self) +{ + free(self->lookup_table); + xkb_state_unref(self->state); + xkb_keymap_unref(self->keymap); + xkb_context_unref(self->context); +} + +xkb_keycode_t keyboard_find_keycode(const struct keyboard* self, + xkb_keysym_t symbol) +{ + struct table_entry* entry = + bsearch(&symbol, self->lookup_table, self->lookup_table_length, + sizeof(*self->lookup_table), compare_symbols); + + return entry ? entry->code : XKB_KEYCODE_INVALID; +} + +static void keyboard_apply_mods(struct keyboard* self, xkb_keycode_t code, + bool is_pressed) +{ + enum xkb_state_component comp, compmask; + xkb_mod_mask_t depressed, latched, locked, group; + + comp = xkb_state_update_key(self->state, code, + is_pressed ? XKB_KEY_DOWN : XKB_KEY_UP); + + compmask = XKB_STATE_MODS_DEPRESSED | + XKB_STATE_MODS_LATCHED | + XKB_STATE_MODS_LOCKED | + XKB_STATE_MODS_EFFECTIVE; + + if (!(comp & compmask)) + return; + + depressed = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_DEPRESSED); + latched = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LATCHED); + locked = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_LOCKED); + group = xkb_state_serialize_mods(self->state, XKB_STATE_MODS_EFFECTIVE); + + // TODO: Handle errors + zwp_virtual_keyboard_v1_modifiers(self->virtual_keyboard, depressed, + latched, locked, group); +} + +void keyboard_feed(struct keyboard* self, xkb_keysym_t symbol, bool is_pressed) +{ + xkb_keycode_t code = keyboard_find_keycode(self, symbol); + if (code == XKB_KEYCODE_INVALID) + return; // TODO: Notify the user about this + + // TODO: Handle errors + zwp_virtual_keyboard_v1_key(self->virtual_keyboard, 0, code - 8, + is_pressed ? WL_KEYBOARD_KEY_STATE_PRESSED + : WL_KEYBOARD_KEY_STATE_RELEASED); + + // TODO: This could cause some synchronisation problems with other + // keyboards in the seat. + keyboard_apply_mods(self, code, is_pressed); +} diff --git a/src/main.c b/src/main.c index 333f23a..1b83399 100644 --- a/src/main.c +++ b/src/main.c @@ -34,6 +34,7 @@ #include "wlr-export-dmabuf-unstable-v1.h" #include "wlr-screencopy-unstable-v1.h" #include "wlr-virtual-pointer-unstable-v1.h" +#include "virtual-keyboard-unstable-v1.h" #include "render.h" #include "dmabuf.h" #include "screencopy.h" @@ -41,6 +42,7 @@ #include "logging.h" #include "output.h" #include "pointer.h" +#include "keyboard.h" enum frame_capture_backend_type { FRAME_CAPTURE_BACKEND_NONE = 0, @@ -52,6 +54,9 @@ struct wayvnc { struct wl_display* display; struct wl_registry* registry; struct wl_list outputs; + struct wl_seat* seat; + + struct zwp_virtual_keyboard_manager_v1* keyboard_manager; struct renderer renderer; const struct output* selected_output; @@ -60,6 +65,7 @@ struct wayvnc { struct screencopy screencopy_backend; struct frame_capture* capture_backend; struct pointer pointer_backend; + struct keyboard keyboard_backend; uv_poll_t wayland_poller; uv_prepare_t flusher; @@ -149,6 +155,20 @@ static void registry_add(void* data, struct wl_registry* registry, 1); return; } + + if (strcmp(interface, wl_seat_interface.name) == 0) { + self->seat = + wl_registry_bind(registry, id, &wl_seat_interface, 7); + return; + } + + if (strcmp(interface, zwp_virtual_keyboard_manager_v1_interface.name) == 0) { + self->keyboard_manager = + wl_registry_bind(registry, id, + &zwp_virtual_keyboard_manager_v1_interface, + 1); + return; + } } static void registry_remove(void* data, struct wl_registry* registry, @@ -168,6 +188,8 @@ static void registry_remove(void* data, struct wl_registry* registry, void wayvnc_destroy(struct wayvnc* self) { output_list_destroy(&self->outputs); + zwp_virtual_keyboard_v1_destroy(self->keyboard_backend.virtual_keyboard); + zwp_virtual_keyboard_manager_v1_destroy(self->keyboard_manager); zwlr_virtual_pointer_manager_v1_destroy(self->pointer_backend.manager); if (self->dmabuf_backend.manager) { pointer_destroy(&self->pointer_backend); @@ -218,6 +240,15 @@ static int init_wayland(struct wayvnc* self) zwlr_virtual_pointer_manager_v1_interface.name); } + assert(self->seat); + assert(self->keyboard_manager); + + self->keyboard_backend.virtual_keyboard = + zwp_virtual_keyboard_manager_v1_create_virtual_keyboard( + self->keyboard_manager, self->seat); + + keyboard_init(&self->keyboard_backend); + return 0; export_manager_failure: @@ -305,6 +336,15 @@ static void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y, pointer_set(&wayvnc->pointer_backend, x, y, button_mask); } +static void on_key_event(struct nvnc_client* client, uint32_t symbol, + bool is_pressed) +{ + struct nvnc* nvnc = nvnc_get_server(client); + struct wayvnc* wayvnc = nvnc_get_userdata(nvnc); + + keyboard_feed(&wayvnc->keyboard_backend, symbol, is_pressed); +} + int init_nvnc(struct wayvnc* self, const char* addr, uint16_t port) { self->nvnc = nvnc_open(addr, port); @@ -326,6 +366,9 @@ int init_nvnc(struct wayvnc* self, const char* addr, uint16_t port) if (self->pointer_backend.manager) nvnc_set_pointer_fn(self->nvnc, on_pointer_event); + if (self->keyboard_backend.virtual_keyboard) + nvnc_set_key_fn(self->nvnc, on_key_event); + return 0; }