Route keyboard input through input-method grabs
20eaf8cfbf494ea4c7741ea80fdcb98d815ec48f
1 parent
8f3a9fa3
swm.c
+46 -20
| 253 | 253 | const arg_t arg; |
|
| 254 | 254 | } key_t; |
|
| 255 | 255 | ||
| 256 | 256 | /* Physical or virtual keyboard group. */ |
|
| 257 | 257 | typedef struct { |
|
| 258 | - | struct wlr_keyboard_group *wlr_group; |
|
| 259 | - | bool is_virtual; |
|
| 260 | - | int nsyms; |
|
| 261 | - | uint32_t repeat_keycode; /* Valid only when nsyms is nonzero. */ |
|
| 262 | - | const xkb_keysym_t *keysyms; /* Valid only when nsyms is nonzero. */ |
|
| 263 | - | uint32_t mods; /* Valid only when nsyms is nonzero. */ |
|
| 264 | - | struct wl_event_source *key_repeat_source; |
|
| 265 | - | uint32_t handled_keys[(KEY_CNT + 31) / 32]; |
|
| 266 | - | struct wl_listener modifiers; |
|
| 267 | - | struct wl_listener key; |
|
| 268 | - | struct wl_listener destroy; |
|
| 258 | + | struct wlr_keyboard_group *wlr_group; |
|
| 259 | + | bool is_virtual; |
|
| 260 | + | struct wlr_virtual_keyboard_v1 *virtual_keyboard; /* Source of a virtual group. */ |
|
| 261 | + | int nsyms; |
|
| 262 | + | uint32_t repeat_keycode; /* Valid only when nsyms is nonzero. */ |
|
| 263 | + | const xkb_keysym_t *keysyms; /* Valid only when nsyms is nonzero. */ |
|
| 264 | + | uint32_t mods; /* Valid only when nsyms is nonzero. */ |
|
| 265 | + | struct wl_event_source *key_repeat_source; |
|
| 266 | + | uint32_t handled_keys[(KEY_CNT + 31) / 32]; |
|
| 267 | + | struct wl_listener modifiers; |
|
| 268 | + | struct wl_listener key; |
|
| 269 | + | struct wl_listener destroy; |
|
| 269 | 270 | } keyboard_group_t; |
|
| 270 | 271 | ||
| 271 | 272 | /* Layer-shell surface. */ |
|
| 272 | 273 | typedef struct { |
|
| 273 | 274 | /* This must remain first so generic surface code can read it. */ |
| 3911 | 3912 | if (!group->nsyms) |
|
| 3912 | 3913 | return false; |
|
| 3913 | 3914 | return event->state == WL_KEYBOARD_KEY_STATE_PRESSED || event->keycode == group->repeat_keycode; |
|
| 3914 | 3915 | } |
|
| 3915 | 3916 | ||
| 3916 | - | /* Handle shortcuts and forward unhandled key events to the focused application. */ |
|
| 3917 | + | /* Select the input-method grab for a keyboard's events. */ |
|
| 3918 | + | static struct wlr_input_method_keyboard_grab_v2 *input_method_grab(keyboard_group_t *group) { |
|
| 3919 | + | if (locked || !input_method || !input_method->keyboard_grab) |
|
| 3920 | + | return nullptr; |
|
| 3921 | + | if (group->virtual_keyboard && wl_resource_get_client(group->virtual_keyboard->resource) == |
|
| 3922 | + | wl_resource_get_client(input_method->resource)) |
|
| 3923 | + | return nullptr; |
|
| 3924 | + | return input_method->keyboard_grab; |
|
| 3925 | + | } |
|
| 3926 | + | ||
| 3927 | + | /* Handle shortcuts and send unhandled keys to the input recipient. */ |
|
| 3917 | 3928 | void key_press(struct wl_listener *listener, void *data) { |
|
| 3918 | 3929 | int i; |
|
| 3919 | 3930 | ||
| 3920 | 3931 | /* This event is raised when a key is pressed or released. */ |
|
| 3921 | - | keyboard_group_t *group = wl_container_of(listener, group, key); |
|
| 3922 | - | struct wlr_keyboard_key_event *event = data; |
|
| 3932 | + | keyboard_group_t *group = wl_container_of(listener, group, key); |
|
| 3933 | + | struct wlr_keyboard_key_event *event = data; |
|
| 3934 | + | struct wlr_input_method_keyboard_grab_v2 *grab; |
|
| 3923 | 3935 | ||
| 3924 | 3936 | /* Convert the input key code to the numbering used by XKB. */ |
|
| 3925 | 3937 | uint32_t keycode = event->keycode + 8; |
|
| 3926 | 3938 | ||
| 3927 | 3939 | /* Resolve the key to one or more symbols using the active key map. */ |
| 3946 | 3958 | } |
|
| 3947 | 3959 | if (!key_event_should_forward(group, event->keycode, event->state, handled)) |
|
| 3948 | 3960 | return; |
|
| 3949 | 3961 | ||
| 3950 | 3962 | wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); |
|
| 3951 | - | /* Forward unhandled keys to the focused application. */ |
|
| 3952 | - | wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, event->state); |
|
| 3963 | + | /* Send unhandled keys to the input method or focused application. */ |
|
| 3964 | + | if ((grab = input_method_grab(group))) { |
|
| 3965 | + | wlr_input_method_keyboard_grab_v2_set_keyboard(grab, &group->wlr_group->keyboard); |
|
| 3966 | + | wlr_input_method_keyboard_grab_v2_send_key( |
|
| 3967 | + | grab, event->time_msec, event->keycode, event->state |
|
| 3968 | + | ); |
|
| 3969 | + | } else |
|
| 3970 | + | wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, event->state); |
|
| 3953 | 3971 | } |
|
| 3954 | 3972 | ||
| 3955 | - | /* Forward a keyboard's updated modifier state to the focused application. */ |
|
| 3973 | + | /* Send a keyboard's modifier state to the input recipient. */ |
|
| 3956 | 3974 | void key_press_modifiers(struct wl_listener *listener, void *data) { |
|
| 3957 | 3975 | /* A modifier such as Shift or Alt changed. */ |
|
| 3958 | - | keyboard_group_t *group = wl_container_of(listener, group, modifiers); |
|
| 3976 | + | keyboard_group_t *group = wl_container_of(listener, group, modifiers); |
|
| 3977 | + | struct wlr_input_method_keyboard_grab_v2 *grab; |
|
| 3959 | 3978 | ||
| 3960 | 3979 | wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); |
|
| 3961 | - | /* Forward the new modifier state to the focused application. */ |
|
| 3962 | - | wlr_seat_keyboard_notify_modifiers(seat, &group->wlr_group->keyboard.modifiers); |
|
| 3980 | + | /* Send modifiers to the input method or focused application. */ |
|
| 3981 | + | if ((grab = input_method_grab(group))) { |
|
| 3982 | + | wlr_input_method_keyboard_grab_v2_set_keyboard(grab, &group->wlr_group->keyboard); |
|
| 3983 | + | wlr_input_method_keyboard_grab_v2_send_modifiers( |
|
| 3984 | + | grab, &group->wlr_group->keyboard.modifiers |
|
| 3985 | + | ); |
|
| 3986 | + | } else |
|
| 3987 | + | wlr_seat_keyboard_notify_modifiers(seat, &group->wlr_group->keyboard.modifiers); |
|
| 3963 | 3988 | tiled_resize_update(); |
|
| 3964 | 3989 | } |
|
| 3965 | 3990 | ||
| 3966 | 3991 | /* Repeat the active shortcut at the keyboard's configured rate. */ |
|
| 3967 | 3992 | int key_repeat(void *data) { |
| 6863 | 6888 | if (!group) { |
|
| 6864 | 6889 | wl_resource_post_no_memory(kb->resource); |
|
| 6865 | 6890 | return; |
|
| 6866 | 6891 | } |
|
| 6867 | 6892 | /* Use the virtual keyboard's key mapping. */ |
|
| 6893 | + | group->virtual_keyboard = kb; |
|
| 6868 | 6894 | wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); |
|
| 6869 | 6895 | LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroy_keyboard_group); |
|
| 6870 | 6896 | ||
| 6871 | 6897 | /* Add the virtual keyboard to its group. */ |
|
| 6872 | 6898 | wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); |
test/clients.py
+61 -0
| 852 | 852 | lock.destroy() |
|
| 853 | 853 | connection.roundtrips() |
|
| 854 | 854 | connection.close() |
|
| 855 | 855 | ||
| 856 | 856 | ||
| 857 | + | def input_method_keys() -> None: |
|
| 858 | + | """Check keyboard grabs and input-method key reinjection.""" |
|
| 859 | + | ||
| 860 | + | connection = Connection( |
|
| 861 | + | WlCompositor, WlShm, WlSeat, XdgWmBase, |
|
| 862 | + | ZwpInputMethodManagerV2, ZwpVirtualKeyboardManagerV1, |
|
| 863 | + | ) |
|
| 864 | + | seat = connection.get(WlSeat) |
|
| 865 | + | virtual = connection.get(ZwpVirtualKeyboardManagerV1).create_virtual_keyboard(seat) |
|
| 866 | + | keymap, indices = keymap_text() |
|
| 867 | + | with tempfile.TemporaryFile(dir=os.environ.get("SWM_TEST_DIR")) as stream: |
|
| 868 | + | stream.write(keymap + b"\0") |
|
| 869 | + | stream.flush() |
|
| 870 | + | virtual.keymap(WlKeyboard.keymap_format.xkb_v1, stream.fileno(), len(keymap) + 1) |
|
| 871 | + | connection.roundtrips() |
|
| 872 | + | keyboard = seat.get_keyboard() |
|
| 873 | + | app_keys = [] |
|
| 874 | + | keyboard.dispatcher["key"] = lambda proxy, serial, time, key, state: app_keys.append((key, state)) |
|
| 875 | + | window = Window(connection, "input-method-keys", 0xFF556677) |
|
| 876 | + | connection.roundtrips(4) |
|
| 877 | + | method = connection.get(ZwpInputMethodManagerV2).get_input_method(seat) |
|
| 878 | + | grab = method.grab_keyboard() |
|
| 879 | + | grabbed_keys = [] |
|
| 880 | + | grabbed_modifiers = [] |
|
| 881 | + | grab.dispatcher["keymap"] = lambda proxy, format, fd, size: os.close(fd) |
|
| 882 | + | grab.dispatcher["key"] = lambda proxy, serial, time, key, state: grabbed_keys.append((key, state)) |
|
| 883 | + | grab.dispatcher["modifiers"] = lambda proxy, serial, depressed, latched, locked, group: ( |
|
| 884 | + | grabbed_modifiers.append(depressed) |
|
| 885 | + | ) |
|
| 886 | + | connection.roundtrips(2) |
|
| 887 | + | subprocess.run([sys.executable, __file__, "keyboard", "30", "1"], check=True) |
|
| 888 | + | connection.roundtrips(2) |
|
| 889 | + | if (30, 1) not in grabbed_keys or (30, 0) not in grabbed_keys: |
|
| 890 | + | raise Failure("input method did not receive the grabbed key") |
|
| 891 | + | if not any(grabbed_modifiers): |
|
| 892 | + | raise Failure("input method did not receive Shift") |
|
| 893 | + | if app_keys: |
|
| 894 | + | raise Failure("grabbed keys reached the application") |
|
| 895 | + | ||
| 896 | + | count = len(grabbed_keys) |
|
| 897 | + | virtual.modifiers(1 << indices["shift"], 0, 0, 0) |
|
| 898 | + | virtual.key(3, 30, 1) |
|
| 899 | + | virtual.key(4, 30, 0) |
|
| 900 | + | virtual.modifiers(0, 0, 0, 0) |
|
| 901 | + | connection.roundtrips(2) |
|
| 902 | + | if app_keys != [(30, 1), (30, 0)] or len(grabbed_keys) != count: |
|
| 903 | + | raise Failure("input-method keys did not reach the application directly") |
|
| 904 | + | grab.release() |
|
| 905 | + | connection.roundtrips() |
|
| 906 | + | app_keys.clear() |
|
| 907 | + | subprocess.run([sys.executable, __file__, "keyboard", "48", "0"], check=True) |
|
| 908 | + | connection.roundtrips(2) |
|
| 909 | + | if app_keys != [(48, 1), (48, 0)]: |
|
| 910 | + | raise Failure("keyboard input did not resume after the grab ended") |
|
| 911 | + | method.destroy() |
|
| 912 | + | window.destroy() |
|
| 913 | + | virtual.destroy() |
|
| 914 | + | connection.close() |
|
| 915 | + | ||
| 916 | + | ||
| 857 | 917 | def text_input(arguments: list[str]) -> None: |
|
| 858 | 918 | """Exercise text input, input methods, and input inhibitors together.""" |
|
| 859 | 919 | ||
| 860 | 920 | connection = Connection( |
|
| 861 | 921 | WlCompositor, |
| 1006 | 1066 | popup.destroy() |
|
| 1007 | 1067 | popup_surface.destroy() |
|
| 1008 | 1068 | text.destroy() |
|
| 1009 | 1069 | method.destroy() |
|
| 1010 | 1070 | connection.close() |
|
| 1071 | + | input_method_keys() |
|
| 1011 | 1072 | ||
| 1012 | 1073 | ||
| 1013 | 1074 | def x11(arguments: list[str]) -> None: |
|
| 1014 | 1075 | """Exercise managed, configured, fullscreen, and unmanaged X11 windows.""" |
|
| 1015 | 1076 |