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:
@@ -511,7 +511,7 @@ Boost uses ICU library (required for linking with static Boost built with libicu
|
||||
endif()
|
||||
|
||||
# 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)
|
||||
else()
|
||||
set(WITH_INPUT_IME OFF)
|
||||
|
||||
@@ -439,6 +439,12 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
|
||||
generate_protocol_bindings(
|
||||
"${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)
|
||||
|
||||
|
||||
@@ -119,6 +119,10 @@ typedef enum {
|
||||
* Support for sampling a color outside of the Blender windows.
|
||||
*/
|
||||
GHOST_kCapabilityDesktopSample = (1 << 5),
|
||||
/**
|
||||
* Supports IME text input methods (when `WITH_INPUT_IME` is defined).
|
||||
*/
|
||||
GHOST_kCapabilityInputIME = (1 << 6),
|
||||
} GHOST_TCapabilityFlag;
|
||||
|
||||
/**
|
||||
@@ -128,7 +132,7 @@ typedef enum {
|
||||
#define GHOST_CAPABILITY_FLAG_ALL \
|
||||
(GHOST_kCapabilityCursorWarp | GHOST_kCapabilityWindowPosition | \
|
||||
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
|
||||
* vertically upright in either the X or Y direction, with X and Y the
|
||||
|
||||
@@ -51,7 +51,7 @@ class GHOST_SystemHeadless : public GHOST_System {
|
||||
~(GHOST_kCapabilityWindowPosition | GHOST_kCapabilityCursorWarp |
|
||||
GHOST_kCapabilityPrimaryClipboard |
|
||||
GHOST_kCapabilityDesktopSample |
|
||||
GHOST_kCapabilityClipboardImages));
|
||||
GHOST_kCapabilityClipboardImages | GHOST_kCapabilityInputIME));
|
||||
}
|
||||
char *getClipboard(bool /*selection*/) const override
|
||||
{
|
||||
|
||||
@@ -781,7 +781,9 @@ GHOST_TCapabilityFlag GHOST_SystemSDL::getCapabilities() const
|
||||
/* This SDL back-end has not yet implemented color sampling the desktop. */
|
||||
GHOST_kCapabilityDesktopSample |
|
||||
/* 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
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
#include <viewporter-client-protocol.h>
|
||||
#include <xdg-activation-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`. */
|
||||
#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;
|
||||
}
|
||||
|
||||
#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 {
|
||||
|
||||
/** Wayland core types. */
|
||||
@@ -755,6 +803,10 @@ struct GWL_Seat {
|
||||
|
||||
/** All currently active tablet tools (needed for changing the cursor). */
|
||||
std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools;
|
||||
|
||||
#ifdef WITH_INPUT_IME
|
||||
struct zwp_text_input_v3 *text_input = nullptr;
|
||||
#endif
|
||||
} wp;
|
||||
|
||||
/** XKB native types. */
|
||||
@@ -780,6 +832,10 @@ struct GWL_Seat {
|
||||
|
||||
} xkb;
|
||||
|
||||
#ifdef WITH_INPUT_IME
|
||||
GWL_SeatIME ime;
|
||||
#endif
|
||||
|
||||
GHOST_SystemWayland *system = nullptr;
|
||||
|
||||
std::string name;
|
||||
@@ -914,6 +970,60 @@ static void gwl_seat_key_repeat_timer_remove(GWL_Seat *seat)
|
||||
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;
|
||||
zwp_pointer_constraints_v1 *pointer_constraints = nullptr;
|
||||
zwp_pointer_gestures_v1 *pointer_gestures = nullptr;
|
||||
#ifdef WITH_INPUT_IME
|
||||
struct zwp_text_input_manager_v3 *text_input_manager = nullptr;
|
||||
#endif
|
||||
} wp;
|
||||
|
||||
/** 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
|
||||
* \{ */
|
||||
@@ -5030,6 +5366,20 @@ static void gwl_registry_wl_seat_update(GWL_Display *display,
|
||||
else {
|
||||
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)
|
||||
{
|
||||
@@ -5312,6 +5662,28 @@ static void gwl_registry_wp_primary_selection_device_manager_remove(GWL_Display
|
||||
*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.
|
||||
*
|
||||
@@ -5379,6 +5751,14 @@ static const GWL_RegistryHandler gwl_registry_handlers[] = {
|
||||
/*update_fn*/ nullptr,
|
||||
/*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. */
|
||||
{
|
||||
/*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
|
||||
* \{ */
|
||||
@@ -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->keyboard.wl.surface_window);
|
||||
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
|
||||
|
||||
|
||||
@@ -236,6 +236,10 @@ class GHOST_SystemWayland : public GHOST_System {
|
||||
|
||||
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();
|
||||
|
||||
/* WAYLAND utility functions. */
|
||||
|
||||
@@ -1742,7 +1742,9 @@ GHOST_TCapabilityFlag GHOST_SystemX11::getCapabilities() const
|
||||
/* No support yet for desktop sampling. */
|
||||
GHOST_kCapabilityDesktopSample |
|
||||
/* 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -140,6 +140,11 @@ class GHOST_WindowWayland : public GHOST_Window {
|
||||
void setOpaque() const;
|
||||
#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. */
|
||||
|
||||
int scale_get() const;
|
||||
|
||||
@@ -3604,7 +3604,11 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
|
||||
data->undo_stack_text = nullptr;
|
||||
|
||||
#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);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -172,6 +172,8 @@ enum eWM_CapabilitiesFlag {
|
||||
WM_CAPABILITY_CLIPBOARD_IMAGES = (1 << 4),
|
||||
/** Ability to sample a color outside of Blender windows. */
|
||||
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. */
|
||||
WM_CAPABILITY_INITIALIZED = (1 << 31),
|
||||
};
|
||||
|
||||
@@ -1977,6 +1977,9 @@ eWM_CapabilitiesFlag WM_capabilities_flag()
|
||||
if (ghost_flag & GHOST_kCapabilityDesktopSample) {
|
||||
flag |= WM_CAPABILITY_DESKTOP_SAMPLE;
|
||||
}
|
||||
if (ghost_flag & GHOST_kCapabilityInputIME) {
|
||||
flag |= WM_CAPABILITY_INPUT_IME;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
BLI_assert(win);
|
||||
if ((WM_capabilities_flag() & WM_CAPABILITY_INPUT_IME) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Convert to native OS window coordinates. */
|
||||
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)
|
||||
{
|
||||
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));
|
||||
win->ime_data = nullptr;
|
||||
win->ime_data_is_composing = false;
|
||||
|
||||
Reference in New Issue
Block a user