GHOST/Wayland: IME support using the text-input protocol

Tested with IBUS on GNOME 45.
Added a capabilities flag to GHOST since support for IME works on
Wayland but not on X11, so runtime detection is needed.
This commit is contained in:
Campbell Barton
2023-10-19 09:09:59 +11:00
parent 4830521a31
commit a38a49b073
13 changed files with 547 additions and 7 deletions

View File

@@ -511,7 +511,7 @@ Boost uses ICU library (required for linking with static Boost built with libicu
endif() endif()
# Misc # Misc
if(WIN32 OR APPLE) if(WIN32 OR APPLE OR ((UNIX AND (NOT HAIKU)) AND WITH_GHOST_WAYLAND))
option(WITH_INPUT_IME "Enable Input Method Editor (IME) for complex Asian character input" ON) option(WITH_INPUT_IME "Enable Input Method Editor (IME) for complex Asian character input" ON)
else() else()
set(WITH_INPUT_IME OFF) set(WITH_INPUT_IME OFF)

View File

@@ -439,6 +439,12 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
generate_protocol_bindings( generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/primary-selection/primary-selection-unstable-v1.xml" "${WAYLAND_PROTOCOLS_DIR}/unstable/primary-selection/primary-selection-unstable-v1.xml"
) )
if(WITH_INPUT_IME)
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/text-input/text-input-unstable-v3.xml"
)
add_definitions(-DWITH_INPUT_IME)
endif()
unset(INC_DST) unset(INC_DST)

View File

@@ -119,6 +119,10 @@ typedef enum {
* Support for sampling a color outside of the Blender windows. * Support for sampling a color outside of the Blender windows.
*/ */
GHOST_kCapabilityDesktopSample = (1 << 5), GHOST_kCapabilityDesktopSample = (1 << 5),
/**
* Supports IME text input methods (when `WITH_INPUT_IME` is defined).
*/
GHOST_kCapabilityInputIME = (1 << 6),
} GHOST_TCapabilityFlag; } GHOST_TCapabilityFlag;
/** /**
@@ -128,7 +132,7 @@ typedef enum {
#define GHOST_CAPABILITY_FLAG_ALL \ #define GHOST_CAPABILITY_FLAG_ALL \
(GHOST_kCapabilityCursorWarp | GHOST_kCapabilityWindowPosition | \ (GHOST_kCapabilityCursorWarp | GHOST_kCapabilityWindowPosition | \
GHOST_kCapabilityPrimaryClipboard | GHOST_kCapabilityGPUReadFrontBuffer | \ GHOST_kCapabilityPrimaryClipboard | GHOST_kCapabilityGPUReadFrontBuffer | \
GHOST_kCapabilityClipboardImages | GHOST_kCapabilityDesktopSample) GHOST_kCapabilityClipboardImages | GHOST_kCapabilityDesktopSample | GHOST_kCapabilityInputIME)
/* Xtilt and Ytilt represent how much the pen is tilted away from /* Xtilt and Ytilt represent how much the pen is tilted away from
* vertically upright in either the X or Y direction, with X and Y the * vertically upright in either the X or Y direction, with X and Y the

View File

@@ -51,7 +51,7 @@ class GHOST_SystemHeadless : public GHOST_System {
~(GHOST_kCapabilityWindowPosition | GHOST_kCapabilityCursorWarp | ~(GHOST_kCapabilityWindowPosition | GHOST_kCapabilityCursorWarp |
GHOST_kCapabilityPrimaryClipboard | GHOST_kCapabilityPrimaryClipboard |
GHOST_kCapabilityDesktopSample | GHOST_kCapabilityDesktopSample |
GHOST_kCapabilityClipboardImages)); GHOST_kCapabilityClipboardImages | GHOST_kCapabilityInputIME));
} }
char *getClipboard(bool /*selection*/) const override char *getClipboard(bool /*selection*/) const override
{ {

View File

@@ -781,7 +781,9 @@ GHOST_TCapabilityFlag GHOST_SystemSDL::getCapabilities() const
/* This SDL back-end has not yet implemented color sampling the desktop. */ /* This SDL back-end has not yet implemented color sampling the desktop. */
GHOST_kCapabilityDesktopSample | GHOST_kCapabilityDesktopSample |
/* This SDL back-end has not yet implemented image copy/paste. */ /* This SDL back-end has not yet implemented image copy/paste. */
GHOST_kCapabilityClipboardImages)); GHOST_kCapabilityClipboardImages |
/* No support yet for IME input methods. */
GHOST_kCapabilityInputIME));
} }
char *GHOST_SystemSDL::getClipboard(bool /*selection*/) const char *GHOST_SystemSDL::getClipboard(bool /*selection*/) const

View File

@@ -68,6 +68,9 @@
#include <viewporter-client-protocol.h> #include <viewporter-client-protocol.h>
#include <xdg-activation-v1-client-protocol.h> #include <xdg-activation-v1-client-protocol.h>
#include <xdg-output-unstable-v1-client-protocol.h> #include <xdg-output-unstable-v1-client-protocol.h>
#ifdef WITH_INPUT_IME
# include <text-input-unstable-v3-client-protocol.h>
#endif
/* Decorations `xdg_decor`. */ /* Decorations `xdg_decor`. */
#include <xdg-decoration-unstable-v1-client-protocol.h> #include <xdg-decoration-unstable-v1-client-protocol.h>
@@ -720,6 +723,51 @@ static void gwl_primary_selection_discard_source(GWL_PrimarySelection *primary)
primary->data_source = nullptr; primary->data_source = nullptr;
} }
#ifdef WITH_INPUT_IME
struct GWL_SeatIME {
struct wl_surface *surface_window = nullptr;
GHOST_TEventImeData event_ime_data = {
/*result_len*/ nullptr,
/*composite_len*/ nullptr,
/*result*/ nullptr,
/*composite*/ nullptr,
/*cursor_position*/ -1,
/*target_start*/ -1,
/*target_end*/ -1,
};
/** When true, the client has indicated that IME input should be activated on text entry. */
bool is_enabled = false;
/**
* When true, some pre-edit text has been entered
* (an IME popup may be showing however this isn't known).
*/
bool has_preedit = false;
/** Storage for #GHOST_TEventImeData::result (the result of the `commit_string` callback). */
std::string result;
/** Storage for #GHOST_TEventImeData::composite (the result of the `preedit_string` callback). */
std::string composite;
/** #zwp_text_input_v3_listener::commit_string was called with a null text argument. */
bool result_is_null = false;
/** #zwp_text_input_v3_listener::preedit_string was called with a null text argument. */
bool composite_is_null = false;
/** #zwp_text_input_v3_listener::preedit_string was called. */
bool has_preedit_string_callback = false;
/** #zwp_text_input_v3_listener::commit_string was called. */
bool has_commit_string_callback = false;
/** Bounds (use for comparison). */
struct {
int x = -1;
int y = -1;
int w = -1;
int h = -1;
} rect;
};
#endif /* WITH_INPUT_IME */
struct GWL_Seat { struct GWL_Seat {
/** Wayland core types. */ /** Wayland core types. */
@@ -755,6 +803,10 @@ struct GWL_Seat {
/** All currently active tablet tools (needed for changing the cursor). */ /** All currently active tablet tools (needed for changing the cursor). */
std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools; std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools;
#ifdef WITH_INPUT_IME
struct zwp_text_input_v3 *text_input = nullptr;
#endif
} wp; } wp;
/** XKB native types. */ /** XKB native types. */
@@ -780,6 +832,10 @@ struct GWL_Seat {
} xkb; } xkb;
#ifdef WITH_INPUT_IME
GWL_SeatIME ime;
#endif
GHOST_SystemWayland *system = nullptr; GHOST_SystemWayland *system = nullptr;
std::string name; std::string name;
@@ -914,6 +970,60 @@ static void gwl_seat_key_repeat_timer_remove(GWL_Seat *seat)
seat->key_repeat.timer = nullptr; seat->key_repeat.timer = nullptr;
} }
#ifdef WITH_INPUT_IME
static void gwl_seat_ime_full_reset(GWL_Seat *seat)
{
const GWL_SeatIME ime_default{};
/* Preserve the following members since they represent the state of the connection to Wayland.
* or which callbacks have run (which shouldn't be reset). */
wl_surface *surface_window = seat->ime.surface_window;
const bool is_enabled = seat->ime.is_enabled;
const bool has_preedit_string_callback = seat->ime.has_preedit_string_callback;
const bool has_commit_string_callback = seat->ime.has_commit_string_callback;
seat->ime = ime_default;
seat->ime.surface_window = surface_window;
seat->ime.is_enabled = is_enabled;
seat->ime.has_preedit_string_callback = has_preedit_string_callback;
seat->ime.has_commit_string_callback = has_commit_string_callback;
}
static void gwl_seat_ime_result_reset(GWL_Seat *seat)
{
seat->ime.result.clear();
seat->ime.result_is_null = false;
GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data;
event_ime_data.result_len = nullptr;
event_ime_data.result = nullptr;
}
static void gwl_seat_ime_preedit_reset(GWL_Seat *seat)
{
seat->ime.composite.clear();
seat->ime.composite_is_null = false;
GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data;
event_ime_data.composite_len = nullptr;
event_ime_data.composite = nullptr;
event_ime_data.cursor_position = -1;
event_ime_data.target_start = -1;
event_ime_data.target_end = -1;
}
static void gwl_seat_ime_rect_reset(GWL_Seat *seat)
{
seat->ime.rect.x = -1;
seat->ime.rect.y = -1;
seat->ime.rect.w = -1;
seat->ime.rect.h = -1;
}
#endif /* WITH_INPUT_IME */
/** \} */ /** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@@ -945,6 +1055,9 @@ struct GWL_Display {
wp_viewporter *viewporter = nullptr; wp_viewporter *viewporter = nullptr;
zwp_pointer_constraints_v1 *pointer_constraints = nullptr; zwp_pointer_constraints_v1 *pointer_constraints = nullptr;
zwp_pointer_gestures_v1 *pointer_gestures = nullptr; zwp_pointer_gestures_v1 *pointer_gestures = nullptr;
#ifdef WITH_INPUT_IME
struct zwp_text_input_manager_v3 *text_input_manager = nullptr;
#endif
} wp; } wp;
/** Wayland XDG types. */ /** Wayland XDG types. */
@@ -4338,6 +4451,229 @@ static const zwp_primary_selection_source_v1_listener primary_selection_source_l
/** \} */ /** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (Text Input), #zwp_text_input_manager_v3
* \{ */
#ifdef WITH_INPUT_IME
class GHOST_EventIME : public GHOST_Event {
public:
/**
* Constructor.
* \param msec: The time this event was generated.
* \param type: The type of key event.
* \param key: The key code of the key.
*/
GHOST_EventIME(uint64_t msec, GHOST_TEventType type, GHOST_IWindow *window, void *customdata)
: GHOST_Event(msec, type, window)
{
this->m_data = customdata;
}
};
static CLG_LogRef LOG_WL_TEXT_INPUT = {"ghost.wl.handle.text_input"};
# define LOG (&LOG_WL_TEXT_INPUT)
static void text_input_handle_enter(void *data,
struct zwp_text_input_v3 * /*zwp_text_input_v3*/,
struct wl_surface *surface)
{
if (!ghost_wl_surface_own(surface)) {
return;
}
CLOG_INFO(LOG, 2, "enter");
GWL_Seat *seat = static_cast<GWL_Seat *>(data);
seat->ime.surface_window = surface;
}
static void text_input_handle_leave(void *data,
struct zwp_text_input_v3 * /*zwp_text_input_v3*/,
struct wl_surface *surface)
{
/* Can be null when closing a window. */
if (!ghost_wl_surface_own_with_null_check(surface)) {
return;
}
CLOG_INFO(LOG, 2, "leave");
GWL_Seat *seat = static_cast<GWL_Seat *>(data);
if (seat->ime.surface_window == surface) {
seat->ime.surface_window = nullptr;
}
}
static void text_input_handle_preedit_string(void *data,
struct zwp_text_input_v3 * /*zwp_text_input_v3*/,
const char *text,
int32_t cursor_begin,
int32_t cursor_end)
{
CLOG_INFO(LOG,
2,
"preedit_string (text=\"%s\", cursor_begin=%d, cursor_end=%d)",
text ? text : "<null>",
cursor_begin,
cursor_end);
GWL_Seat *seat = static_cast<GWL_Seat *>(data);
if (UNLIKELY(seat->ime.surface_window == nullptr)) {
return;
}
if (seat->ime.has_preedit == false) {
/* Starting IME input. */
gwl_seat_ime_full_reset(seat);
}
seat->ime.composite_is_null = (text == nullptr);
if (!seat->ime.composite_is_null) {
seat->ime.composite = text;
seat->ime.event_ime_data.composite = (void *)seat->ime.composite.c_str();
seat->ime.event_ime_data.composite_len = (void *)seat->ime.composite.size();
seat->ime.event_ime_data.cursor_position = cursor_begin;
seat->ime.event_ime_data.target_start = cursor_begin;
seat->ime.event_ime_data.target_end = cursor_end;
}
seat->ime.has_preedit_string_callback = true;
}
static void text_input_handle_commit_string(void *data,
struct zwp_text_input_v3 * /*zwp_text_input_v3*/,
const char *text)
{
CLOG_INFO(LOG, 2, "commit_string (text=\"%s\")", text ? text : "<null>");
GWL_Seat *seat = static_cast<GWL_Seat *>(data);
if (UNLIKELY(seat->ime.surface_window == nullptr)) {
return;
}
seat->ime.result_is_null = (text == nullptr);
if (seat->ime.result_is_null) {
seat->ime.result = "";
}
else {
seat->ime.result = text;
}
seat->ime.result_is_null = (text == nullptr);
seat->ime.event_ime_data.result = (void *)seat->ime.result.c_str();
seat->ime.event_ime_data.result_len = (void *)seat->ime.result.size();
seat->ime.event_ime_data.cursor_position = seat->ime.result.size();
seat->ime.has_commit_string_callback = true;
}
static void text_input_handle_delete_surrounding_text(
void * /*data*/,
struct zwp_text_input_v3 * /*zwp_text_input_v3*/,
uint32_t before_length,
uint32_t after_length)
{
CLOG_INFO(LOG,
2,
"delete_surrounding_text (before_length=%u, after_length=%u)",
before_length,
after_length);
/* NOTE: Currently unused, do we care about this event?
* SDL ignores this event. */
}
static void text_input_handle_done(void *data,
struct zwp_text_input_v3 * /*zwp_text_input_v3*/,
uint32_t /*serial*/)
{
CLOG_INFO(LOG, 2, "done");
GWL_Seat *seat = static_cast<GWL_Seat *>(data);
GHOST_SystemWayland *system = seat->system;
GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->ime.surface_window);
if (seat->ime.has_commit_string_callback) {
if (seat->ime.has_preedit) {
const bool is_end = seat->ime.composite_is_null;
if (is_end) {
seat->ime.has_preedit = false;
/* `commit_string` (end). */
system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(),
GHOST_kEventImeComposition,
win,
&seat->ime.event_ime_data));
system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(),
GHOST_kEventImeCompositionEnd,
win,
&seat->ime.event_ime_data));
}
else {
/* `commit_string` (continues). */
system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(),
GHOST_kEventImeComposition,
win,
&seat->ime.event_ime_data));
}
}
else {
/* `commit_string` ran with no active IME popup, start & end to insert text. */
system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(),
GHOST_kEventImeCompositionStart,
win,
&seat->ime.event_ime_data));
system->pushEvent_maybe_pending(new GHOST_EventIME(
system->getMilliSeconds(), GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(),
GHOST_kEventImeCompositionEnd,
win,
&seat->ime.event_ime_data));
}
if (seat->ime.has_preedit == false) {
gwl_seat_ime_preedit_reset(seat);
}
}
else if (seat->ime.has_preedit_string_callback) {
const bool is_end = seat->ime.composite_is_null;
if (is_end) {
/* `preedit_string` (end). */
seat->ime.has_preedit = false;
system->pushEvent_maybe_pending(new GHOST_EventIME(seat->system->getMilliSeconds(),
GHOST_kEventImeCompositionEnd,
win,
&seat->ime.event_ime_data));
}
else {
const bool is_start = seat->ime.has_preedit == false;
/* `preedit_string` (start or continue). */
seat->ime.has_preedit = true;
system->pushEvent_maybe_pending(new GHOST_EventIME(
seat->system->getMilliSeconds(),
is_start ? GHOST_kEventImeCompositionStart : GHOST_kEventImeComposition,
win,
&seat->ime.event_ime_data));
}
}
seat->ime.has_preedit_string_callback = false;
seat->ime.has_commit_string_callback = false;
}
static struct zwp_text_input_v3_listener text_input_listener = {
/*enter*/ text_input_handle_enter,
/*leave*/ text_input_handle_leave,
/*preedit_string*/ text_input_handle_preedit_string,
/*commit_string*/ text_input_handle_commit_string,
/*delete_surrounding_text*/ text_input_handle_delete_surrounding_text,
/*done*/ text_input_handle_done,
};
# undef LOG
#endif /* WITH_INPUT_IME. */
/** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/** \name Listener (Seat), #wl_seat_listener /** \name Listener (Seat), #wl_seat_listener
* \{ */ * \{ */
@@ -5030,6 +5366,20 @@ static void gwl_registry_wl_seat_update(GWL_Display *display,
else { else {
seat->wp.primary_selection_device = nullptr; seat->wp.primary_selection_device = nullptr;
} }
#ifdef WITH_INPUT_IME
if (display->wp.text_input_manager) {
if (seat->wp.text_input == nullptr) {
seat->wp.text_input = zwp_text_input_manager_v3_get_text_input(
display->wp.text_input_manager, seat->wl.seat);
zwp_text_input_v3_set_user_data(seat->wp.text_input, seat);
zwp_text_input_v3_add_listener(seat->wp.text_input, &text_input_listener, seat);
}
}
else {
seat->wp.text_input = nullptr;
}
#endif /* WITH_INPUT_IME */
} }
static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit) static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit)
{ {
@@ -5312,6 +5662,28 @@ static void gwl_registry_wp_primary_selection_device_manager_remove(GWL_Display
*value_p = nullptr; *value_p = nullptr;
} }
#ifdef WITH_INPUT_IME
/* #GWL_Display.wp_text_input_manager */
static void gwl_registry_wp_text_input_manager_add(GWL_Display *display,
const GWL_RegisteryAdd_Params *params)
{
display->wp.text_input_manager = static_cast<zwp_text_input_manager_v3 *>(wl_registry_bind(
display->wl.registry, params->name, &zwp_text_input_manager_v3_interface, 1));
gwl_registry_entry_add(display, params, nullptr);
}
static void gwl_registry_wp_text_input_manager_remove(GWL_Display *display,
void * /*user_data*/,
const bool /*on_exit*/)
{
struct zwp_text_input_manager_v3 **value_p = &display->wp.text_input_manager;
zwp_text_input_manager_v3_destroy(*value_p);
*value_p = nullptr;
}
#endif /* WITH_INPUT_IME */
/** /**
* Map interfaces to initialization functions. * Map interfaces to initialization functions.
* *
@@ -5379,6 +5751,14 @@ static const GWL_RegistryHandler gwl_registry_handlers[] = {
/*update_fn*/ nullptr, /*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_wp_relative_pointer_manager_remove, /*remove_fn*/ gwl_registry_wp_relative_pointer_manager_remove,
}, },
#ifdef WITH_INPUT_IME
{
/*interface_p*/ &zwp_text_input_manager_v3_interface.name,
/*add_fn*/ gwl_registry_wp_text_input_manager_add,
/*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_wp_text_input_manager_remove,
},
#endif
/* Higher level interfaces. */ /* Higher level interfaces. */
{ {
/*interface_p*/ &zwp_pointer_constraints_v1_interface.name, /*interface_p*/ &zwp_pointer_constraints_v1_interface.name,
@@ -7143,6 +7523,105 @@ GHOST_TimerManager *GHOST_SystemWayland::ghost_timer_manager()
/** \} */ /** \} */
/* -------------------------------------------------------------------- */
/** \name Public WAYLAND Text Input (IME) Functions
*
* Functionality only used for the WAYLAND implementation.
* \{ */
#ifdef WITH_INPUT_IME
void GHOST_SystemWayland::ime_begin(
GHOST_WindowWayland *win, int32_t x, int32_t y, int32_t w, int32_t h, bool completed) const
{
GWL_Seat *seat = gwl_display_seat_active_get(display_);
if (UNLIKELY(!seat)) {
return;
}
if (seat->wp.text_input == nullptr) {
return;
}
/* Prevent a feedback loop because the commits from this function cause
* #zwp_text_input_v3_listener::preedit_string to run again which sends an event,
* refreshing the position, running this function again. */
gwl_seat_ime_result_reset(seat);
/* Don't re-enable if we're already enabled. */
if (seat->ime.is_enabled && completed) {
return;
}
bool force_rect_update = false;
if (seat->ime.is_enabled == false) {
seat->ime.has_preedit = false;
seat->ime.is_enabled = true;
/* NOTE(@flibit): For some reason this has to be done twice,
* it appears to be a bug in mutter? Maybe? */
zwp_text_input_v3_enable(seat->wp.text_input);
zwp_text_input_v3_commit(seat->wp.text_input);
zwp_text_input_v3_enable(seat->wp.text_input);
zwp_text_input_v3_commit(seat->wp.text_input);
/* Now that it's enabled, set the input properties. */
zwp_text_input_v3_set_content_type(seat->wp.text_input,
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL);
gwl_seat_ime_rect_reset(seat);
force_rect_update = true;
}
if ((force_rect_update == false) && /* Was just created, always update. */
(seat->ime.rect.x == x) && /* X. */
(seat->ime.rect.y == y) && /* Y. */
(seat->ime.rect.w == w) && /* W. */
(seat->ime.rect.h == h)) /* H. */
{
/* Only re-update the rectangle as needed. */
printf("DONT REPOSITION\n");
}
else {
const int rect_x = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(x)));
const int rect_y = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(y)));
const int rect_w = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(w))) + 1;
const int rect_h = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(h))) + 1;
zwp_text_input_v3_set_cursor_rectangle(seat->wp.text_input, rect_x, rect_y, rect_w, rect_h);
zwp_text_input_v3_commit(seat->wp.text_input);
seat->ime.rect.x = x;
seat->ime.rect.y = y;
seat->ime.rect.w = w;
seat->ime.rect.h = h;
}
}
void GHOST_SystemWayland::ime_end(GHOST_WindowWayland * /*window*/) const
{
GWL_Seat *seat = gwl_display_seat_active_get(display_);
if (UNLIKELY(!seat)) {
return;
}
seat->ime.is_enabled = false;
gwl_seat_ime_rect_reset(seat);
if (seat->wp.text_input == nullptr) {
return;
}
zwp_text_input_v3_disable(seat->wp.text_input);
zwp_text_input_v3_commit(seat->wp.text_input);
}
#endif /* WITH_INPUT_IME */
/** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/** \name Public WAYLAND Query Access /** \name Public WAYLAND Query Access
* \{ */ * \{ */
@@ -7215,6 +7694,9 @@ bool GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface)
SURFACE_CLEAR_PTR(seat->tablet.wl.surface_window); SURFACE_CLEAR_PTR(seat->tablet.wl.surface_window);
SURFACE_CLEAR_PTR(seat->keyboard.wl.surface_window); SURFACE_CLEAR_PTR(seat->keyboard.wl.surface_window);
SURFACE_CLEAR_PTR(seat->wl.surface_window_focus_dnd); SURFACE_CLEAR_PTR(seat->wl.surface_window_focus_dnd);
#ifdef WITH_INPUT_IME
SURFACE_CLEAR_PTR(seat->ime.surface_window);
#endif
} }
#undef SURFACE_CLEAR_PTR #undef SURFACE_CLEAR_PTR

View File

@@ -236,6 +236,10 @@ class GHOST_SystemWayland : public GHOST_System {
struct wl_shm *wl_shm_get() const; struct wl_shm *wl_shm_get() const;
void ime_begin(
GHOST_WindowWayland *win, int32_t x, int32_t y, int32_t w, int32_t h, bool completed) const;
void ime_end(GHOST_WindowWayland *win) const;
static const char *xdg_app_id_get(); static const char *xdg_app_id_get();
/* WAYLAND utility functions. */ /* WAYLAND utility functions. */

View File

@@ -1742,7 +1742,9 @@ GHOST_TCapabilityFlag GHOST_SystemX11::getCapabilities() const
/* No support yet for desktop sampling. */ /* No support yet for desktop sampling. */
GHOST_kCapabilityDesktopSample | GHOST_kCapabilityDesktopSample |
/* No support yet for image copy/paste. */ /* No support yet for image copy/paste. */
GHOST_kCapabilityClipboardImages)); GHOST_kCapabilityClipboardImages |
/* No support yet for IME input methods. */
GHOST_kCapabilityInputIME));
} }
void GHOST_SystemX11::addDirtyWindow(GHOST_WindowX11 *bad_wind) void GHOST_SystemX11::addDirtyWindow(GHOST_WindowX11 *bad_wind)

View File

@@ -2012,6 +2012,20 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType
} }
} }
#ifdef WITH_INPUT_IME
void GHOST_WindowWayland::beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed)
{
system_->ime_begin(this, x, y, w, h, completed);
}
void GHOST_WindowWayland::endIME()
{
system_->ime_end(this);
}
#endif
/** \} */ /** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */

View File

@@ -140,6 +140,11 @@ class GHOST_WindowWayland : public GHOST_Window {
void setOpaque() const; void setOpaque() const;
#endif #endif
#ifdef WITH_INPUT_IME
void beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed) override;
void endIME() override;
#endif /* WITH_INPUT_IME */
/* WAYLAND direct-data access. */ /* WAYLAND direct-data access. */
int scale_get() const; int scale_get() const;

View File

@@ -3604,7 +3604,11 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
data->undo_stack_text = nullptr; data->undo_stack_text = nullptr;
#ifdef WITH_INPUT_IME #ifdef WITH_INPUT_IME
if (win->ime_data) { /* See #wm_window_IME_end code-comments for details. */
# if defined(WIN32) || defined(__APPLE__)
if (win->ime_data)
# endif
{
ui_textedit_ime_end(win, but); ui_textedit_ime_end(win, but);
} }
#endif #endif

View File

@@ -172,6 +172,8 @@ enum eWM_CapabilitiesFlag {
WM_CAPABILITY_CLIPBOARD_IMAGES = (1 << 4), WM_CAPABILITY_CLIPBOARD_IMAGES = (1 << 4),
/** Ability to sample a color outside of Blender windows. */ /** Ability to sample a color outside of Blender windows. */
WM_CAPABILITY_DESKTOP_SAMPLE = (1 << 5), WM_CAPABILITY_DESKTOP_SAMPLE = (1 << 5),
/** Support for IME input methods. */
WM_CAPABILITY_INPUT_IME = (1 << 6),
/** The initial value, indicates the value needs to be set by inspecting GHOST. */ /** The initial value, indicates the value needs to be set by inspecting GHOST. */
WM_CAPABILITY_INITIALIZED = (1 << 31), WM_CAPABILITY_INITIALIZED = (1 << 31),
}; };

View File

@@ -1977,6 +1977,9 @@ eWM_CapabilitiesFlag WM_capabilities_flag()
if (ghost_flag & GHOST_kCapabilityDesktopSample) { if (ghost_flag & GHOST_kCapabilityDesktopSample) {
flag |= WM_CAPABILITY_DESKTOP_SAMPLE; flag |= WM_CAPABILITY_DESKTOP_SAMPLE;
} }
if (ghost_flag & GHOST_kCapabilityInputIME) {
flag |= WM_CAPABILITY_INPUT_IME;
}
return flag; return flag;
} }
@@ -2739,6 +2742,9 @@ bool WM_window_is_temp_screen(const wmWindow *win)
void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete) void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete)
{ {
BLI_assert(win); BLI_assert(win);
if ((WM_capabilities_flag() & WM_CAPABILITY_INPUT_IME) == 0) {
return;
}
/* Convert to native OS window coordinates. */ /* Convert to native OS window coordinates. */
float fac = GHOST_GetNativePixelSize(static_cast<GHOST_WindowHandle>(win->ghostwin)); float fac = GHOST_GetNativePixelSize(static_cast<GHOST_WindowHandle>(win->ghostwin));
@@ -2750,8 +2756,17 @@ void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complet
void wm_window_IME_end(wmWindow *win) void wm_window_IME_end(wmWindow *win)
{ {
BLI_assert(win && win->ime_data); if ((WM_capabilities_flag() & WM_CAPABILITY_INPUT_IME) == 0) {
return;
}
BLI_assert(win);
/* NOTE(@ideasman42): on WAYLAND a call to "begin" must be closed by an "end" call.
* Even if no IME events were generated (which assigned `ime_data`).
* TODO: check if #GHOST_EndIME can run on WIN32 & APPLE without causing problems. */
# if defined(WIN32) || defined(__APPLE__)
BLI_assert(win->ime_data);
# endif
GHOST_EndIME(static_cast<GHOST_WindowHandle>(win->ghostwin)); GHOST_EndIME(static_cast<GHOST_WindowHandle>(win->ghostwin));
win->ime_data = nullptr; win->ime_data = nullptr;
win->ime_data_is_composing = false; win->ime_data_is_composing = false;