Browse Source

x11: Implement ime-aware setting using XIM

rdb 4 years ago
parent
commit
4cd579d0ba

+ 5 - 0
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."));

+ 1 - 0
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;

+ 0 - 6
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."));

+ 0 - 2
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;

+ 193 - 8
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();
+}

+ 16 - 0
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;