emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master d76fb0c11e: Allow using GTK+ to handle input methods on X


From: Po Lu
Subject: master d76fb0c11e: Allow using GTK+ to handle input methods on X
Date: Sat, 8 Jan 2022 02:26:18 -0500 (EST)

branch: master
commit d76fb0c11e9859db0d03d6496f5a720d304f4ca9
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    Allow using GTK+ to handle input methods on X
    
    * doc/emacs/xresources.texi (Table of Resources): Document new
    value of `inputStyle'.
    * etc/NEWS: Announce new option.
    
    * lisp/cus-start.el (standard): Add `x-gtk-use-native-input'.
    * src/gtkutil.c (xg_mark_data): Mark xg_pending_quit_event.
    (xg_add_virtual_mods):
    (xg_im_context_commit):
    (xg_im_context_preedit_changed):
    (xg_im_context_preedit_end):
    (xg_widget_key_press_event_cb):
    (xg_filter_key): New functions.
    * src/gtkutil.h: Add prototype for `xg_filter_key'.
    * src/xfns.c (xic_set_preeditarea): Set cursor location for the
    GTK IM context as well.
    * src/xterm.c (xg_pending_quit_event): New variable.
    (x_focus_changed): Set focus on the GTK input context as well.
    (x_filter_event): Filter events through GTK if the user asked
    for it.
    (handle_one_xevent): Likewise.
    (XTread_socket): Set hold_quit to xg_pending_quit_event if it
    exists.
    (x_draw_window_cursor): Always set preedit area even if XIC
    doesn't exist.
    * src/xterm.h (struct x_display_info): New field
    `prefer_native_input'.
    (struct x_output): New field `im_context'.
---
 doc/emacs/xresources.texi |   4 +
 etc/NEWS                  |   5 +
 lisp/cus-start.el         |   1 +
 src/gtkutil.c             | 354 ++++++++++++++++++++++++++++++++++++++++++++++
 src/gtkutil.h             |   2 +
 src/xfns.c                |  37 +++--
 src/xterm.c               | 114 ++++++++++++++-
 src/xterm.h               |  10 ++
 8 files changed, 510 insertions(+), 17 deletions(-)

diff --git a/doc/emacs/xresources.texi b/doc/emacs/xresources.texi
index c5dc4e8150..a07c14fda9 100644
--- a/doc/emacs/xresources.texi
+++ b/doc/emacs/xresources.texi
@@ -355,6 +355,10 @@ Let the input method decide how to display itself.  This 
is usually
 equivalent to @samp{overthespot}, but it might work with more input
 methods.
 
+@item native
+Use the toolkit for handling input methods.  This is currently
+implemented only on GTK.
+
 @item root
 Use some location on display specific to the input method for
 displaying the preview text.
diff --git a/etc/NEWS b/etc/NEWS
index ea08f7f3de..fda2a3946f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -149,6 +149,11 @@ and pop-up menus.
 This controls the style of the pre-edit and status areas of X input
 methods.
 
+---
+** New user option 'x-gtk-use-native-input'.
+This controls whether or not GTK input methods are used by Emacs,
+instead of XIM input methods.
+
 ---
 ** New minor mode 'pixel-scroll-precision-mode'.
 When enabled, and if your mouse supports it, you can scroll the
diff --git a/lisp/cus-start.el b/lisp/cus-start.el
index 667e36b211..4227cec425 100644
--- a/lisp/cus-start.el
+++ b/lisp/cus-start.el
@@ -829,6 +829,7 @@ since it could result in memory overflow and make Emacs 
crash."
             (x-stretch-cursor display boolean "21.1")
             (scroll-bar-adjust-thumb-portion windows boolean "24.4")
              (x-scroll-event-delta-factor mouse float "29.1")
+             (x-gtk-use-native-input keyboard boolean "29.1")
             ;; xselect.c
             (x-select-enable-clipboard-manager killing boolean "24.1")
             ;; xsettings.c
diff --git a/src/gtkutil.c b/src/gtkutil.c
index 93f51d7796..4c516a4479 100644
--- a/src/gtkutil.c
+++ b/src/gtkutil.c
@@ -76,6 +76,13 @@ typedef struct pgtk_output xp_output;
 #define XG_TEXT_OPEN   GTK_STOCK_OPEN
 #endif
 
+#ifndef HAVE_PGTK
+static void xg_im_context_commit (GtkIMContext *, gchar *, gpointer);
+static void xg_im_context_preedit_changed (GtkIMContext *, gpointer);
+static void xg_im_context_preedit_end (GtkIMContext *, gpointer);
+static bool xg_widget_key_press_event_cb (GtkWidget *, GdkEvent *, gpointer);
+#endif
+
 #ifndef HAVE_GTK3
 
 #ifdef HAVE_FREETYPE
@@ -1436,6 +1443,9 @@ xg_create_frame_widgets (struct frame *f)
   GtkWidget *wfixed;
 #ifndef HAVE_GTK3
   GtkRcStyle *style;
+#endif
+#ifndef HAVE_PGTK
+  GtkIMContext *imc;
 #endif
   GtkWindowType type = GTK_WINDOW_TOPLEVEL;
   char *title = 0;
@@ -1621,6 +1631,22 @@ xg_create_frame_widgets (struct frame *f)
 #ifndef HAVE_PGTK
   gtk_widget_set_tooltip_text (wtop, "Dummy text");
   g_signal_connect (wtop, "query-tooltip", G_CALLBACK (qttip_cb), f);
+
+  imc = gtk_im_multicontext_new ();
+  g_object_ref (imc);
+  gtk_im_context_set_use_preedit (imc, TRUE);
+
+  g_signal_connect (G_OBJECT (imc), "commit",
+                   G_CALLBACK (xg_im_context_commit), f);
+  g_signal_connect (G_OBJECT (imc), "preedit-changed",
+                   G_CALLBACK (xg_im_context_preedit_changed), NULL);
+  g_signal_connect (G_OBJECT (imc), "preedit-end",
+                   G_CALLBACK (xg_im_context_preedit_end), NULL);
+  FRAME_X_OUTPUT (f)->im_context = imc;
+
+  g_signal_connect (G_OBJECT (wfixed), "key-press-event",
+                   G_CALLBACK (xg_widget_key_press_event_cb),
+                   NULL);
 #endif
 
   {
@@ -1761,6 +1787,7 @@ xg_free_frame_widgets (struct frame *f)
       /* x_free_frame_resources should have taken care of it */
 #ifndef HAVE_PGTK
       eassert (!FRAME_X_DOUBLE_BUFFERED_P (f));
+      g_object_unref (FRAME_X_OUTPUT (f)->im_context);
 #endif
       gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f));
       FRAME_X_WINDOW (f) = 0; /* Set to avoid XDestroyWindow in xterm.c */
@@ -2928,6 +2955,14 @@ xg_mark_data (void)
             }
         }
     }
+
+  if (xg_pending_quit_event.kind != NO_EVENT)
+    {
+      eassert (xg_pending_quit_event.kind == ASCII_KEYSTROKE_EVENT);
+
+      mark_object (xg_pending_quit_event.frame_or_window);
+      mark_object (xg_pending_quit_event.arg);
+    }
 }
 
 /* Callback called when a menu item is destroyed.  Used to free data.
@@ -5963,4 +5998,323 @@ xg_initialize (void)
 #endif
 }
 
+#ifndef HAVE_PGTK
+static void
+xg_add_virtual_mods (struct x_display_info *dpyinfo, GdkEventKey *key)
+{
+  guint modifiers = key->state;
+
+  if (modifiers & dpyinfo->meta_mod_mask)
+    {
+      /* GDK always assumes Mod1 is alt, but that's no reason for
+        us to make that mistake as well.  */
+      if (!dpyinfo->alt_mod_mask)
+       key->state |= GDK_MOD1_MASK;
+      else
+       key->state |= GDK_META_MASK;
+    }
+
+  if (modifiers & dpyinfo->alt_mod_mask)
+    key->state |= GDK_MOD1_MASK;
+  if (modifiers & dpyinfo->super_mod_mask)
+    key->state |= GDK_SUPER_MASK;
+  if (modifiers & dpyinfo->hyper_mod_mask)
+    key->state |= GDK_HYPER_MASK;
+}
+
+static void
+xg_im_context_commit (GtkIMContext *imc, gchar *str,
+                     gpointer user_data)
+{
+  struct frame *f = user_data;
+  struct input_event ie;
+  gunichar *ucs4_str;
+
+  ucs4_str = g_utf8_to_ucs4_fast (str, -1, NULL);
+
+  if (!ucs4_str)
+    return;
+
+  for (gunichar *c = ucs4_str; *c; c++)
+    {
+      EVENT_INIT (ie);
+      ie.kind = (SINGLE_BYTE_CHAR_P (*c)
+                ? ASCII_KEYSTROKE_EVENT
+                : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+      ie.arg = Qnil;
+      ie.code = *c;
+      XSETFRAME (ie.frame_or_window, f);
+      ie.modifiers = 0;
+      ie.timestamp = 0;
+
+      kbd_buffer_store_event (&ie);
+    }
+
+  g_free (ucs4_str);
+}
+
+static void
+xg_im_context_preedit_changed (GtkIMContext *imc, gpointer user_data)
+{
+  PangoAttrList *list;
+  gchar *str;
+  gint cursor;
+  struct input_event inev;
+
+  gtk_im_context_get_preedit_string (imc, &str, &list, &cursor);
+
+  EVENT_INIT (inev);
+  inev.kind = PREEDIT_TEXT_EVENT;
+  inev.arg = build_string_from_utf8 (str);
+  kbd_buffer_store_event (&inev);
+
+  g_free (str);
+  pango_attr_list_unref (list);
+}
+
+static void
+xg_im_context_preedit_end (GtkIMContext *imc, gpointer user_data)
+{
+  struct input_event inev;
+
+  EVENT_INIT (inev);
+  inev.kind = PREEDIT_TEXT_EVENT;
+  inev.arg = Qnil;
+  kbd_buffer_store_event (&inev);
+}
+
+static bool
+xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event,
+                             gpointer user_data)
+{
+  Lisp_Object tail, tem;
+  struct frame *f = NULL;
+  union buffered_input_event inev;
+  guint keysym = event->key.keyval;
+  gunichar *cb;
+  ptrdiff_t i;
+  glong len;
+
+  FOR_EACH_FRAME (tail, tem)
+    {
+      if (FRAME_X_P (XFRAME (tem))
+         && (FRAME_GTK_WIDGET (XFRAME (tem)) == widget))
+       {
+         f = XFRAME (tem);
+         break;
+       }
+    }
+
+  if (!f)
+    return true;
+
+  if (!x_gtk_use_native_input
+      && !FRAME_DISPLAY_INFO (f)->prefer_native_input)
+    return true;
+
+  EVENT_INIT (inev.ie);
+  XSETFRAME (inev.ie.frame_or_window, f);
+
+  inev.ie.modifiers |= x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f),
+                                              event->key.state);
+
+  /* First deal with keysyms which have defined
+     translations to characters.  */
+  if (keysym >= 32 && keysym < 128)
+    /* Avoid explicitly decoding each ASCII character.  */
+    {
+      inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+      inev.ie.code = keysym;
+      goto done;
+    }
+
+  /* Keysyms directly mapped to Unicode characters.  */
+  if (keysym >= 0x01000000 && keysym <= 0x0110FFFF)
+    {
+      if (keysym < 0x01000080)
+       inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+      else
+       inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
+      inev.ie.code = keysym & 0xFFFFFF;
+      goto done;
+    }
+
+  /* Random non-modifier sorts of keysyms.  */
+  if (((keysym >= GDK_KEY_BackSpace && keysym <= GDK_KEY_Escape)
+       || keysym == GDK_KEY_Delete
+#ifdef GDK_KEY_ISO_Left_Tab
+       || (keysym >= GDK_KEY_ISO_Left_Tab && keysym <= GDK_KEY_ISO_Enter)
+#endif
+       || IsCursorKey (keysym) /* 0xff50 <= x < 0xff60 */
+       || IsMiscFunctionKey (keysym)   /* 0xff60 <= x < VARIES */
+#ifdef GDK_KEY_dead_circumflex
+       || keysym == GDK_KEY_dead_circumflex
+#endif
+#ifdef GDK_KEY_dead_grave
+       || keysym == GDK_KEY_dead_grave
+#endif
+#ifdef GDK_KEY_dead_tilde
+       || keysym == GDK_KEY_dead_tilde
+#endif
+#ifdef GDK_KEY_dead_diaeresis
+       || keysym == GDK_KEY_dead_diaeresis
+#endif
+#ifdef GDK_KEY_dead_macron
+       || keysym == GDK_KEY_dead_macron
+#endif
+#ifdef GDK_KEY_dead_degree
+       || keysym == GDK_KEY_dead_degree
+#endif
+#ifdef GDK_KEY_dead_acute
+       || keysym == GDK_KEY_dead_acute
+#endif
+#ifdef GDK_KEY_dead_cedilla
+       || keysym == GDK_KEY_dead_cedilla
+#endif
+#ifdef GDK_KEY_dead_breve
+       || keysym == GDK_KEY_dead_breve
+#endif
+#ifdef GDK_KEY_dead_ogonek
+       || keysym == GDK_KEY_dead_ogonek
+#endif
+#ifdef GDK_KEY_dead_caron
+       || keysym == GDK_KEY_dead_caron
+#endif
+#ifdef GDK_KEY_dead_doubleacute
+       || keysym == GDK_KEY_dead_doubleacute
+#endif
+#ifdef GDK_KEY_dead_abovedot
+       || keysym == GDK_KEY_dead_abovedot
+#endif
+       || IsKeypadKey (keysym) /* 0xff80 <= x < 0xffbe */
+       || IsFunctionKey (keysym)       /* 0xffbe <= x < 0xffe1 */
+       /* Any "vendor-specific" key is ok.  */
+       || (keysym & (1 << 28))
+       || (keysym != GDK_KEY_VoidSymbol && !event->key.string))
+      && !(event->key.is_modifier))
+    {
+      inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+      inev.ie.code = keysym;
+      goto done;
+    }
+
+  if (event->key.string)
+    {
+      cb = g_utf8_to_ucs4_fast (event->key.string, -1, &len);
+
+      for (i = 0; i < len; ++i)
+       {
+         inev.ie.kind = (SINGLE_BYTE_CHAR_P (cb[i])
+                         ? ASCII_KEYSTROKE_EVENT
+                         : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+         inev.ie.code = cb[i];
+
+         kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event);
+       }
+
+      g_free (cb);
+
+      inev.ie.kind = NO_EVENT;
+    }
+
+ done:
+  if (inev.ie.kind != NO_EVENT)
+    {
+      xg_pending_quit_event.kind = NO_EVENT;
+      kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event);
+    }
+  return true;
+}
+
+bool
+xg_filter_key (struct frame *frame, XEvent *xkey)
+{
+  GdkEvent *xg_event = gdk_event_new (GDK_KEY_PRESS);
+  GdkDisplay *dpy = gtk_widget_get_display (FRAME_GTK_WIDGET (frame));
+  GdkKeymap *keymap = gdk_keymap_get_for_display (dpy);
+  GdkModifierType consumed;
+  struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
+  bool result;
+
+  xg_event->any.window = gtk_widget_get_window (FRAME_GTK_WIDGET (frame));
+  g_object_ref (xg_event->any.window);
+
+#if GTK_CHECK_VERSION (3, 20, 0)
+  GdkSeat *seat = gdk_display_get_default_seat (dpy);
+
+  gdk_event_set_device (xg_event,
+                       gdk_seat_get_keyboard (seat));
+#elif GTK_CHECK_VERSION (3, 16, 0)
+  GdkDeviceManager *manager = gdk_display_get_device_manager (dpy);
+  GList *devices = gdk_device_manager_list_devices (manager,
+                                                   GDK_DEVICE_TYPE_MASTER);
+  GdkDevice *device;
+  GList *tem;
+  for (tem = devices; tem; tem = tem->next)
+    {
+      device = GDK_DEVICE (tem->data);
+
+      if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
+       {
+         gdk_event_set_device (xg_event, device);
+         break;
+       }
+    }
+
+  g_list_free (devices);
+#endif
+
+#ifdef HAVE_XINPUT2
+  if (xkey->type != GenericEvent)
+    {
+#endif
+      xg_event->key.hardware_keycode = xkey->xkey.keycode;
+
+#ifdef HAVE_XKB
+      if (dpyinfo->supports_xkb)
+       xg_event->key.group = XkbGroupForCoreState (xkey->xkey.state);
+#endif
+      xg_event->key.state = xkey->xkey.state;
+      gdk_keymap_translate_keyboard_state (keymap,
+                                          xkey->xkey.keycode,
+                                          xkey->xkey.state,
+                                          xg_event->key.group,
+                                          &xg_event->key.keyval,
+                                          NULL, NULL, &consumed);
+      xg_add_virtual_mods (dpyinfo, &xg_event->key);
+      xg_event->key.state &= ~consumed;
+#if GTK_CHECK_VERSION (3, 6, 0)
+      xg_event->key.is_modifier = gdk_x11_keymap_key_is_modifier (keymap,
+                                                                 
xg_event->key.hardware_keycode);
+#endif
+#ifdef HAVE_XINPUT2
+    }
+  else
+    {
+      XIDeviceEvent *xev = (XIDeviceEvent *) xkey->xcookie.data;
+
+      xg_event->key.hardware_keycode = xev->detail;
+      xg_event->key.group = xev->group.effective;
+      xg_event->key.state = xev->mods.effective;
+      gdk_keymap_translate_keyboard_state (keymap,
+                                          xev->detail,
+                                          xev->mods.effective,
+                                          xg_event->key.group,
+                                          &xg_event->key.keyval,
+                                          NULL, NULL, &consumed);
+      xg_add_virtual_mods (dpyinfo, &xg_event->key);
+      xg_event->key.state &= ~consumed;
+      xg_event->key.is_modifier = gdk_x11_keymap_key_is_modifier (keymap,
+                                                                 
xg_event->key.hardware_keycode);
+    }
+#endif
+
+  result = gtk_im_context_filter_keypress (FRAME_X_OUTPUT (frame)->im_context,
+                                          &xg_event->key);
+
+  gdk_event_free (xg_event);
+
+  return result;
+}
+#endif
 #endif /* USE_GTK */
diff --git a/src/gtkutil.h b/src/gtkutil.h
index 5a91825928..a1dd281f1d 100644
--- a/src/gtkutil.h
+++ b/src/gtkutil.h
@@ -217,6 +217,8 @@ extern void xg_print_frames_dialog (Lisp_Object);
 extern bool xg_is_menu_window (Display *dpy, Window);
 #endif
 
+extern bool xg_filter_key (struct frame *frame, XEvent *xkey);
+
 /* Mark all callback data that are Lisp_object:s during GC.  */
 extern void xg_mark_data (void);
 
diff --git a/src/xfns.c b/src/xfns.c
index 705fa548a2..073200ff75 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -2820,16 +2820,33 @@ xic_set_preeditarea (struct window *w, int x, int y)
   XVaNestedList attr;
   XPoint spot;
 
-  spot.x = WINDOW_TO_FRAME_PIXEL_X (w, x) + WINDOW_LEFT_FRINGE_WIDTH (w) + 
WINDOW_LEFT_MARGIN_WIDTH(w);
-  spot.y = WINDOW_TO_FRAME_PIXEL_Y (w, y) + FONT_BASE (FRAME_FONT (f));
-  attr = XVaCreateNestedList (0, XNSpotLocation, &spot,
-                             XNPreeditStartCallback, 
&Xxic_preedit_start_callback,
-                             XNPreeditDoneCallback, 
&Xxic_preedit_done_callback,
-                             XNPreeditDrawCallback, 
&Xxic_preedit_draw_callback,
-                             XNPreeditCaretCallback, 
&Xxic_preedit_caret_callback,
-                             NULL);
-  XSetICValues (FRAME_XIC (f), XNPreeditAttributes, attr, NULL);
-  XFree (attr);
+  if (FRAME_XIC (WINDOW_XFRAME (w)))
+    {
+      spot.x = WINDOW_TO_FRAME_PIXEL_X (w, x) + WINDOW_LEFT_FRINGE_WIDTH (w) + 
WINDOW_LEFT_MARGIN_WIDTH(w);
+      spot.y = WINDOW_TO_FRAME_PIXEL_Y (w, y) + FONT_BASE (FRAME_FONT (f));
+      attr = XVaCreateNestedList (0, XNSpotLocation, &spot,
+                                 XNPreeditStartCallback, 
&Xxic_preedit_start_callback,
+                                 XNPreeditDoneCallback, 
&Xxic_preedit_done_callback,
+                                 XNPreeditDrawCallback, 
&Xxic_preedit_draw_callback,
+                                 XNPreeditCaretCallback, 
&Xxic_preedit_caret_callback,
+                                 NULL);
+      XSetICValues (FRAME_XIC (f), XNPreeditAttributes, attr, NULL);
+      XFree (attr);
+    }
+#ifdef USE_GTK
+  GdkRectangle rect;
+  rect.x = (WINDOW_TO_FRAME_PIXEL_X (w, x)
+           + WINDOW_LEFT_FRINGE_WIDTH (w)
+           + WINDOW_LEFT_MARGIN_WIDTH (w));
+  rect.y = (WINDOW_TO_FRAME_PIXEL_Y (w, y)
+           + FRAME_TOOLBAR_HEIGHT (f)
+           + FRAME_MENUBAR_HEIGHT (f));
+  rect.width = w->phys_cursor_width;
+  rect.height = w->phys_cursor_height;
+
+  gtk_im_context_set_cursor_location (FRAME_X_OUTPUT (f)->im_context,
+                                     &rect);
+#endif
 }
 
 
diff --git a/src/xterm.c b/src/xterm.c
index b284fdd312..9b4bd4b8db 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -147,6 +147,17 @@ bool use_xim = true;
 bool use_xim = false;  /* configure --without-xim */
 #endif
 
+#ifdef USE_GTK
+/* GTK can't tolerate a call to `handle_interrupt' inside an event
+   signal handler, but we have to store input events inside the
+   handler for native input to work.
+
+   This acts as a `hold_quit', and it is stored in the keyboard buffer
+   (thereby causing the call to `handle_interrupt') after the GTK
+   signal handler exits and control returns to XTread_socket.  */
+struct input_event xg_pending_quit_event = { .kind = NO_EVENT };
+#endif
+
 /* Non-zero means that a HELP_EVENT has been generated since Emacs
    start.  */
 
@@ -4931,7 +4942,8 @@ x_new_focus_frame (struct x_display_info *dpyinfo, struct 
frame *frame)
    a FOCUS_IN_EVENT into *BUFP.  */
 
 static void
-x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct 
frame *frame, struct input_event *bufp)
+x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct 
frame *frame,
+                struct input_event *bufp)
 {
   if (type == FocusIn)
     {
@@ -4947,7 +4959,15 @@ x_focus_changed (int type, int state, struct 
x_display_info *dpyinfo, struct fra
 
 #ifdef HAVE_X_I18N
       if (FRAME_XIC (frame))
-        XSetICFocus (FRAME_XIC (frame));
+       XSetICFocus (FRAME_XIC (frame));
+#ifdef USE_GTK
+      GtkWidget *widget;
+
+      gtk_im_context_focus_in (FRAME_X_OUTPUT (frame)->im_context);
+      widget = FRAME_GTK_OUTER_WIDGET (frame);
+      gtk_im_context_set_client_window (FRAME_X_OUTPUT (frame)->im_context,
+                                       gtk_widget_get_window (widget));
+#endif
 #endif
     }
   else if (type == FocusOut)
@@ -4966,6 +4986,10 @@ x_focus_changed (int type, int state, struct 
x_display_info *dpyinfo, struct fra
 #ifdef HAVE_X_I18N
       if (FRAME_XIC (frame))
         XUnsetICFocus (FRAME_XIC (frame));
+#ifdef USE_GTK
+      gtk_im_context_focus_out (FRAME_X_OUTPUT (frame)->im_context);
+      gtk_im_context_set_client_window (FRAME_X_OUTPUT (frame)->im_context, 
NULL);
+#endif
 #endif
       if (frame->pointer_invisible)
         XTtoggle_invisible_pointer (frame, false);
@@ -8229,10 +8253,52 @@ x_filter_event (struct x_display_info *dpyinfo, XEvent 
*event)
    XFilterEvent because that's the one for which the IC
    was created.  */
 
-  struct frame *f1 = x_any_window_to_frame (dpyinfo,
-                                            event->xclient.window);
+  struct frame *f1;
 
-  return XFilterEvent (event, f1 ? FRAME_X_WINDOW (f1) : None);
+#if defined HAVE_XINPUT2 && defined USE_GTK
+  bool xinput_event = false;
+  if (dpyinfo->supports_xi2
+      && event->type == GenericEvent
+      && (event->xgeneric.extension
+         == dpyinfo->xi2_opcode)
+      && (event->xgeneric.evtype
+         == XI_KeyPress))
+    {
+      f1 = x_any_window_to_frame (dpyinfo,
+                                 ((XIDeviceEvent *)
+                                  event->xcookie.data)->event);
+      xinput_event = true;
+    }
+  else
+#endif
+    f1 = x_any_window_to_frame (dpyinfo,
+                               event->xclient.window);
+
+#ifdef USE_GTK
+  if (!x_gtk_use_native_input
+      && !dpyinfo->prefer_native_input)
+    {
+#endif
+      return XFilterEvent (event, f1 ? FRAME_X_WINDOW (f1) : None);
+#ifdef USE_GTK
+    }
+  else if (f1 && (event->type == KeyPress
+#ifdef HAVE_XINPUT2
+                 || xinput_event
+#endif
+                 ))
+    {
+      bool result;
+
+      block_input ();
+      result = xg_filter_key (f1, event);
+      unblock_input ();
+
+      return result;
+    }
+
+  return 0;
+#endif
 }
 #endif
 
@@ -10679,12 +10745,23 @@ handle_one_xevent (struct x_display_info *dpyinfo,
              xkey.keycode = xev->detail;
              xkey.same_screen = True;
 
+#ifdef USE_GTK
+             if ((!x_gtk_use_native_input
+                  && x_filter_event (dpyinfo, (XEvent *) &xkey))
+                 || (x_gtk_use_native_input
+                     && x_filter_event (dpyinfo, event)))
+               {
+                 *finish = X_EVENT_DROP;
+                 goto XI_OTHER;
+               }
+#else
              if (x_filter_event (dpyinfo, (XEvent *) &xkey))
                {
                  *finish = X_EVENT_DROP;
                  goto XI_OTHER;
                }
 #endif
+#endif
 
 #ifdef HAVE_XKB
              if (dpyinfo->xkb_desc)
@@ -11421,6 +11498,20 @@ XTread_socket (struct terminal *terminal, struct 
input_event *hold_quit)
       if (current_finish == X_EVENT_GOTO_OUT)
         break;
     }
+
+  /* Now see if `xg_pending_quit_event' was set.  */
+  if (xg_pending_quit_event.kind != NO_EVENT)
+    {
+      /* Check that the frame is still valid.  It could have been
+        deleted between now and the time the event was recorded.  */
+      if (FRAME_LIVE_P (XFRAME (xg_pending_quit_event.frame_or_window)))
+       /* Store that event into hold_quit and clear the pending quit
+          event.  */
+       *hold_quit = xg_pending_quit_event;
+
+      /* If the frame is invalid, just clear the event as well.  */
+      xg_pending_quit_event.kind = NO_EVENT;
+    }
 #endif /* USE_GTK */
 
   /* On some systems, an X bug causes Emacs to get no more events
@@ -11726,8 +11817,7 @@ x_draw_window_cursor (struct window *w, struct 
glyph_row *glyph_row, int x,
 
 #ifdef HAVE_X_I18N
       if (w == XWINDOW (f->selected_window))
-       if (FRAME_XIC (f))
-         xic_set_preeditarea (w, x, y);
+       xic_set_preeditarea (w, x, y);
 #endif
     }
 
@@ -15299,6 +15389,10 @@ x_term_init (Lisp_Object display_name, char 
*xrm_option, char *resource_name)
          dpyinfo->preferred_xim_style = STYLE_OFFTHESPOT;
        else if (!strcmp (SSDATA (value), "root"))
          dpyinfo->preferred_xim_style = STYLE_ROOT;
+#ifdef USE_GTK
+       else if (!strcmp (SSDATA (value), "native"))
+         dpyinfo->prefer_native_input = true;
+#endif
       }
 #endif
   }
@@ -15846,4 +15940,10 @@ always uses gtk_window_move and ignores the value of 
this variable.  */);
 This option is only effective when Emacs is built with XInput 2
 support. */);
   Vx_scroll_event_delta_factor = make_float (1.0);
+
+  DEFVAR_BOOL ("x-gtk-use-native-input", x_gtk_use_native_input,
+              doc: /* Non-nil means to use GTK for input method support.
+This provides better support for some modern input methods, and is
+only effective when Emacs is built with GTK.  */);
+  x_gtk_use_native_input = false;
 }
diff --git a/src/xterm.h b/src/xterm.h
index a796f69ddc..fc47fe0c6e 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -527,6 +527,10 @@ struct x_display_info
   int xkb_event_type;
   XkbDescPtr xkb_desc;
 #endif
+
+#ifdef USE_GTK
+  bool prefer_native_input;
+#endif
 };
 
 #ifdef HAVE_X_I18N
@@ -643,6 +647,8 @@ struct x_output
   GtkTooltip *ttip_widget;
   GtkWidget *ttip_lbl;
   GtkWindow *ttip_window;
+
+  GtkIMContext *im_context;
 #endif /* USE_GTK */
 
   /* If >=0, a bitmap index.  The indicated bitmap is used for the
@@ -1340,6 +1346,10 @@ extern void x_session_close (void);
 #define STYLE_NONE (XIMPreeditNothing | XIMStatusNothing)
 #endif
 
+#ifdef USE_GTK
+extern struct input_event xg_pending_quit_event;
+#endif
+
 /* Is the frame embedded into another application? */
 
 #define FRAME_X_EMBEDDED_P(f) (FRAME_X_OUTPUT(f)->explicit_parent != 0)



reply via email to

[Prev in Thread] Current Thread [Next in Thread]