[Wayland] Implement IME support.

This commit is contained in:
bruvzg
2024-06-11 12:24:54 +03:00
parent 62a056aa56
commit be25e60f61
8 changed files with 751 additions and 4 deletions

View File

@@ -151,6 +151,16 @@ env.WAYLAND_API_CODE(
source="#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml",
)
env.WAYLAND_API_HEADER(
target="protocol/text_input.gen.h",
source="#thirdparty/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml",
)
env.WAYLAND_API_CODE(
target="protocol/text_input.gen.c",
source="#thirdparty/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml",
)
env.WAYLAND_API_HEADER(
target="protocol/xdg_foreign.gen.h",
source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml",
@@ -175,6 +185,7 @@ source_files = [
"protocol/primary_selection.gen.c",
"protocol/idle_inhibit.gen.c",
"protocol/tablet.gen.c",
"protocol/text_input.gen.c",
"display_server_wayland.cpp",
"wayland_thread.cpp",
"key_mapping_xkb.cpp",

View File

@@ -208,6 +208,7 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const {
case FEATURE_HIDPI:
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_IME:
case FEATURE_CLIPBOARD_PRIMARY: {
return true;
} break;
@@ -903,13 +904,23 @@ bool DisplayServerWayland::can_any_window_draw() const {
}
void DisplayServerWayland::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) {
// TODO
DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_ime_active active %s", p_active ? "true" : "false"));
MutexLock mutex_lock(wayland_thread.mutex);
wayland_thread.window_set_ime_active(p_active, MAIN_WINDOW_ID);
}
void DisplayServerWayland::window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id) {
// TODO
DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_ime_position pos %s window %d", p_pos, p_window_id));
MutexLock mutex_lock(wayland_thread.mutex);
wayland_thread.window_set_ime_position(p_pos, MAIN_WINDOW_ID);
}
Point2i DisplayServerWayland::ime_get_selection() const {
return ime_selection;
}
String DisplayServerWayland::ime_get_text() const {
return ime_text;
}
// NOTE: While Wayland is supposed to be tear-free, wayland-protocols version
@@ -1146,6 +1157,37 @@ void DisplayServerWayland::process_events() {
}
}
}
Ref<WaylandThread::IMECommitEventMessage> ime_commit_msg = msg;
if (ime_commit_msg.is_valid()) {
for (int i = 0; i < ime_commit_msg->text.length(); i++) {
const char32_t codepoint = ime_commit_msg->text[i];
Ref<InputEventKey> ke;
ke.instantiate();
ke->set_window_id(MAIN_WINDOW_ID);
ke->set_pressed(true);
ke->set_echo(false);
ke->set_keycode(Key::NONE);
ke->set_physical_keycode(Key::NONE);
ke->set_key_label(Key::NONE);
ke->set_unicode(codepoint);
Input::get_singleton()->parse_input_event(ke);
}
ime_text = String();
ime_selection = Vector2i();
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
}
Ref<WaylandThread::IMEUpdateEventMessage> ime_update_msg = msg;
if (ime_update_msg.is_valid()) {
ime_text = ime_update_msg->text;
ime_selection = ime_update_msg->selection;
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
}
}
wayland_thread.keyboard_echo_keys();

View File

@@ -115,6 +115,9 @@ class DisplayServerWayland : public DisplayServer {
Context context;
String ime_text;
Vector2i ime_selection;
bool suspended = false;
bool emulate_vsync = false;
@@ -259,6 +262,9 @@ public:
virtual void window_set_ime_active(const bool p_active, WindowID p_window_id = MAIN_WINDOW_ID) override;
virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window_id = MAIN_WINDOW_ID) override;
virtual Point2i ime_get_selection() const override;
virtual String ime_get_text() const override;
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window_id = MAIN_WINDOW_ID) override;
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_window_id) const override;

View File

@@ -448,6 +448,12 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re
zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);
}
if (!ss->wp_text_input && registry->wp_text_input_manager) {
// IME.
ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat);
zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss);
}
registry->wl_seats.push_back(wl_seat);
wl_seat_add_listener(wl_seat, &wl_seat_listener, ss);
@@ -547,6 +553,22 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re
return;
}
if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) {
registry->wp_text_input_manager = (struct zwp_text_input_manager_v3 *)wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, 1);
registry->wp_text_input_manager_name = name;
// This global creates some seat data. Let's do that for the ones already available.
for (struct wl_seat *wl_seat : registry->wl_seats) {
SeatState *ss = wl_seat_get_seat_state(wl_seat);
ERR_FAIL_NULL(ss);
ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat);
zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss);
}
return;
}
}
void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) {
@@ -825,6 +847,25 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
return;
}
if (name == registry->wp_text_input_manager_name) {
if (registry->wp_text_input_manager) {
zwp_text_input_manager_v3_destroy(registry->wp_text_input_manager);
registry->wp_text_input_manager = nullptr;
}
registry->wp_text_input_manager_name = 0;
for (struct wl_seat *wl_seat : registry->wl_seats) {
SeatState *ss = wl_seat_get_seat_state(wl_seat);
ERR_FAIL_NULL(ss);
zwp_text_input_v3_destroy(ss->wp_text_input);
ss->wp_text_input = nullptr;
}
return;
}
{
// Iterate through all of the seats to find if any got removed.
List<struct wl_seat *>::Element *E = registry->wl_seats.front();
@@ -2535,6 +2576,118 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_
old_td = td;
}
void WaylandThread::_wp_text_input_on_enter(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) {
SeatState *ss = (SeatState *)data;
if (!ss) {
return;
}
ss->ime_enabled = true;
}
void WaylandThread::_wp_text_input_on_leave(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) {
SeatState *ss = (SeatState *)data;
if (!ss) {
return;
}
ss->ime_enabled = false;
ss->ime_active = false;
ss->ime_text = String();
ss->ime_text_commit = String();
ss->ime_cursor = Vector2i();
Ref<IMEUpdateEventMessage> msg;
msg.instantiate();
msg->text = String();
msg->selection = Vector2i();
ss->wayland_thread->push_message(msg);
}
void WaylandThread::_wp_text_input_on_preedit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text, int32_t cursor_begin, int32_t cursor_end) {
SeatState *ss = (SeatState *)data;
if (!ss) {
return;
}
ss->ime_text = String::utf8(text);
// Convert cursor positions from UTF-8 to UTF-32 offset.
int32_t cursor_begin_utf32 = 0;
int32_t cursor_end_utf32 = 0;
for (int i = 0; i < ss->ime_text.length(); i++) {
uint32_t c = ss->ime_text[i];
if (c <= 0x7f) { // 7 bits.
cursor_begin -= 1;
cursor_end -= 1;
} else if (c <= 0x7ff) { // 11 bits
cursor_begin -= 2;
cursor_end -= 2;
} else if (c <= 0xffff) { // 16 bits
cursor_begin -= 3;
cursor_end -= 3;
} else if (c <= 0x001fffff) { // 21 bits
cursor_begin -= 4;
cursor_end -= 4;
} else if (c <= 0x03ffffff) { // 26 bits
cursor_begin -= 5;
cursor_end -= 5;
} else if (c <= 0x7fffffff) { // 31 bits
cursor_begin -= 6;
cursor_end -= 6;
} else {
cursor_begin -= 1;
cursor_end -= 1;
}
if (cursor_begin == 0) {
cursor_begin_utf32 = i + 1;
}
if (cursor_end == 0) {
cursor_end_utf32 = i + 1;
}
if (cursor_begin <= 0 && cursor_end <= 0) {
break;
}
}
ss->ime_cursor = Vector2i(cursor_begin_utf32, cursor_end_utf32 - cursor_begin_utf32);
}
void WaylandThread::_wp_text_input_on_commit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text) {
SeatState *ss = (SeatState *)data;
if (!ss) {
return;
}
ss->ime_text_commit = String::utf8(text);
}
void WaylandThread::_wp_text_input_on_delete_surrounding_text(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t before_length, uint32_t after_length) {
// Not implemented.
}
void WaylandThread::_wp_text_input_on_done(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t serial) {
SeatState *ss = (SeatState *)data;
if (!ss) {
return;
}
if (!ss->ime_text_commit.is_empty()) {
Ref<IMECommitEventMessage> msg;
msg.instantiate();
msg->text = ss->ime_text_commit;
ss->wayland_thread->push_message(msg);
} else if (!ss->ime_text.is_empty()) {
Ref<IMEUpdateEventMessage> msg;
msg.instantiate();
msg->text = ss->ime_text;
msg->selection = ss->ime_cursor;
ss->wayland_thread->push_message(msg);
}
ss->ime_text = String();
ss->ime_text_commit = String();
ss->ime_cursor = Vector2i();
}
void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token) {
WindowState *ws = (WindowState *)data;
ERR_FAIL_NULL(ws);
@@ -3729,6 +3882,35 @@ void WaylandThread::cursor_shape_clear_custom_image(DisplayServer::CursorShape p
}
}
void WaylandThread::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) {
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
if (ss && ss->wp_text_input && ss->ime_enabled) {
if (p_active) {
ss->ime_active = true;
zwp_text_input_v3_enable(ss->wp_text_input);
zwp_text_input_v3_set_cursor_rectangle(ss->wp_text_input, ss->ime_rect.position.x, ss->ime_rect.position.y, ss->ime_rect.size.x, ss->ime_rect.size.y);
} else {
ss->ime_active = false;
ss->ime_text = String();
ss->ime_text_commit = String();
ss->ime_cursor = Vector2i();
zwp_text_input_v3_disable(ss->wp_text_input);
}
zwp_text_input_v3_commit(ss->wp_text_input);
}
}
void WaylandThread::window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id) {
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
if (ss && ss->wp_text_input && ss->ime_enabled) {
ss->ime_rect = Rect2i(p_pos, Size2i(1, 10));
zwp_text_input_v3_set_cursor_rectangle(ss->wp_text_input, ss->ime_rect.position.x, ss->ime_rect.position.y, ss->ime_rect.size.x, ss->ime_rect.size.y);
zwp_text_input_v3_commit(ss->wp_text_input);
}
}
int WaylandThread::keyboard_get_layout_count() const {
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);

View File

@@ -62,6 +62,7 @@
#undef pointer
#include "wayland/protocol/fractional_scale.gen.h"
#include "wayland/protocol/tablet.gen.h"
#include "wayland/protocol/text_input.gen.h"
#include "wayland/protocol/viewporter.gen.h"
#include "wayland/protocol/wayland.gen.h"
#include "wayland/protocol/xdg_activation.gen.h"
@@ -112,6 +113,17 @@ public:
Vector<String> files;
};
class IMEUpdateEventMessage : public Message {
public:
String text;
Vector2i selection;
};
class IMECommitEventMessage : public Message {
public:
String text;
};
struct RegistryState {
WaylandThread *wayland_thread;
@@ -170,6 +182,9 @@ public:
struct zwp_tablet_manager_v2 *wp_tablet_manager = nullptr;
uint32_t wp_tablet_manager_name = 0;
struct zwp_text_input_manager_v3 *wp_text_input_manager = nullptr;
uint32_t wp_text_input_manager_name = 0;
};
// General Wayland-specific states. Shouldn't be accessed directly.
@@ -438,6 +453,15 @@ public:
struct zwp_tablet_seat_v2 *wp_tablet_seat = nullptr;
List<struct zwp_tablet_tool_v2 *> tablet_tools;
// IME.
struct zwp_text_input_v3 *wp_text_input = nullptr;
bool ime_enabled = false;
bool ime_active = false;
String ime_text;
String ime_text_commit;
Vector2i ime_cursor;
Rect2i ime_rect;
};
struct CustomCursor {
@@ -618,6 +642,13 @@ private:
static void _wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state);
static void _wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t time);
static void _wp_text_input_on_enter(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface);
static void _wp_text_input_on_leave(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface);
static void _wp_text_input_on_preedit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text, int32_t cursor_begin, int32_t cursor_end);
static void _wp_text_input_on_commit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text);
static void _wp_text_input_on_delete_surrounding_text(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t before_length, uint32_t after_length);
static void _wp_text_input_on_done(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t serial);
static void _xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode);
static void _xdg_exported_on_exported(void *data, zxdg_exported_v1 *exported, const char *handle);
@@ -779,6 +810,15 @@ private:
.frame = _wp_tablet_tool_on_frame,
};
static constexpr struct zwp_text_input_v3_listener wp_text_input_listener = {
.enter = _wp_text_input_on_enter,
.leave = _wp_text_input_on_leave,
.preedit_string = _wp_text_input_on_preedit_string,
.commit_string = _wp_text_input_on_commit_string,
.delete_surrounding_text = _wp_text_input_on_delete_surrounding_text,
.done = _wp_text_input_on_done,
};
static constexpr struct zxdg_exported_v1_listener xdg_exported_listener = {
.handle = _xdg_exported_on_exported
};
@@ -929,6 +969,9 @@ public:
void cursor_shape_set_custom_image(DisplayServer::CursorShape p_cursor_shape, Ref<Image> p_image, const Point2i &p_hotspot);
void cursor_shape_clear_custom_image(DisplayServer::CursorShape p_cursor_shape);
void window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id);
void window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id);
int keyboard_get_layout_count() const;
int keyboard_get_current_layout_index() const;
void keyboard_set_current_layout_index(int p_index);