Fix cannot input Chinese after restarting the input method on X11

Register a callback via `XRegisterIMInstantiateCallback()` to detect
whether the input method is available again.
This commit is contained in:
风青山
2025-03-07 15:36:01 +08:00
parent f2cc3f1275
commit 456261cd7e
2 changed files with 125 additions and 104 deletions

View File

@ -4143,7 +4143,6 @@ void DisplayServerX11::_xim_preedit_caret_callback(::XIM xim, ::XPointer client_
void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data,
::XPointer call_data) {
WARN_PRINT("Input method stopped");
DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);
ds->xim = nullptr;
@ -6047,6 +6046,73 @@ DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, W
return ds;
}
void DisplayServerX11::_create_xic(WindowData &wd) {
if (xim && xim_style) {
// Block events polling while changing input focus
// because it triggers some event polling internally.
MutexLock mutex_lock(events_mutex);
// Force on-the-spot for the over-the-spot style.
if ((xim_style & XIMPreeditPosition) != 0) {
xim_style &= ~XIMPreeditPosition;
xim_style |= XIMPreeditCallbacks;
}
if ((xim_style & XIMPreeditCallbacks) != 0) {
::XIMCallback preedit_start_callback;
preedit_start_callback.client_data = (::XPointer)(this);
preedit_start_callback.callback = (::XIMProc)(void *)(_xim_preedit_start_callback);
::XIMCallback preedit_done_callback;
preedit_done_callback.client_data = (::XPointer)(this);
preedit_done_callback.callback = (::XIMProc)(_xim_preedit_done_callback);
::XIMCallback preedit_draw_callback;
preedit_draw_callback.client_data = (::XPointer)(this);
preedit_draw_callback.callback = (::XIMProc)(_xim_preedit_draw_callback);
::XIMCallback preedit_caret_callback;
preedit_caret_callback.client_data = (::XPointer)(this);
preedit_caret_callback.callback = (::XIMProc)(_xim_preedit_caret_callback);
::XVaNestedList preedit_attributes = XVaCreateNestedList(0,
XNPreeditStartCallback, &preedit_start_callback,
XNPreeditDoneCallback, &preedit_done_callback,
XNPreeditDrawCallback, &preedit_draw_callback,
XNPreeditCaretCallback, &preedit_caret_callback,
(char *)nullptr);
wd.xic = XCreateIC(xim,
XNInputStyle, xim_style,
XNClientWindow, wd.x11_xim_window,
XNFocusWindow, wd.x11_xim_window,
XNPreeditAttributes, preedit_attributes,
(char *)nullptr);
XFree(preedit_attributes);
} else {
wd.xic = XCreateIC(xim,
XNInputStyle, xim_style,
XNClientWindow, wd.x11_xim_window,
XNFocusWindow, wd.x11_xim_window,
(char *)nullptr);
}
long im_event_mask = 0;
if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {
WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value.");
XDestroyIC(wd.xic);
wd.xic = nullptr;
}
if (wd.xic) {
XUnsetICFocus(wd.xic);
} else {
WARN_PRINT("XCreateIC couldn't create wd.xic.");
}
} else {
wd.xic = nullptr;
WARN_PRINT("XCreateIC couldn't create wd.xic.");
}
}
DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, Window p_parent_window) {
//Create window
@ -6252,70 +6318,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);
}
if (xim && xim_style) {
// Block events polling while changing input focus
// because it triggers some event polling internally.
MutexLock mutex_lock(events_mutex);
// Force on-the-spot for the over-the-spot style.
if ((xim_style & XIMPreeditPosition) != 0) {
xim_style &= ~XIMPreeditPosition;
xim_style |= XIMPreeditCallbacks;
}
if ((xim_style & XIMPreeditCallbacks) != 0) {
::XIMCallback preedit_start_callback;
preedit_start_callback.client_data = (::XPointer)(this);
preedit_start_callback.callback = (::XIMProc)(void *)(_xim_preedit_start_callback);
::XIMCallback preedit_done_callback;
preedit_done_callback.client_data = (::XPointer)(this);
preedit_done_callback.callback = (::XIMProc)(_xim_preedit_done_callback);
::XIMCallback preedit_draw_callback;
preedit_draw_callback.client_data = (::XPointer)(this);
preedit_draw_callback.callback = (::XIMProc)(_xim_preedit_draw_callback);
::XIMCallback preedit_caret_callback;
preedit_caret_callback.client_data = (::XPointer)(this);
preedit_caret_callback.callback = (::XIMProc)(_xim_preedit_caret_callback);
::XVaNestedList preedit_attributes = XVaCreateNestedList(0,
XNPreeditStartCallback, &preedit_start_callback,
XNPreeditDoneCallback, &preedit_done_callback,
XNPreeditDrawCallback, &preedit_draw_callback,
XNPreeditCaretCallback, &preedit_caret_callback,
(char *)nullptr);
wd.xic = XCreateIC(xim,
XNInputStyle, xim_style,
XNClientWindow, wd.x11_xim_window,
XNFocusWindow, wd.x11_xim_window,
XNPreeditAttributes, preedit_attributes,
(char *)nullptr);
XFree(preedit_attributes);
} else {
wd.xic = XCreateIC(xim,
XNInputStyle, xim_style,
XNClientWindow, wd.x11_xim_window,
XNFocusWindow, wd.x11_xim_window,
(char *)nullptr);
}
if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {
WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");
XDestroyIC(wd.xic);
wd.xic = nullptr;
}
if (wd.xic) {
XUnsetICFocus(wd.xic);
} else {
WARN_PRINT("XCreateIC couldn't create wd.xic");
}
} else {
wd.xic = nullptr;
WARN_PRINT("XCreateIC couldn't create wd.xic");
}
_create_xic(wd);
_update_context(wd);
if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {
@ -6471,6 +6474,54 @@ static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMSt
return p_style_a;
}
void DisplayServerX11::_xim_instantiate_callback(::Display *display, ::XPointer client_data,
::XPointer call_data) {
DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data);
ds->xim = XOpenIM(display, nullptr, nullptr, nullptr);
if (ds->xim == nullptr) {
WARN_PRINT("XOpenIM failed.");
ds->xim_style = 0L;
} else {
::XIMCallback im_destroy_callback;
im_destroy_callback.client_data = client_data;
im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback);
if (XSetIMValues(ds->xim, XNDestroyCallback, &im_destroy_callback,
nullptr) != nullptr) {
WARN_PRINT("Error setting XIM destroy callback.");
}
::XIMStyles *xim_styles = nullptr;
ds->xim_style = 0L;
char *imvalret = XGetIMValues(ds->xim, XNQueryInputStyle, &xim_styles, nullptr);
if (imvalret != nullptr || xim_styles == nullptr) {
fprintf(stderr, "Input method doesn't support any styles\n");
}
if (xim_styles) {
ds->xim_style = 0L;
for (int i = 0; i < xim_styles->count_styles; i++) {
const ::XIMStyle &style = xim_styles->supported_styles[i];
if (!_is_xim_style_supported(style)) {
continue;
}
ds->xim_style = _get_best_xim_style(ds->xim_style, style);
}
XFree(xim_styles);
}
XFree(imvalret);
}
// The input method has been (re)started.
for (KeyValue<WindowID, WindowData> &E : ds->windows) {
ds->_create_xic(E.value);
}
}
DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
KeyMappingX11::initialize();
@ -6663,10 +6714,10 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
if (modifiers == nullptr) {
if (OS::get_singleton()->is_stdout_verbose()) {
WARN_PRINT("IME is disabled");
WARN_PRINT("IME is disabled.");
}
XSetLocaleModifiers("@im=none");
WARN_PRINT("Error setting locale modifiers");
WARN_PRINT("Error setting locale modifiers.");
}
const char *err;
@ -6711,42 +6762,9 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
return;
}
xim = XOpenIM(x11_display, nullptr, nullptr, nullptr);
if (xim == nullptr) {
WARN_PRINT("XOpenIM failed");
xim_style = 0L;
} else {
::XIMCallback im_destroy_callback;
im_destroy_callback.client_data = (::XPointer)(this);
im_destroy_callback.callback = (::XIMProc)(_xim_destroy_callback);
if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback,
nullptr) != nullptr) {
WARN_PRINT("Error setting XIM destroy callback");
}
::XIMStyles *xim_styles = nullptr;
xim_style = 0L;
char *imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, nullptr);
if (imvalret != nullptr || xim_styles == nullptr) {
fprintf(stderr, "Input method doesn't support any styles\n");
}
if (xim_styles) {
xim_style = 0L;
for (int i = 0; i < xim_styles->count_styles; i++) {
const ::XIMStyle &style = xim_styles->supported_styles[i];
if (!_is_xim_style_supported(style)) {
continue;
}
xim_style = _get_best_xim_style(xim_style, style);
}
XFree(xim_styles);
}
XFree(imvalret);
if (!XRegisterIMInstantiateCallback(x11_display, nullptr, nullptr, nullptr, (::XIDProc)(_xim_instantiate_callback), (::XPointer)this)) {
WARN_PRINT("Error registering XIM instantiate callback.");
_xim_instantiate_callback(x11_display, (::XPointer)this, nullptr);
}
/* Atom internment */

View File

@ -236,6 +236,7 @@ class DisplayServerX11 : public DisplayServer {
WindowID last_focused_window = INVALID_WINDOW_ID;
WindowID window_id_counter = MAIN_WINDOW_ID;
void _create_xic(WindowData &wd);
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, Window p_parent_window);
String internal_clipboard;
@ -246,7 +247,7 @@ class DisplayServerX11 : public DisplayServer {
int xmblen = 0;
unsigned long last_timestamp = 0;
::Time last_keyrelease_time = 0;
::XIM xim;
::XIM xim = nullptr;
::XIMStyle xim_style;
static int _xim_preedit_start_callback(::XIM xim, ::XPointer client_data,
@ -259,6 +260,8 @@ class DisplayServerX11 : public DisplayServer {
::XIMPreeditCaretCallbackStruct *call_data);
static void _xim_destroy_callback(::XIM im, ::XPointer client_data,
::XPointer call_data);
static void _xim_instantiate_callback(::Display *display, ::XPointer client_data,
::XPointer call_data);
Point2i last_mouse_pos;
bool last_mouse_pos_valid = false;