x11: Implement ime-aware setting using XIM

This commit is contained in:
rdb 2021-07-05 17:51:01 +02:00
parent 4ba3df7082
commit 4cd579d0ba
6 changed files with 215 additions and 16 deletions

View File

@ -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."));

View File

@ -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;

View File

@ -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."));

View File

@ -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;

View File

@ -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();
}

View File

@ -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;