diff --git a/DOCS/interface-changes/input.txt b/DOCS/interface-changes/input.txt new file mode 100644 index 0000000000000..e00e796af058c --- /dev/null +++ b/DOCS/interface-changes/input.txt @@ -0,0 +1 @@ +add `--input-ime` to enable or disable the IME on supported VOs (Wayland) diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 4e932f1eff69c..79576c736c3fd 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -4460,6 +4460,17 @@ Input ``N`` pixels while the mouse button is being held down (default: 3). This only affects VOs which support the ``begin-vo-dragging`` command. +``--input-ime=`` + Enable keyboard input via an active input method (IME) connected to the VO. + (default: yes). The input popup window, if there is any, is always + positioned at the top left of the window. Whether pre-edit text is drawn + depends on the platform. You may need to configure your IME to display the + pre-edit inside of the input popup window if you cannot read the pre-edit + text in the mpv window. + + Wayland only. On Windows, the IME is currently always enabled. This option + is not applicable to terminal input. + OSD --- diff --git a/options/options.c b/options/options.c index 3bb958c49a042..ffbd5bbf20038 100644 --- a/options/options.c +++ b/options/options.c @@ -150,6 +150,7 @@ static const m_option_t mp_vo_opt_list[] = { {"fs", OPT_ALIAS("fullscreen")}, {"input-cursor-passthrough", OPT_BOOL(cursor_passthrough)}, {"native-keyrepeat", OPT_BOOL(native_keyrepeat)}, + {"input-ime", OPT_BOOL(input_ime)}, {"panscan", OPT_FLOAT(panscan), M_RANGE(0.0, 1.0)}, {"video-zoom", OPT_FLOAT(zoom), M_RANGE(-20.0, 20.0)}, {"video-pan-x", OPT_FLOAT(pan_x)}, @@ -259,6 +260,7 @@ const struct m_sub_options vo_sub_opts = { .keepaspect = true, .keepaspect_window = true, .native_fs = true, + .input_ime = true, .taskbar_progress = true, .show_in_taskbar = true, .border = true, diff --git a/options/options.h b/options/options.h index 3f184c49cf5a9..31e72cfcec6ce 100644 --- a/options/options.h +++ b/options/options.h @@ -34,6 +34,7 @@ typedef struct mp_vo_opts { bool x11_wid_title; bool cursor_passthrough; bool native_keyrepeat; + bool input_ime; int wl_configure_bounds; int wl_content_type; diff --git a/video/out/meson.build b/video/out/meson.build index 95ebd4a8cdbc4..a68cf72f8fa31 100644 --- a/video/out/meson.build +++ b/video/out/meson.build @@ -3,6 +3,7 @@ protocols = [[wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'] [wl_protocol_dir, 'stable/viewporter/viewporter.xml'], [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/text-input/text-input-unstable-v3.xml'], [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], [wl_protocol_dir, 'staging/content-type/content-type-v1.xml'], diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index cfa90a957cd88..9392cea38177b 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -37,6 +37,7 @@ // Generated from wayland-protocols #include "idle-inhibit-unstable-v1.h" +#include "text-input-unstable-v3.h" #include "linux-dmabuf-unstable-v1.h" #include "presentation-time.h" #include "xdg-decoration-unstable-v1.h" @@ -191,6 +192,7 @@ struct vo_wayland_seat { struct wl_pointer *pointer; struct wl_touch *touch; struct wl_data_device *dnd_ddev; + struct vo_wayland_text_input *text_input; /* TODO: unvoid this if required wayland protocols is bumped to 1.32+ */ void *cursor_shape_device; uint32_t pointer_enter_serial; @@ -230,6 +232,13 @@ struct vo_wayland_data_offer { bool offered_plain_text; }; +struct vo_wayland_text_input { + struct zwp_text_input_v3 *text_input; + uint32_t serial; + char *commit_string; + bool has_focus; +}; + static bool single_output_spanned(struct vo_wayland_state *wl); static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels, @@ -877,6 +886,96 @@ static const struct wl_data_device_listener data_device_listener = { data_device_handle_selection, }; +static void enable_ime(struct vo_wayland_text_input *ti) +{ + if (!ti->has_focus) + return; + + zwp_text_input_v3_enable(ti->text_input); + zwp_text_input_v3_set_content_type( + ti->text_input, + ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL); + zwp_text_input_v3_set_cursor_rectangle(ti->text_input, 0, 0, 0, 0); + zwp_text_input_v3_commit(ti->text_input); + ti->serial++; +} + +static void disable_ime(struct vo_wayland_text_input *ti) +{ + zwp_text_input_v3_disable(ti->text_input); + zwp_text_input_v3_commit(ti->text_input); + ti->serial++; +} + +static void text_input_enter(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + struct wl_surface *surface) +{ + struct vo_wayland_seat *s = data; + struct vo_wayland_text_input *ti = s->text_input; + struct vo_wayland_state *wl = s->wl; + + ti->has_focus = true; + + if (!wl->opts->input_ime) + return; + + enable_ime(ti); +} + +static void text_input_leave(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + struct wl_surface *surface) +{ + struct vo_wayland_seat *s = data; + struct vo_wayland_text_input *ti = s->text_input; + + ti->has_focus = false; + disable_ime(ti); +} + +static void text_input_preedit_string(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + const char *text, int32_t cursor_begin, int32_t cursor_end) +{ +} + +static void text_input_commit_string(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + const char *text) +{ + struct vo_wayland_seat *s = data; + struct vo_wayland_text_input *ti = s->text_input; + + talloc_replace(ti, ti->commit_string, text); +} + +static void text_input_delete_surrounding_text(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t before_length, uint32_t after_length) +{ +} + +static void text_input_done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t serial) +{ + struct vo_wayland_seat *s = data; + struct vo_wayland_text_input *ti = s->text_input; + struct vo_wayland_state *wl = s->wl; + + if (ti->serial == serial && ti->commit_string) + mp_input_put_key_utf8(wl->vo->input_ctx, 0, bstr0(ti->commit_string)); + + if (ti->commit_string) + TA_FREEP(&ti->commit_string); +} + +static const struct zwp_text_input_v3_listener text_input_listener = { + text_input_enter, + text_input_leave, + text_input_preedit_string, + text_input_commit_string, + text_input_delete_surrounding_text, + text_input_done, +}; + static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, int32_t subpixel, @@ -1805,6 +1904,11 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id wl->idle_inhibit_manager = wl_registry_bind(reg, id, &zwp_idle_inhibit_manager_v1_interface, ver); } + if (!strcmp(interface, zwp_text_input_manager_v3_interface.name) && found++) { + ver = 1; + wl->text_input_manager = wl_registry_bind(reg, id, &zwp_text_input_manager_v3_interface, ver); + } + if (found > 1) MP_VERBOSE(wl, "Registered interface %s at version %d\n", interface, ver); } @@ -2433,6 +2537,8 @@ static void remove_seat(struct vo_wayland_seat *seat) wl_touch_destroy(seat->touch); if (seat->dnd_ddev) wl_data_device_destroy(seat->dnd_ddev); + if (seat->text_input) + zwp_text_input_v3_destroy(seat->text_input->text_input); #if HAVE_WAYLAND_PROTOCOLS_1_32 if (seat->cursor_shape_device) wp_cursor_shape_device_v1_destroy(seat->cursor_shape_device); @@ -2744,6 +2850,21 @@ static void toggle_fullscreen(struct vo_wayland_state *wl) } } +static void toggle_ime(struct vo_wayland_state *wl) +{ + struct vo_wayland_seat *seat; + wl_list_for_each(seat, &wl->seat_list, link) { + struct vo_wayland_text_input *ti = seat->text_input; + if (!ti) + continue; + if (wl->opts->input_ime) { + enable_ime(ti); + } else { + disable_ime(ti); + } + } +} + static void toggle_maximized(struct vo_wayland_state *wl) { if (wl->opts->window_maximized) { @@ -2935,6 +3056,8 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) set_content_type(wl); if (opt == &opts->cursor_passthrough) set_input_region(wl, opts->cursor_passthrough); + if (opt == &opts->input_ime) + toggle_ime(wl); if (opt == &opts->fullscreen) toggle_fullscreen(wl); if (opt == &opts->window_maximized) @@ -3248,6 +3371,18 @@ bool vo_wayland_init(struct vo *vo) zwp_idle_inhibit_manager_v1_interface.name); } + if (wl->text_input_manager) { + struct vo_wayland_seat *seat; + wl_list_for_each(seat, &wl->seat_list, link) { + seat->text_input = talloc_zero(seat, struct vo_wayland_text_input); + seat->text_input->text_input = zwp_text_input_manager_v3_get_text_input(wl->text_input_manager, seat->seat); + zwp_text_input_v3_add_listener(seat->text_input->text_input, &text_input_listener, seat); + } + } else { + MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", + zwp_text_input_manager_v3_interface.name); + } + wl->display_fd = wl_display_get_fd(wl->display); update_app_id(wl); @@ -3406,6 +3541,9 @@ void vo_wayland_uninit(struct vo *vo) if (wl->idle_inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(wl->idle_inhibit_manager); + if (wl->text_input_manager) + zwp_text_input_manager_v3_destroy(wl->text_input_manager); + if (wl->presentation) wp_presentation_destroy(wl->presentation); diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index 38939d56ac6b1..da97de9335512 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -121,6 +121,9 @@ struct vo_wayland_state { struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager; struct zwp_idle_inhibitor_v1 *idle_inhibitor; + /* text-input */ + struct zwp_text_input_manager_v3 *text_input_manager; + /* linux-dmabuf */ struct wl_list tranche_list; struct vo_wayland_tranche *current_tranche;