diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index 4ae956d50b..093069a2b5 100644 --- a/panda/src/display/config_display.cxx +++ b/panda/src/display/config_display.cxx @@ -383,6 +383,11 @@ ConfigVariableFilename subprocess_window "and is not used or needed in other environments. See " "WindowProperties::set_subprocess_window().")); +ConfigVariableBool ime_aware +("ime-aware", false, + PRC_DESC("Set this true to show candidate strings in Panda3D rather than via " + "an OS-provided external popup window.")); + ConfigVariableString framebuffer_mode ("framebuffer-mode", "", PRC_DESC("No longer has any effect. Do not use.")); diff --git a/panda/src/display/config_display.h b/panda/src/display/config_display.h index c06d284674..8560251f1b 100644 --- a/panda/src/display/config_display.h +++ b/panda/src/display/config_display.h @@ -84,6 +84,7 @@ extern EXPCL_PANDA_DISPLAY ConfigVariableString window_title; extern EXPCL_PANDA_DISPLAY ConfigVariableInt parent_window_handle; extern EXPCL_PANDA_DISPLAY ConfigVariableBool win_unexposed_draw; extern EXPCL_PANDA_DISPLAY ConfigVariableFilename subprocess_window; +extern EXPCL_PANDA_DISPLAY ConfigVariableBool ime_aware; extern EXPCL_PANDA_DISPLAY ConfigVariableString framebuffer_mode; extern EXPCL_PANDA_DISPLAY ConfigVariableBool framebuffer_hardware; diff --git a/panda/src/windisplay/config_windisplay.cxx b/panda/src/windisplay/config_windisplay.cxx index ca3dc64f72..6d655d905b 100644 --- a/panda/src/windisplay/config_windisplay.cxx +++ b/panda/src/windisplay/config_windisplay.cxx @@ -49,12 +49,6 @@ ConfigVariableBool auto_cpu_data "require an explicit call to pipe->lookup_cpu_data(). Setting this " "true may slow down startup time by 1-2 seconds.")); -ConfigVariableBool ime_aware -("ime-aware", false, - PRC_DESC("Set this true to show ime texts on the chat panel and hide the " - "IME default windows. This is a mechanism to work around DX8/9 " - "interface.")); - ConfigVariableBool ime_hide ("ime-hide", false, PRC_DESC("Set this true to hide ime windows.")); diff --git a/panda/src/windisplay/config_windisplay.h b/panda/src/windisplay/config_windisplay.h index ae6568ec11..377b272472 100644 --- a/panda/src/windisplay/config_windisplay.h +++ b/panda/src/windisplay/config_windisplay.h @@ -25,8 +25,6 @@ extern ConfigVariableBool responsive_minimized_fullscreen_window; extern ConfigVariableBool hold_keys_across_windows; extern ConfigVariableBool do_vidmemsize_check; extern ConfigVariableBool auto_cpu_data; -extern ConfigVariableBool ime_composition_w; -extern ConfigVariableBool ime_aware; extern ConfigVariableBool ime_hide; extern ConfigVariableBool request_dxdisplay_information; extern ConfigVariableBool dpi_aware; diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 871d3f49de..8d28847b39 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -493,7 +493,8 @@ process_events() { break; case ClientMessage: - if ((Atom)(event.xclient.data.l[0]) == _wm_delete_window) { + if ((Atom)(event.xclient.data.l[0]) == _wm_delete_window && + event.xany.window == _xwindow) { // This is a message from the window manager indicating that the user // has requested to close the window. string close_request_event = get_close_request_event(); @@ -1182,15 +1183,41 @@ open_window() { set_wm_properties(_properties, false); - // We don't specify any fancy properties of the XIC. It would be nicer if - // we could support fancy IM's that want preedit callbacks, etc., but that - // can wait until we have an X server that actually supports these to test - // it on. + // Initialize the input context, which (if enabled) will enable us to capture + // candidate strings and display them inside Panda3D rather than via an + // external popup window. XIM im = x11_pipe->get_im(); _ic = nullptr; if (im) { - _ic = XCreateIC(im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, _xwindow, nullptr); + if (ime_aware) { + XIMCallback start_callback; + start_callback.client_data = (XPointer)this; + start_callback.callback = (XIMProc)xim_preedit_start; + XIMCallback draw_callback; + draw_callback.client_data = (XPointer)this; + draw_callback.callback = (XIMProc)xim_preedit_draw; + XIMCallback caret_callback; + caret_callback.client_data = (XPointer)this; + caret_callback.callback = (XIMProc)xim_preedit_caret; + XIMCallback done_callback; + done_callback.client_data = (XPointer)this; + done_callback.callback = (XIMProc)xim_preedit_done; + XVaNestedList preedit_attributes = XVaCreateNestedList( + 0, + XNPreeditStartCallback, &start_callback, + XNPreeditDrawCallback, &draw_callback, + XNPreeditCaretCallback, &caret_callback, + XNPreeditDoneCallback, &done_callback, + nullptr); + _ic = XCreateIC(im, + XNInputStyle, XIMPreeditCallbacks | XIMStatusNothing, + XNClientWindow, _xwindow, + XNPreeditAttributes, preedit_attributes, + nullptr); + } else { + _ic = XCreateIC(im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, _xwindow, nullptr); + } if (_ic == (XIC)nullptr) { x11display_cat.warning() << "Couldn't create input context.\n"; @@ -1549,6 +1576,123 @@ open_raw_mice() { #endif } +/** + * + */ +int x11GraphicsWindow:: +handle_preedit_start() { + _preedit_state = new PreeditState; + + if (x11display_cat.is_spam()) { + x11display_cat.spam() + << "Preedit started\n"; + } + + return sizeof(_preedit_state->_buffer) / sizeof(wchar_t); +} + +/** + * + */ +void x11GraphicsWindow:: +handle_preedit_draw(XIMPreeditDrawCallbackStruct &data) { + nassertv_always(_preedit_state != nullptr); + PreeditState &state = *_preedit_state; + + if (data.text != nullptr) { + // Replace characters in the preedit buffer. + int added_chars = data.text->length - data.chg_length; + memmove(state._buffer + data.chg_first, + state._buffer + data.chg_first + data.chg_length, + state._length - (size_t)(data.chg_first + data.chg_length) + data.text->length); + state._length += added_chars; + + if (added_chars != 0) { + if (state._highlight_start > data.chg_first) { + state._highlight_start = std::max(data.chg_first, state._highlight_start + added_chars); + } + if (state._highlight_end > data.chg_first) { + state._highlight_end = std::max(data.chg_first, state._highlight_end + added_chars); + } + } + + if (data.text->encoding_is_wchar) { + memcpy(state._buffer + data.chg_first, data.text->string.wide_char, data.text->length * sizeof(wchar_t)); + } else { + mbstowcs(state._buffer + data.chg_first, data.text->string.multi_byte, data.text->length); + } + + if (data.text->feedback != nullptr) { + // Update the highlighted region. + for (int i = 0; i < data.text->length; ++i) { + if (data.text->feedback[i] & XIMReverse) { + if (state._highlight_end > state._highlight_start) { + state._highlight_start = std::min(state._highlight_start, data.chg_first + i); + state._highlight_end = std::max(state._highlight_end, data.chg_first + i + 1); + } else { + state._highlight_start = data.chg_first + i; + state._highlight_end = data.chg_first + i + 1; + } + } + else if (state._highlight_end > state._highlight_start) { + if (state._highlight_start == data.chg_first + i) { + ++state._highlight_start; + } + if (state._highlight_end == data.chg_first + i + 1) { + --state._highlight_end; + } + if (state._highlight_end <= state._highlight_start) { + state._highlight_start = 0; + state._highlight_end = 0; + } + } + } + } + } else { + // Delete characters from the preedit buffer. + memmove(state._buffer + data.chg_first, + state._buffer + data.chg_first + data.chg_length, + state._length - (size_t)(data.chg_first + data.chg_length)); + state._length -= data.chg_length; + + if (state._highlight_start > data.chg_first) { + state._highlight_start = std::max(data.chg_first, state._highlight_start - data.chg_length); + } + if (state._highlight_end > data.chg_first) { + state._highlight_end = std::max(data.chg_first, state._highlight_end - data.chg_length); + } + } + _input->candidate(std::wstring(state._buffer, state._length), + state._highlight_start, state._highlight_end, data.caret); +} + +/** + * + */ +void x11GraphicsWindow:: +handle_preedit_caret(XIMPreeditCaretCallbackStruct &data) { + nassertv_always(_preedit_state != nullptr); + PreeditState &state = *_preedit_state; + + if (data.direction == XIMAbsolutePosition) { + _input->candidate(std::wstring(state._buffer, state._length), + state._highlight_start, state._highlight_end, data.position); + } +} + +/** + * + */ +void x11GraphicsWindow:: +handle_preedit_done() { + if (x11display_cat.is_spam()) { + x11display_cat.spam() + << "Preedit done\n"; + } + delete _preedit_state; + _preedit_state = nullptr; +} + /** * Generates a keystroke corresponding to the indicated X KeyPress event. */ @@ -2194,7 +2338,8 @@ check_event(X11_Display *display, XEvent *event, char *arg) { // We accept any event that is sent to our window. However, we have to let // raw mouse events through, since they're not associated with any window. return (event->xany.window == self->_xwindow || - (event->type == GenericEvent && self->_raw_mouse_enabled)); + (event->type == GenericEvent && self->_raw_mouse_enabled)) || + (event->type == ClientMessage); } /** @@ -2510,3 +2655,43 @@ cleanup: return ret; } + +/** + * + */ +int x11GraphicsWindow:: +xim_preedit_start(XIC ic, XPointer client_data, XPointer call_data) { + x11GraphicsWindow *window = (x11GraphicsWindow *)client_data; + return window->handle_preedit_start(); +} + +/** + * + */ +void x11GraphicsWindow:: +xim_preedit_draw(XIC ic, XPointer client_data, + XIMPreeditDrawCallbackStruct *call_data) { + x11GraphicsWindow *window = (x11GraphicsWindow *)client_data; + nassertv_always(call_data != nullptr); + window->handle_preedit_draw(*call_data); +} + +/** + * + */ +void x11GraphicsWindow:: +xim_preedit_caret(XIC ic, XPointer client_data, + XIMPreeditCaretCallbackStruct *call_data) { + x11GraphicsWindow *window = (x11GraphicsWindow *)client_data; + nassertv_always(call_data != nullptr); + window->handle_preedit_caret(*call_data); +} + +/** + * + */ +void x11GraphicsWindow:: +xim_preedit_done(XIC ic, XPointer client_data, XPointer call_data) { + x11GraphicsWindow *window = (x11GraphicsWindow *)client_data; + window->handle_preedit_done(); +} diff --git a/panda/src/x11display/x11GraphicsWindow.h b/panda/src/x11display/x11GraphicsWindow.h index e8787ae574..0744ebe143 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -54,6 +54,10 @@ protected: bool already_mapped); virtual void setup_colormap(XVisualInfo *visual); + int handle_preedit_start(); + void handle_preedit_draw(XIMPreeditDrawCallbackStruct &data); + void handle_preedit_caret(XIMPreeditCaretCallbackStruct &data); + void handle_preedit_done(); void handle_keystroke(XKeyEvent &event); void handle_keypress(XKeyEvent &event); void handle_keyrelease(XKeyEvent &event); @@ -72,6 +76,11 @@ private: X11_Cursor get_cursor(const Filename &filename); X11_Cursor read_ico(std::istream &ico); + static int xim_preedit_start(XIC ic, XPointer client_data, XPointer call_data); + static void xim_preedit_draw(XIC ic, XPointer client_data, XIMPreeditDrawCallbackStruct *call_data); + static void xim_preedit_caret(XIC ic, XPointer client_data, XIMPreeditCaretCallbackStruct *call_data); + static void xim_preedit_done(XIC ic, XPointer client_data, XPointer call_data); + protected: X11_Display *_display; int _screen; @@ -85,6 +94,13 @@ protected: LVecBase2i _fixed_size; GraphicsWindowInputDevice *_input; + struct PreeditState { + wchar_t _buffer[1024]; + size_t _length = 0; + int _highlight_start = 0; + int _highlight_end = 0; + }; + PreeditState *_preedit_state = nullptr; long _event_mask; bool _awaiting_configure;