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()
|
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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user