diff --git a/examples/input-method-keyboard-grab.c b/examples/input-method-keyboard-grab.c new file mode 100644 index 0000000000..881f91a92d --- /dev/null +++ b/examples/input-method-keyboard-grab.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "input-method-unstable-v2-client-protocol.h" + +static struct wl_display *display = NULL; +static struct wl_seat *seat = NULL; +static struct zwp_input_method_manager_v2 *input_method_manager = NULL; +static struct zwp_input_method_v2 *input_method = NULL; +static struct zwp_input_method_keyboard_grab_v2 *kb_grab = NULL; + +static bool active = false; +static bool pending_active = false; + +static struct xkb_context *xkb_context = NULL; +static struct xkb_keymap *keymap = NULL; +static struct xkb_state *xkb_state = NULL; + +static void handle_key(void *data, + struct zwp_input_method_keyboard_grab_v2 *im_keyboard_grab, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + printf("handle_key %u %u %u %u\n", serial, time, key, state); + xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, key + 8); + char keysym_name[64]; + xkb_keysym_get_name(keysym, keysym_name, sizeof(keysym_name)); + printf("xkb translated to %s\n", keysym_name); + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + if (keysym == XKB_KEY_KP_Enter || keysym == XKB_KEY_Return) { + printf("Stopping grab\n"); + zwp_input_method_keyboard_grab_v2_release(kb_grab); + kb_grab = NULL; + } + } + +} + +static void handle_modifiers(void *data, + struct zwp_input_method_keyboard_grab_v2 *im_keyboard_grab, + uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) { + printf("handle_modifiers %u %u %u %u %u\n", serial, mods_depressed, + mods_latched, mods_locked, group); + xkb_state_update_mask(xkb_state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); +} + +static void handle_keymap(void *data, + struct zwp_input_method_keyboard_grab_v2 *im_keyboard_grab, + uint32_t format, int32_t fd, uint32_t size) { + printf("handle_keymap\n"); + char *keymap_string = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + xkb_keymap_unref(keymap); + keymap = xkb_keymap_new_from_string(xkb_context, keymap_string, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(keymap_string, size); + close(fd); + xkb_state_unref(xkb_state); + xkb_state = xkb_state_new(keymap); +} + +static void handle_repeat_info(void *data, + struct zwp_input_method_keyboard_grab_v2 *im_keyboard_grab, + int32_t rate, int32_t delay) { + printf("handle_repeat_info %d %d", rate, delay); +} + + +static const struct zwp_input_method_keyboard_grab_v2_listener grab_listener = { + .key = handle_key, + .modifiers = handle_modifiers, + .keymap = handle_keymap, + .repeat_info = handle_repeat_info, +}; + +static void handle_activate(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + pending_active = true; +} + +static void handle_deactivate(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + pending_active = false; +} + +static void handle_unavailable(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + printf("IM unavailable\n"); + zwp_input_method_v2_destroy(zwp_input_method_v2); + input_method = NULL; +} + +static void im_activate(void *data, + struct zwp_input_method_v2 *id) { + kb_grab = zwp_input_method_v2_grab_keyboard(input_method); + if (kb_grab == NULL) { + fprintf(stderr, "Failed to grab\n"); + exit(EXIT_FAILURE); + } + zwp_input_method_keyboard_grab_v2_add_listener(kb_grab, &grab_listener, + NULL); + printf("Started grab, press enter to stop grab\n"); +} + +static void im_deactivate(void *data, + struct zwp_input_method_v2 *context) { + if (kb_grab != NULL) { + zwp_input_method_keyboard_grab_v2_release(kb_grab); + kb_grab = NULL; + } +} + +static void handle_done(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + bool prev_active = active; + if (active != pending_active) { + printf("Now %s\n", pending_active ? "active" : "inactive"); + } + active = pending_active; + if (active && !prev_active) { + im_activate(data, zwp_input_method_v2); + } else if (!active && prev_active) { + im_deactivate(data, zwp_input_method_v2); + } +} + +static void handle_surrounding_text(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + const char *text, uint32_t cursor, uint32_t anchor) { + // not for this test +} + +static void handle_text_change_cause(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + uint32_t cause) { + // not for this test +} + +static void handle_content_type(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + uint32_t hint, uint32_t purpose) { + // not for this test +} + +static const struct zwp_input_method_v2_listener im_listener = { + .activate = handle_activate, + .deactivate = handle_deactivate, + .surrounding_text = handle_surrounding_text, + .text_change_cause = handle_text_change_cause, + .content_type = handle_content_type, + .done = handle_done, + .unavailable = handle_unavailable, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, zwp_input_method_manager_v2_interface.name) == 0) { + input_method_manager = wl_registry_bind(registry, name, + &zwp_input_method_manager_v2_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + // who cares +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +int main(int argc, char **argv) { + xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (xkb_context == NULL) { + fprintf(stderr, "Failed to create xkb context\n"); + return EXIT_FAILURE; + } + + display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "Failed to create display\n"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (input_method_manager == NULL) { + fprintf(stderr, "input-method not available\n"); + return EXIT_FAILURE; + } + if (seat == NULL) { + fprintf(stderr, "seat not available\n"); + return EXIT_FAILURE; + } + + input_method = zwp_input_method_manager_v2_get_input_method( + input_method_manager, seat); + zwp_input_method_v2_add_listener(input_method, &im_listener, NULL); + + wl_display_roundtrip(display); + + while (wl_display_dispatch(display) != -1) { + // This space is intentionally left blank + }; + + return EXIT_SUCCESS; +} diff --git a/examples/meson.build b/examples/meson.build index 5668b7ecb3..c8016d3a05 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -170,6 +170,13 @@ clients = { 'dep': wlroots, 'proto': ['wlr-virtual-pointer-unstable-v1'], }, + 'input-method-keyboard-grab': { + 'src': 'input-method-keyboard-grab.c', + 'dep': xkbcommon, + 'proto': [ + 'input-method-unstable-v2', + ], + }, } foreach name, info : compositors diff --git a/include/wlr/types/wlr_input_method_v2.h b/include/wlr/types/wlr_input_method_v2.h index 2ea4a6e549..99361d0cd0 100644 --- a/include/wlr/types/wlr_input_method_v2.h +++ b/include/wlr/types/wlr_input_method_v2.h @@ -34,6 +34,7 @@ struct wlr_input_method_v2 { struct wl_resource *resource; struct wlr_seat *seat; + struct wlr_seat_client *seat_client; struct wlr_input_method_v2_state pending; struct wlr_input_method_v2_state current; @@ -41,16 +42,33 @@ struct wlr_input_method_v2 { bool client_active; // state known to the client uint32_t current_serial; // received in last commit call + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab; + struct wl_list link; - struct wl_listener seat_destroy; + struct wl_listener seat_client_destroy; struct { struct wl_signal commit; // (struct wlr_input_method_v2*) + struct wl_signal grab_keyboard; // (struct wlr_input_method_keyboard_grab_v2*) struct wl_signal destroy; // (struct wlr_input_method_v2*) } events; }; +struct wlr_input_method_keyboard_grab_v2 { + struct wl_resource *resource; + struct wlr_input_method_v2 *input_method; + struct wlr_keyboard *keyboard; + + struct wl_listener keyboard_keymap; + struct wl_listener keyboard_repeat_info; + struct wl_listener keyboard_destroy; + + struct { + struct wl_signal destroy; // (struct wlr_input_method_keyboard_grab_v2*) + } events; +}; + struct wlr_input_method_manager_v2 { struct wl_global *global; struct wl_list input_methods; // struct wlr_input_method_v2*::link @@ -82,4 +100,16 @@ void wlr_input_method_v2_send_done(struct wlr_input_method_v2 *input_method); void wlr_input_method_v2_send_unavailable( struct wlr_input_method_v2 *input_method); +void wlr_input_method_keyboard_grab_v2_send_key( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + uint32_t time, uint32_t key, uint32_t state); +void wlr_input_method_keyboard_grab_v2_send_modifiers( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard_modifiers *modifiers); +void wlr_input_method_keyboard_grab_v2_set_keyboard( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard *keyboard); +void wlr_input_method_keyboard_grab_v2_destroy( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab); + #endif diff --git a/include/wlr/types/wlr_virtual_keyboard_v1.h b/include/wlr/types/wlr_virtual_keyboard_v1.h index 78f80fbb7c..a3f4e4521e 100644 --- a/include/wlr/types/wlr_virtual_keyboard_v1.h +++ b/include/wlr/types/wlr_virtual_keyboard_v1.h @@ -26,8 +26,8 @@ struct wlr_virtual_keyboard_manager_v1 { }; struct wlr_virtual_keyboard_v1 { - struct wl_resource *resource; struct wlr_input_device input_device; + struct wl_resource *resource; struct wlr_seat *seat; bool has_keymap; @@ -41,4 +41,7 @@ struct wlr_virtual_keyboard_v1 { struct wlr_virtual_keyboard_manager_v1* wlr_virtual_keyboard_manager_v1_create( struct wl_display *display); +struct wlr_virtual_keyboard_v1 *wlr_input_device_get_virtual_keyboard( + struct wlr_input_device *wlr_dev); + #endif diff --git a/types/wlr_input_method_v2.c b/types/wlr_input_method_v2.c index 525b3616c8..cb2482888b 100644 --- a/types/wlr_input_method_v2.c +++ b/types/wlr_input_method_v2.c @@ -3,15 +3,19 @@ #endif #include #include +#include +#include #include #include #include #include #include #include "input-method-unstable-v2-protocol.h" +#include "util/shm.h" #include "util/signal.h" static const struct zwp_input_method_v2_interface input_method_impl; +static const struct zwp_input_method_keyboard_grab_v2_interface keyboard_grab_impl; static struct wlr_input_method_v2 *input_method_from_resource( struct wl_resource *resource) { @@ -20,10 +24,19 @@ static struct wlr_input_method_v2 *input_method_from_resource( return wl_resource_get_user_data(resource); } +static struct wlr_input_method_keyboard_grab_v2 *keyboard_grab_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_input_method_keyboard_grab_v2_interface, + &keyboard_grab_impl)); + return wl_resource_get_user_data(resource); +} + static void input_method_destroy(struct wlr_input_method_v2 *input_method) { wlr_signal_emit_safe(&input_method->events.destroy, input_method); wl_list_remove(wl_resource_get_link(input_method->resource)); - wl_list_remove(&input_method->seat_destroy.link); + wl_list_remove(&input_method->seat_client_destroy.link); + wlr_input_method_keyboard_grab_v2_destroy(input_method->keyboard_grab); free(input_method->pending.commit_text); free(input_method->pending.preedit.text); free(input_method->current.commit_text); @@ -101,10 +114,189 @@ static void im_get_input_popup_surface(struct wl_client *client, wlr_log(WLR_INFO, "Stub: zwp_input_method_v2::get_input_popup_surface"); } +void wlr_input_method_keyboard_grab_v2_destroy( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab) { + if (!keyboard_grab) { + return; + } + wlr_signal_emit_safe(&keyboard_grab->events.destroy, keyboard_grab); + keyboard_grab->input_method->keyboard_grab = NULL; + if (keyboard_grab->keyboard) { + wl_list_remove(&keyboard_grab->keyboard_keymap.link); + wl_list_remove(&keyboard_grab->keyboard_repeat_info.link); + wl_list_remove(&keyboard_grab->keyboard_destroy.link); + } + wl_resource_set_user_data(keyboard_grab->resource, NULL); + free(keyboard_grab); +} + +static void keyboard_grab_resource_destroy(struct wl_resource *resource) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + keyboard_grab_from_resource(resource); + wlr_input_method_keyboard_grab_v2_destroy(keyboard_grab); +} + +static void keyboard_grab_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_input_method_keyboard_grab_v2_interface keyboard_grab_impl = { + .release = keyboard_grab_release, +}; + +void wlr_input_method_keyboard_grab_v2_send_key( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + uint32_t time, uint32_t key, uint32_t state) { + zwp_input_method_keyboard_grab_v2_send_key( + keyboard_grab->resource, + wlr_seat_client_next_serial(keyboard_grab->input_method->seat_client), + time, key, state); +} + +void wlr_input_method_keyboard_grab_v2_send_modifiers( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard_modifiers *modifiers) { + zwp_input_method_keyboard_grab_v2_send_modifiers( + keyboard_grab->resource, + wlr_seat_client_next_serial(keyboard_grab->input_method->seat_client), + modifiers->depressed, modifiers->latched, + modifiers->locked, modifiers->group); +} + +static bool keyboard_grab_send_keymap( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard *keyboard) { + int keymap_fd = allocate_shm_file(keyboard->keymap_size); + if (keymap_fd < 0) { + wlr_log(WLR_ERROR, "creating a keymap file for %zu bytes failed", + keyboard->keymap_size); + return false; + } + + void *ptr = mmap(NULL, keyboard->keymap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, keymap_fd, 0); + if (ptr == MAP_FAILED) { + wlr_log(WLR_ERROR, "failed to mmap() %zu bytes", + keyboard->keymap_size); + close(keymap_fd); + return false; + } + + memcpy(ptr, keyboard->keymap_string, keyboard->keymap_size); + munmap(ptr, keyboard->keymap_size); + + zwp_input_method_keyboard_grab_v2_send_keymap(keyboard_grab->resource, + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd, + keyboard->keymap_size); + + close(keymap_fd); + return true; +} + +static void keyboard_grab_send_repeat_info( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard *keyboard) { + zwp_input_method_keyboard_grab_v2_send_repeat_info( + keyboard_grab->resource, keyboard->repeat_info.rate, + keyboard->repeat_info.delay); +} + +static void handle_keyboard_keymap(struct wl_listener *listener, void *data) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + wl_container_of(listener, keyboard_grab, keyboard_keymap); + keyboard_grab_send_keymap(keyboard_grab, data); +} + +static void handle_keyboard_repeat_info(struct wl_listener *listener, + void *data) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + wl_container_of(listener, keyboard_grab, keyboard_repeat_info); + keyboard_grab_send_repeat_info(keyboard_grab, data); +} + +static void handle_keyboard_destroy(struct wl_listener *listener, + void *data) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + wl_container_of(listener, keyboard_grab, keyboard_destroy); + wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, NULL); +} + +void wlr_input_method_keyboard_grab_v2_set_keyboard( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard *keyboard) { + if (keyboard == keyboard_grab->keyboard) { + return; + } + + if (keyboard_grab->keyboard) { + wl_list_remove(&keyboard_grab->keyboard_keymap.link); + wl_list_remove(&keyboard_grab->keyboard_repeat_info.link); + wl_list_remove(&keyboard_grab->keyboard_destroy.link); + } + + if (keyboard) { + if (keyboard_grab->keyboard == NULL || + strcmp(keyboard_grab->keyboard->keymap_string, + keyboard->keymap_string) != 0) { + // send keymap only if it is changed, or if input method is not + // aware that it did not change and blindly send it back with + // virtual keyboard, it may cause an infinite recursion. + if (!keyboard_grab_send_keymap(keyboard_grab, keyboard)) { + wlr_log(WLR_ERROR, "Failed to send keymap for input-method keyboard grab"); + return; + } + } + keyboard_grab_send_repeat_info(keyboard_grab, keyboard); + keyboard_grab->keyboard_keymap.notify = handle_keyboard_keymap; + wl_signal_add(&keyboard->events.keymap, + &keyboard_grab->keyboard_keymap); + keyboard_grab->keyboard_repeat_info.notify = + handle_keyboard_repeat_info; + wl_signal_add(&keyboard->events.repeat_info, + &keyboard_grab->keyboard_repeat_info); + keyboard_grab->keyboard_destroy.notify = + handle_keyboard_destroy; + wl_signal_add(&keyboard->events.destroy, + &keyboard_grab->keyboard_destroy); + } + + keyboard_grab->keyboard = keyboard; +}; static void im_grab_keyboard(struct wl_client *client, struct wl_resource *resource, uint32_t keyboard) { - wlr_log(WLR_INFO, "Stub: zwp_input_method_v2::grab_keyboard"); + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + if (input_method->keyboard_grab) { + // Already grabbed + return; + } + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + calloc(1, sizeof(struct wlr_input_method_keyboard_grab_v2)); + if (!keyboard_grab) { + wl_client_post_no_memory(client); + return; + } + struct wl_resource *keyboard_grab_resource = wl_resource_create( + client, &zwp_input_method_keyboard_grab_v2_interface, + wl_resource_get_version(resource), keyboard); + if (keyboard_grab_resource == NULL) { + free(keyboard_grab); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(keyboard_grab_resource, + &keyboard_grab_impl, keyboard_grab, + keyboard_grab_resource_destroy); + keyboard_grab->resource = keyboard_grab_resource; + keyboard_grab->input_method = input_method; + input_method->keyboard_grab = keyboard_grab; + wl_signal_init(&keyboard_grab->events.destroy); + wlr_signal_emit_safe(&input_method->events.grab_keyboard, keyboard_grab); } static const struct zwp_input_method_v2_interface input_method_impl = { @@ -176,10 +368,10 @@ static struct wlr_input_method_manager_v2 *input_method_manager_from_resource( return wl_resource_get_user_data(resource); } -static void input_method_handle_seat_destroy(struct wl_listener *listener, +static void input_method_handle_seat_client_destroy(struct wl_listener *listener, void *data) { struct wlr_input_method_v2 *input_method = wl_container_of(listener, - input_method, seat_destroy); + input_method, seat_client_destroy); wlr_input_method_v2_send_unavailable(input_method); } @@ -196,6 +388,7 @@ static void manager_get_input_method(struct wl_client *client, return; } wl_signal_init(&input_method->events.commit); + wl_signal_init(&input_method->events.grab_keyboard); wl_signal_init(&input_method->events.destroy); int version = wl_resource_get_version(resource); struct wl_resource *im_resource = wl_resource_create(client, @@ -208,14 +401,14 @@ static void manager_get_input_method(struct wl_client *client, wl_resource_set_implementation(im_resource, &input_method_impl, input_method, input_method_resource_destroy); - struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); - wl_signal_add(&seat_client->events.destroy, - &input_method->seat_destroy); - input_method->seat_destroy.notify = - input_method_handle_seat_destroy; + input_method->seat_client = wlr_seat_client_from_resource(seat); + input_method->seat = input_method->seat_client->seat; + wl_signal_add(&input_method->seat_client->events.destroy, + &input_method->seat_client_destroy); + input_method->seat_client_destroy.notify = + input_method_handle_seat_client_destroy; input_method->resource = im_resource; - input_method->seat = seat_client->seat; wl_list_insert(&im_manager->input_methods, wl_resource_get_link(input_method->resource)); wlr_signal_emit_safe(&im_manager->events.input_method, input_method); diff --git a/types/wlr_virtual_keyboard_v1.c b/types/wlr_virtual_keyboard_v1.c index 258a08cb88..7ae030eec2 100644 --- a/types/wlr_virtual_keyboard_v1.c +++ b/types/wlr_virtual_keyboard_v1.c @@ -40,6 +40,14 @@ static struct wlr_virtual_keyboard_v1 *virtual_keyboard_from_resource( return wl_resource_get_user_data(resource); } +struct wlr_virtual_keyboard_v1 *wlr_input_device_get_virtual_keyboard( + struct wlr_input_device *wlr_dev) { + if (wlr_dev->impl != &input_device_impl) { + return NULL; + } + return (struct wlr_virtual_keyboard_v1 *)wlr_dev; +} + static void virtual_keyboard_keymap(struct wl_client *client, struct wl_resource *resource, uint32_t format, int32_t fd, uint32_t size) {