diff --git a/include/wlr/types/wlr_ext_virtual_keyboard_v1.h b/include/wlr/types/wlr_ext_virtual_keyboard_v1.h new file mode 100644 index 000000000..ec3e8b017 --- /dev/null +++ b/include/wlr/types/wlr_ext_virtual_keyboard_v1.h @@ -0,0 +1,45 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_EXT_VIRTUAL_KEYBOARD_V1_H +#define WLR_TYPES_EXT_VIRTUAL_KEYBOARD_V1_H + +#include +#include + +struct wlr_ext_virtual_keyboard_manager_v1 { + struct wl_global *global; + struct wl_list keyboards; // wlr_ext_virtual_keyboard_v1.link + + struct { + struct wl_signal new_keyboard; // struct wlr_ext_virtual_keyboard_v1 + struct wl_signal destroy; + } events; + + // private state + struct wl_listener display_destroy; + +}; + +struct wlr_ext_virtual_keyboard_v1 { + struct wlr_keyboard keyboard; + struct wl_resource *resource; + struct wlr_ext_virtual_keyboard_manager_v1 *manager; + struct wlr_seat *seat; + bool has_keymap; + + struct wl_list link; // wlr_ext_virtual_keyboard_manager_v1.ext_virtual_keyboards +}; + +struct wlr_ext_virtual_keyboard_manager_v1* wlr_ext_virtual_keyboard_manager_v1_create( + struct wl_display *display, uint32_t version); + +struct wlr_ext_virtual_keyboard_v1 *wlr_ext_virtual_keyboard_v1_try_from_wlr_input_device( + struct wlr_input_device *wlr_dev); + +#endif diff --git a/protocol/ext-virtual-keyboard-v1.xml b/protocol/ext-virtual-keyboard-v1.xml new file mode 100644 index 000000000..b472edc01 --- /dev/null +++ b/protocol/ext-virtual-keyboard-v1.xml @@ -0,0 +1,186 @@ + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2013 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2018 Purism SPC + Copyright © 2023 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows privileged clients to create a virtual keyboard device + and emit synthetic input events. It is intended to be used by remote + desktop applications and on-screen keyboards. + + The compositor may choose to restrict this protocol to a special client + launched by the compositor itself or expose it to all privileged clients, + this is compositor policy. + + The key words "must", "must not", "required", "shall", "shall not", "should", + "should not", "recommended", "may", and "optional" in this document are to + be interpreted as described in IETF RFC 2119. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + A virtual keyboard manager allows an application to create virtual + keyboards. + + + + + Create a new virtual keyboard. + + If the seat is null, the compositor will pick one. If the seat is + non-null, the compositor must attach the virtual keyboard to that seat + or immediately send the finished event on the newly created virtual + keyboard object. + + + + + + + + Destroy the virtual keyboard manager. + + Existing ext_virtual_keyboard_v1 objects remain valid. + + + + + + + The virtual keyboard interface allows a client to send synthetic keyboard + input events. + + The virtual keyboard has exclusive control over its modifier state. The + compositor must ensure that the correct modifier state is applied before + passing on events from the virtual keyboard to wl_keyboard. + + + + + + + + + + + Provide a file descriptor to the compositor which can be memory-mapped + to provide a keyboard mapping description. + + The FD must be mapped with MAP_PRIVATE by the recipient, as MAP_SHARED + may fail. + + The underlying keymap data must remain available as long as the object + hasn't been destroyed. + + If the keymap format is invalid, the invalid_keymap protocol error is + raised. The only valid format is xkb_v1. + + The compositor must take care to notify clients of the correct keymap + before it sends other wl_keyboard events, i.e. set the keymap + corresponding to the event source. + + + + + + + + + Notify that a key was pressed or released. + + The time argument is a timestamp with millisecond granularity in the + CLOCK_MONOTONIC domain. + + If the state is invalid, the invalid_key_state protocol error is raised. + If no keymap has been set, the missing_keymap protocol error is raised. + + Valid values for state are released, pressed and repeated from + wl_keyboard.key_state. All other values are invalid. + + + + + + + + + Notify that the modifier and/or group state has changed. + + If no keymap has been set, the missing_keymap protocol error is raised. + + + + + + + + + + Sets the repeat info for associated wl_keyboard objects. + + This request should be passed by the compositor to clients via the + wl_keyboard.repeat_info event. + + + + + + + + Destroy the virtual keyboard. + + The compositor should release any pressed keys. + + + + + + The compositor has decided that the virtual keyboard should be + destroyed as it will no longer be used by the compositor. Exactly when + this event is sent is compositor policy, but it must never be sent more + than once for a given virtual keyboard object. + + This might be sent because the user has decided to stop the virtual + input device, or the compositor has decided to deny the client request + for some other reason. + + Upon receiving this event, the client should send a destroy request. + + + + diff --git a/protocol/meson.build b/protocol/meson.build index 30aeb68c6..4d6de4beb 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -77,6 +77,7 @@ protocols = { 'wlr-output-power-management-unstable-v1': 'wlr-output-power-management-unstable-v1.xml', 'wlr-screencopy-unstable-v1': 'wlr-screencopy-unstable-v1.xml', 'wlr-virtual-pointer-unstable-v1': 'wlr-virtual-pointer-unstable-v1.xml', + 'ext-virtual-keyboard-v1': 'ext-virtual-keyboard-v1.xml', } protocols_code = {} diff --git a/types/meson.build b/types/meson.build index 26958f048..e72346c0c 100644 --- a/types/meson.build +++ b/types/meson.build @@ -54,6 +54,7 @@ wlr_files += files( 'wlr_ext_workspace_v1.c', 'wlr_fixes.c', 'wlr_foreign_toplevel_management_v1.c', + 'wlr_ext_virtual_keyboard_v1.c', 'wlr_fractional_scale_v1.c', 'wlr_gamma_control_v1.c', 'wlr_idle_inhibit_v1.c', diff --git a/types/seat/wlr_seat.c b/types/seat/wlr_seat.c index 015a57b52..951c12b35 100644 --- a/types/seat/wlr_seat.c +++ b/types/seat/wlr_seat.c @@ -11,7 +11,7 @@ #include "types/wlr_seat.h" #include "util/global.h" -#define SEAT_VERSION 9 +#define SEAT_VERSION 10 static void seat_handle_get_pointer(struct wl_client *client, struct wl_resource *seat_resource, uint32_t id) { diff --git a/types/seat/wlr_seat_keyboard.c b/types/seat/wlr_seat_keyboard.c index 3227035e2..b8cee942d 100644 --- a/types/seat/wlr_seat_keyboard.c +++ b/types/seat/wlr_seat_keyboard.c @@ -78,6 +78,12 @@ void wlr_seat_keyboard_send_key(struct wlr_seat *wlr_seat, uint32_t time, continue; } + int version = wl_resource_get_version(resource); + if (state == WL_KEYBOARD_KEY_STATE_REPEATED && + version < WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION) { + continue; + } + wl_keyboard_send_key(resource, serial, time, key, state); } } diff --git a/types/wlr_ext_virtual_keyboard_v1.c b/types/wlr_ext_virtual_keyboard_v1.c new file mode 100644 index 000000000..091e5b1a3 --- /dev/null +++ b/types/wlr_ext_virtual_keyboard_v1.c @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext-virtual-keyboard-v1-protocol.h" + +static const struct wlr_keyboard_impl keyboard_impl = { + .name = "ext-virtual-keyboard-v1", +}; + +static const struct ext_virtual_keyboard_v1_interface ext_virtual_keyboard_impl; + +static struct wlr_ext_virtual_keyboard_v1 *ext_virtual_keyboard_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_virtual_keyboard_v1_interface, &ext_virtual_keyboard_impl)); + return wl_resource_get_user_data(resource); +} + +struct wlr_ext_virtual_keyboard_v1 *wlr_ext_virtual_keyboard_v1_try_from_wlr_input_device( + struct wlr_input_device *wlr_dev) { + if (wlr_dev->type != WLR_INPUT_DEVICE_KEYBOARD) { + return NULL; + } + struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(wlr_dev); + if (wlr_keyboard->impl != &keyboard_impl) { + return NULL; + } + return wl_container_of(wlr_keyboard, + (struct wlr_ext_virtual_keyboard_v1 *)NULL, keyboard); +} + +static void ext_virtual_keyboard_keymap(struct wl_client *client, + struct wl_resource *resource, uint32_t format, int32_t fd, + uint32_t size) { + struct wlr_ext_virtual_keyboard_v1 *keyboard = + ext_virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + wl_resource_post_error(resource, + EXT_VIRTUAL_KEYBOARD_V1_ERROR_INVALID_KEYMAP, + "Unsupported keymap format"); + return; + } + + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wl_client_post_no_memory(client); + goto context_fail; + } + void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + wl_resource_post_error(resource, + EXT_VIRTUAL_KEYBOARD_V1_ERROR_INVALID_KEYMAP, + "Failed to map file descriptor to memory"); + goto fd_fail; + } + struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, data, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(data, size); + if (!keymap) { + wl_resource_post_error(resource, + EXT_VIRTUAL_KEYBOARD_V1_ERROR_INVALID_KEYMAP, + "Failed to parse keymap"); + goto keymap_fail; + } + wlr_keyboard_set_keymap(&keyboard->keyboard, keymap); + + bool had_keymap = keyboard->has_keymap; + keyboard->has_keymap = true; + + if (!had_keymap) { + wl_signal_emit_mutable(&keyboard->manager->events.new_keyboard, + keyboard); + } + + xkb_keymap_unref(keymap); +keymap_fail: +fd_fail: + xkb_context_unref(context); +context_fail: + close(fd); +} + +static bool is_key_state_valid(enum wl_keyboard_key_state state) { + switch (state) { + case WL_KEYBOARD_KEY_STATE_PRESSED: + case WL_KEYBOARD_KEY_STATE_RELEASED: + case WL_KEYBOARD_KEY_STATE_REPEATED: + return true; + } + return false; +} + +static void ext_virtual_keyboard_key(struct wl_client *client, + struct wl_resource *resource, uint32_t time, uint32_t key, + uint32_t state) { + struct wlr_ext_virtual_keyboard_v1 *keyboard = + ext_virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + if (!is_key_state_valid(state)) { + wl_resource_post_error(resource, + EXT_VIRTUAL_KEYBOARD_V1_ERROR_INVALID_KEY_STATE, + "Invalid key state"); + return; + } + if (!keyboard->has_keymap) { + wl_resource_post_error(resource, + EXT_VIRTUAL_KEYBOARD_V1_ERROR_MISSING_KEYMAP, + "Cannot send a keypress before defining a keymap"); + return; + } + struct wlr_keyboard_key_event event = { + .time_msec = time, + .keycode = key, + .update_state = false, + .state = state, + }; + wlr_keyboard_notify_key(&keyboard->keyboard, &event); +} + +static void ext_virtual_keyboard_modifiers(struct wl_client *client, + struct wl_resource *resource, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + struct wlr_ext_virtual_keyboard_v1 *keyboard = + ext_virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + if (!keyboard->has_keymap) { + wl_resource_post_error(resource, + EXT_VIRTUAL_KEYBOARD_V1_ERROR_MISSING_KEYMAP, + "Cannot send a modifier state before defining a keymap"); + return; + } + wlr_keyboard_notify_modifiers(&keyboard->keyboard, + mods_depressed, mods_latched, mods_locked, group); +} + +static void ext_virtual_keyboard_repeat_info(struct wl_client *client, + struct wl_resource *resource, int32_t rate, int32_t delay) { + struct wlr_ext_virtual_keyboard_v1 *keyboard = + ext_virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + + wlr_keyboard_set_repeat_info(&keyboard->keyboard, rate, delay); +} + +static void ext_virtual_keyboard_destroy_resource(struct wl_resource *resource) { + struct wlr_ext_virtual_keyboard_v1 *keyboard = + ext_virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + + wlr_keyboard_finish(&keyboard->keyboard); + wl_list_remove(&keyboard->link); + free(keyboard); +} + +static void ext_virtual_keyboard_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_virtual_keyboard_v1_interface ext_virtual_keyboard_impl = { + .keymap = ext_virtual_keyboard_keymap, + .key = ext_virtual_keyboard_key, + .modifiers = ext_virtual_keyboard_modifiers, + .repeat_info = ext_virtual_keyboard_repeat_info, + .destroy = ext_virtual_keyboard_destroy, +}; + +static const struct ext_virtual_keyboard_manager_v1_interface manager_impl; + +static struct wlr_ext_virtual_keyboard_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_virtual_keyboard_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void ext_virtual_keyboard_manager_create_ext_virtual_keyboard( + struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat, uint32_t id) { + struct wlr_ext_virtual_keyboard_manager_v1 *manager = + manager_from_resource(resource); + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + + struct wl_resource *keyboard_resource = wl_resource_create(client, + &ext_virtual_keyboard_v1_interface, wl_resource_get_version(resource), + id); + if (!keyboard_resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(keyboard_resource, &ext_virtual_keyboard_impl, + NULL, ext_virtual_keyboard_destroy_resource); + if (seat_client == NULL) { + return; + } + + struct wlr_ext_virtual_keyboard_v1 *ext_virtual_keyboard = calloc(1, sizeof(*ext_virtual_keyboard)); + if (!ext_virtual_keyboard) { + wl_client_post_no_memory(client); + return; + } + + wlr_keyboard_init(&ext_virtual_keyboard->keyboard, &keyboard_impl, + "wlr_ext_virtual_keyboard_v1"); + + ext_virtual_keyboard->resource = keyboard_resource; + ext_virtual_keyboard->manager = manager; + ext_virtual_keyboard->seat = seat_client->seat; + wl_resource_set_user_data(keyboard_resource, ext_virtual_keyboard); + + wl_list_insert(&manager->keyboards, &ext_virtual_keyboard->link); +} + +static const struct ext_virtual_keyboard_manager_v1_interface manager_impl = { + .create_virtual_keyboard = ext_virtual_keyboard_manager_create_ext_virtual_keyboard, +}; + +static void ext_virtual_keyboard_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_virtual_keyboard_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_virtual_keyboard_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_virtual_keyboard_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_virtual_keyboard_manager_v1 *wlr_ext_virtual_keyboard_manager_v1_create( + struct wl_display *display, uint32_t version) { + struct wlr_ext_virtual_keyboard_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_virtual_keyboard_manager_v1_interface, version, manager, + ext_virtual_keyboard_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + wl_list_init(&manager->keyboards); + + wl_signal_init(&manager->events.new_keyboard); + wl_signal_init(&manager->events.destroy); + + return manager; +}