diff options
| author | Po Lu | 2022-01-08 15:21:51 +0800 |
|---|---|---|
| committer | Po Lu | 2022-01-08 15:21:51 +0800 |
| commit | d76fb0c11e9859db0d03d6496f5a720d304f4ca9 (patch) | |
| tree | 89035fe1de1c0835716c45c9747e74128308e039 /src | |
| parent | 63c83e40dad88036d5ceef17eace51056a18b55f (diff) | |
| download | emacs-d76fb0c11e9859db0d03d6496f5a720d304f4ca9.tar.gz emacs-d76fb0c11e9859db0d03d6496f5a720d304f4ca9.zip | |
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'.
Diffstat (limited to 'src')
| -rw-r--r-- | src/gtkutil.c | 354 | ||||
| -rw-r--r-- | src/gtkutil.h | 2 | ||||
| -rw-r--r-- | src/xfns.c | 37 | ||||
| -rw-r--r-- | src/xterm.c | 114 | ||||
| -rw-r--r-- | src/xterm.h | 10 |
5 files changed, 500 insertions, 17 deletions
diff --git a/src/gtkutil.c b/src/gtkutil.c index 93f51d77962..4c516a4479a 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c | |||
| @@ -76,6 +76,13 @@ typedef struct pgtk_output xp_output; | |||
| 76 | #define XG_TEXT_OPEN GTK_STOCK_OPEN | 76 | #define XG_TEXT_OPEN GTK_STOCK_OPEN |
| 77 | #endif | 77 | #endif |
| 78 | 78 | ||
| 79 | #ifndef HAVE_PGTK | ||
| 80 | static void xg_im_context_commit (GtkIMContext *, gchar *, gpointer); | ||
| 81 | static void xg_im_context_preedit_changed (GtkIMContext *, gpointer); | ||
| 82 | static void xg_im_context_preedit_end (GtkIMContext *, gpointer); | ||
| 83 | static bool xg_widget_key_press_event_cb (GtkWidget *, GdkEvent *, gpointer); | ||
| 84 | #endif | ||
| 85 | |||
| 79 | #ifndef HAVE_GTK3 | 86 | #ifndef HAVE_GTK3 |
| 80 | 87 | ||
| 81 | #ifdef HAVE_FREETYPE | 88 | #ifdef HAVE_FREETYPE |
| @@ -1437,6 +1444,9 @@ xg_create_frame_widgets (struct frame *f) | |||
| 1437 | #ifndef HAVE_GTK3 | 1444 | #ifndef HAVE_GTK3 |
| 1438 | GtkRcStyle *style; | 1445 | GtkRcStyle *style; |
| 1439 | #endif | 1446 | #endif |
| 1447 | #ifndef HAVE_PGTK | ||
| 1448 | GtkIMContext *imc; | ||
| 1449 | #endif | ||
| 1440 | GtkWindowType type = GTK_WINDOW_TOPLEVEL; | 1450 | GtkWindowType type = GTK_WINDOW_TOPLEVEL; |
| 1441 | char *title = 0; | 1451 | char *title = 0; |
| 1442 | 1452 | ||
| @@ -1621,6 +1631,22 @@ xg_create_frame_widgets (struct frame *f) | |||
| 1621 | #ifndef HAVE_PGTK | 1631 | #ifndef HAVE_PGTK |
| 1622 | gtk_widget_set_tooltip_text (wtop, "Dummy text"); | 1632 | gtk_widget_set_tooltip_text (wtop, "Dummy text"); |
| 1623 | g_signal_connect (wtop, "query-tooltip", G_CALLBACK (qttip_cb), f); | 1633 | g_signal_connect (wtop, "query-tooltip", G_CALLBACK (qttip_cb), f); |
| 1634 | |||
| 1635 | imc = gtk_im_multicontext_new (); | ||
| 1636 | g_object_ref (imc); | ||
| 1637 | gtk_im_context_set_use_preedit (imc, TRUE); | ||
| 1638 | |||
| 1639 | g_signal_connect (G_OBJECT (imc), "commit", | ||
| 1640 | G_CALLBACK (xg_im_context_commit), f); | ||
| 1641 | g_signal_connect (G_OBJECT (imc), "preedit-changed", | ||
| 1642 | G_CALLBACK (xg_im_context_preedit_changed), NULL); | ||
| 1643 | g_signal_connect (G_OBJECT (imc), "preedit-end", | ||
| 1644 | G_CALLBACK (xg_im_context_preedit_end), NULL); | ||
| 1645 | FRAME_X_OUTPUT (f)->im_context = imc; | ||
| 1646 | |||
| 1647 | g_signal_connect (G_OBJECT (wfixed), "key-press-event", | ||
| 1648 | G_CALLBACK (xg_widget_key_press_event_cb), | ||
| 1649 | NULL); | ||
| 1624 | #endif | 1650 | #endif |
| 1625 | 1651 | ||
| 1626 | { | 1652 | { |
| @@ -1761,6 +1787,7 @@ xg_free_frame_widgets (struct frame *f) | |||
| 1761 | /* x_free_frame_resources should have taken care of it */ | 1787 | /* x_free_frame_resources should have taken care of it */ |
| 1762 | #ifndef HAVE_PGTK | 1788 | #ifndef HAVE_PGTK |
| 1763 | eassert (!FRAME_X_DOUBLE_BUFFERED_P (f)); | 1789 | eassert (!FRAME_X_DOUBLE_BUFFERED_P (f)); |
| 1790 | g_object_unref (FRAME_X_OUTPUT (f)->im_context); | ||
| 1764 | #endif | 1791 | #endif |
| 1765 | gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f)); | 1792 | gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f)); |
| 1766 | FRAME_X_WINDOW (f) = 0; /* Set to avoid XDestroyWindow in xterm.c */ | 1793 | FRAME_X_WINDOW (f) = 0; /* Set to avoid XDestroyWindow in xterm.c */ |
| @@ -2928,6 +2955,14 @@ xg_mark_data (void) | |||
| 2928 | } | 2955 | } |
| 2929 | } | 2956 | } |
| 2930 | } | 2957 | } |
| 2958 | |||
| 2959 | if (xg_pending_quit_event.kind != NO_EVENT) | ||
| 2960 | { | ||
| 2961 | eassert (xg_pending_quit_event.kind == ASCII_KEYSTROKE_EVENT); | ||
| 2962 | |||
| 2963 | mark_object (xg_pending_quit_event.frame_or_window); | ||
| 2964 | mark_object (xg_pending_quit_event.arg); | ||
| 2965 | } | ||
| 2931 | } | 2966 | } |
| 2932 | 2967 | ||
| 2933 | /* Callback called when a menu item is destroyed. Used to free data. | 2968 | /* Callback called when a menu item is destroyed. Used to free data. |
| @@ -5963,4 +5998,323 @@ xg_initialize (void) | |||
| 5963 | #endif | 5998 | #endif |
| 5964 | } | 5999 | } |
| 5965 | 6000 | ||
| 6001 | #ifndef HAVE_PGTK | ||
| 6002 | static void | ||
| 6003 | xg_add_virtual_mods (struct x_display_info *dpyinfo, GdkEventKey *key) | ||
| 6004 | { | ||
| 6005 | guint modifiers = key->state; | ||
| 6006 | |||
| 6007 | if (modifiers & dpyinfo->meta_mod_mask) | ||
| 6008 | { | ||
| 6009 | /* GDK always assumes Mod1 is alt, but that's no reason for | ||
| 6010 | us to make that mistake as well. */ | ||
| 6011 | if (!dpyinfo->alt_mod_mask) | ||
| 6012 | key->state |= GDK_MOD1_MASK; | ||
| 6013 | else | ||
| 6014 | key->state |= GDK_META_MASK; | ||
| 6015 | } | ||
| 6016 | |||
| 6017 | if (modifiers & dpyinfo->alt_mod_mask) | ||
| 6018 | key->state |= GDK_MOD1_MASK; | ||
| 6019 | if (modifiers & dpyinfo->super_mod_mask) | ||
| 6020 | key->state |= GDK_SUPER_MASK; | ||
| 6021 | if (modifiers & dpyinfo->hyper_mod_mask) | ||
| 6022 | key->state |= GDK_HYPER_MASK; | ||
| 6023 | } | ||
| 6024 | |||
| 6025 | static void | ||
| 6026 | xg_im_context_commit (GtkIMContext *imc, gchar *str, | ||
| 6027 | gpointer user_data) | ||
| 6028 | { | ||
| 6029 | struct frame *f = user_data; | ||
| 6030 | struct input_event ie; | ||
| 6031 | gunichar *ucs4_str; | ||
| 6032 | |||
| 6033 | ucs4_str = g_utf8_to_ucs4_fast (str, -1, NULL); | ||
| 6034 | |||
| 6035 | if (!ucs4_str) | ||
| 6036 | return; | ||
| 6037 | |||
| 6038 | for (gunichar *c = ucs4_str; *c; c++) | ||
| 6039 | { | ||
| 6040 | EVENT_INIT (ie); | ||
| 6041 | ie.kind = (SINGLE_BYTE_CHAR_P (*c) | ||
| 6042 | ? ASCII_KEYSTROKE_EVENT | ||
| 6043 | : MULTIBYTE_CHAR_KEYSTROKE_EVENT); | ||
| 6044 | ie.arg = Qnil; | ||
| 6045 | ie.code = *c; | ||
| 6046 | XSETFRAME (ie.frame_or_window, f); | ||
| 6047 | ie.modifiers = 0; | ||
| 6048 | ie.timestamp = 0; | ||
| 6049 | |||
| 6050 | kbd_buffer_store_event (&ie); | ||
| 6051 | } | ||
| 6052 | |||
| 6053 | g_free (ucs4_str); | ||
| 6054 | } | ||
| 6055 | |||
| 6056 | static void | ||
| 6057 | xg_im_context_preedit_changed (GtkIMContext *imc, gpointer user_data) | ||
| 6058 | { | ||
| 6059 | PangoAttrList *list; | ||
| 6060 | gchar *str; | ||
| 6061 | gint cursor; | ||
| 6062 | struct input_event inev; | ||
| 6063 | |||
| 6064 | gtk_im_context_get_preedit_string (imc, &str, &list, &cursor); | ||
| 6065 | |||
| 6066 | EVENT_INIT (inev); | ||
| 6067 | inev.kind = PREEDIT_TEXT_EVENT; | ||
| 6068 | inev.arg = build_string_from_utf8 (str); | ||
| 6069 | kbd_buffer_store_event (&inev); | ||
| 6070 | |||
| 6071 | g_free (str); | ||
| 6072 | pango_attr_list_unref (list); | ||
| 6073 | } | ||
| 6074 | |||
| 6075 | static void | ||
| 6076 | xg_im_context_preedit_end (GtkIMContext *imc, gpointer user_data) | ||
| 6077 | { | ||
| 6078 | struct input_event inev; | ||
| 6079 | |||
| 6080 | EVENT_INIT (inev); | ||
| 6081 | inev.kind = PREEDIT_TEXT_EVENT; | ||
| 6082 | inev.arg = Qnil; | ||
| 6083 | kbd_buffer_store_event (&inev); | ||
| 6084 | } | ||
| 6085 | |||
| 6086 | static bool | ||
| 6087 | xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, | ||
| 6088 | gpointer user_data) | ||
| 6089 | { | ||
| 6090 | Lisp_Object tail, tem; | ||
| 6091 | struct frame *f = NULL; | ||
| 6092 | union buffered_input_event inev; | ||
| 6093 | guint keysym = event->key.keyval; | ||
| 6094 | gunichar *cb; | ||
| 6095 | ptrdiff_t i; | ||
| 6096 | glong len; | ||
| 6097 | |||
| 6098 | FOR_EACH_FRAME (tail, tem) | ||
| 6099 | { | ||
| 6100 | if (FRAME_X_P (XFRAME (tem)) | ||
| 6101 | && (FRAME_GTK_WIDGET (XFRAME (tem)) == widget)) | ||
| 6102 | { | ||
| 6103 | f = XFRAME (tem); | ||
| 6104 | break; | ||
| 6105 | } | ||
| 6106 | } | ||
| 6107 | |||
| 6108 | if (!f) | ||
| 6109 | return true; | ||
| 6110 | |||
| 6111 | if (!x_gtk_use_native_input | ||
| 6112 | && !FRAME_DISPLAY_INFO (f)->prefer_native_input) | ||
| 6113 | return true; | ||
| 6114 | |||
| 6115 | EVENT_INIT (inev.ie); | ||
| 6116 | XSETFRAME (inev.ie.frame_or_window, f); | ||
| 6117 | |||
| 6118 | inev.ie.modifiers |= x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), | ||
| 6119 | event->key.state); | ||
| 6120 | |||
| 6121 | /* First deal with keysyms which have defined | ||
| 6122 | translations to characters. */ | ||
| 6123 | if (keysym >= 32 && keysym < 128) | ||
| 6124 | /* Avoid explicitly decoding each ASCII character. */ | ||
| 6125 | { | ||
| 6126 | inev.ie.kind = ASCII_KEYSTROKE_EVENT; | ||
| 6127 | inev.ie.code = keysym; | ||
| 6128 | goto done; | ||
| 6129 | } | ||
| 6130 | |||
| 6131 | /* Keysyms directly mapped to Unicode characters. */ | ||
| 6132 | if (keysym >= 0x01000000 && keysym <= 0x0110FFFF) | ||
| 6133 | { | ||
| 6134 | if (keysym < 0x01000080) | ||
| 6135 | inev.ie.kind = ASCII_KEYSTROKE_EVENT; | ||
| 6136 | else | ||
| 6137 | inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT; | ||
| 6138 | inev.ie.code = keysym & 0xFFFFFF; | ||
| 6139 | goto done; | ||
| 6140 | } | ||
| 6141 | |||
| 6142 | /* Random non-modifier sorts of keysyms. */ | ||
| 6143 | if (((keysym >= GDK_KEY_BackSpace && keysym <= GDK_KEY_Escape) | ||
| 6144 | || keysym == GDK_KEY_Delete | ||
| 6145 | #ifdef GDK_KEY_ISO_Left_Tab | ||
| 6146 | || (keysym >= GDK_KEY_ISO_Left_Tab && keysym <= GDK_KEY_ISO_Enter) | ||
| 6147 | #endif | ||
| 6148 | || IsCursorKey (keysym) /* 0xff50 <= x < 0xff60 */ | ||
| 6149 | || IsMiscFunctionKey (keysym) /* 0xff60 <= x < VARIES */ | ||
| 6150 | #ifdef GDK_KEY_dead_circumflex | ||
| 6151 | || keysym == GDK_KEY_dead_circumflex | ||
| 6152 | #endif | ||
| 6153 | #ifdef GDK_KEY_dead_grave | ||
| 6154 | || keysym == GDK_KEY_dead_grave | ||
| 6155 | #endif | ||
| 6156 | #ifdef GDK_KEY_dead_tilde | ||
| 6157 | || keysym == GDK_KEY_dead_tilde | ||
| 6158 | #endif | ||
| 6159 | #ifdef GDK_KEY_dead_diaeresis | ||
| 6160 | || keysym == GDK_KEY_dead_diaeresis | ||
| 6161 | #endif | ||
| 6162 | #ifdef GDK_KEY_dead_macron | ||
| 6163 | || keysym == GDK_KEY_dead_macron | ||
| 6164 | #endif | ||
| 6165 | #ifdef GDK_KEY_dead_degree | ||
| 6166 | || keysym == GDK_KEY_dead_degree | ||
| 6167 | #endif | ||
| 6168 | #ifdef GDK_KEY_dead_acute | ||
| 6169 | || keysym == GDK_KEY_dead_acute | ||
| 6170 | #endif | ||
| 6171 | #ifdef GDK_KEY_dead_cedilla | ||
| 6172 | || keysym == GDK_KEY_dead_cedilla | ||
| 6173 | #endif | ||
| 6174 | #ifdef GDK_KEY_dead_breve | ||
| 6175 | || keysym == GDK_KEY_dead_breve | ||
| 6176 | #endif | ||
| 6177 | #ifdef GDK_KEY_dead_ogonek | ||
| 6178 | || keysym == GDK_KEY_dead_ogonek | ||
| 6179 | #endif | ||
| 6180 | #ifdef GDK_KEY_dead_caron | ||
| 6181 | || keysym == GDK_KEY_dead_caron | ||
| 6182 | #endif | ||
| 6183 | #ifdef GDK_KEY_dead_doubleacute | ||
| 6184 | || keysym == GDK_KEY_dead_doubleacute | ||
| 6185 | #endif | ||
| 6186 | #ifdef GDK_KEY_dead_abovedot | ||
| 6187 | || keysym == GDK_KEY_dead_abovedot | ||
| 6188 | #endif | ||
| 6189 | || IsKeypadKey (keysym) /* 0xff80 <= x < 0xffbe */ | ||
| 6190 | || IsFunctionKey (keysym) /* 0xffbe <= x < 0xffe1 */ | ||
| 6191 | /* Any "vendor-specific" key is ok. */ | ||
| 6192 | || (keysym & (1 << 28)) | ||
| 6193 | || (keysym != GDK_KEY_VoidSymbol && !event->key.string)) | ||
| 6194 | && !(event->key.is_modifier)) | ||
| 6195 | { | ||
| 6196 | inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT; | ||
| 6197 | inev.ie.code = keysym; | ||
| 6198 | goto done; | ||
| 6199 | } | ||
| 6200 | |||
| 6201 | if (event->key.string) | ||
| 6202 | { | ||
| 6203 | cb = g_utf8_to_ucs4_fast (event->key.string, -1, &len); | ||
| 6204 | |||
| 6205 | for (i = 0; i < len; ++i) | ||
| 6206 | { | ||
| 6207 | inev.ie.kind = (SINGLE_BYTE_CHAR_P (cb[i]) | ||
| 6208 | ? ASCII_KEYSTROKE_EVENT | ||
| 6209 | : MULTIBYTE_CHAR_KEYSTROKE_EVENT); | ||
| 6210 | inev.ie.code = cb[i]; | ||
| 6211 | |||
| 6212 | kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); | ||
| 6213 | } | ||
| 6214 | |||
| 6215 | g_free (cb); | ||
| 6216 | |||
| 6217 | inev.ie.kind = NO_EVENT; | ||
| 6218 | } | ||
| 6219 | |||
| 6220 | done: | ||
| 6221 | if (inev.ie.kind != NO_EVENT) | ||
| 6222 | { | ||
| 6223 | xg_pending_quit_event.kind = NO_EVENT; | ||
| 6224 | kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); | ||
| 6225 | } | ||
| 6226 | return true; | ||
| 6227 | } | ||
| 6228 | |||
| 6229 | bool | ||
| 6230 | xg_filter_key (struct frame *frame, XEvent *xkey) | ||
| 6231 | { | ||
| 6232 | GdkEvent *xg_event = gdk_event_new (GDK_KEY_PRESS); | ||
| 6233 | GdkDisplay *dpy = gtk_widget_get_display (FRAME_GTK_WIDGET (frame)); | ||
| 6234 | GdkKeymap *keymap = gdk_keymap_get_for_display (dpy); | ||
| 6235 | GdkModifierType consumed; | ||
| 6236 | struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame); | ||
| 6237 | bool result; | ||
| 6238 | |||
| 6239 | xg_event->any.window = gtk_widget_get_window (FRAME_GTK_WIDGET (frame)); | ||
| 6240 | g_object_ref (xg_event->any.window); | ||
| 6241 | |||
| 6242 | #if GTK_CHECK_VERSION (3, 20, 0) | ||
| 6243 | GdkSeat *seat = gdk_display_get_default_seat (dpy); | ||
| 6244 | |||
| 6245 | gdk_event_set_device (xg_event, | ||
| 6246 | gdk_seat_get_keyboard (seat)); | ||
| 6247 | #elif GTK_CHECK_VERSION (3, 16, 0) | ||
| 6248 | GdkDeviceManager *manager = gdk_display_get_device_manager (dpy); | ||
| 6249 | GList *devices = gdk_device_manager_list_devices (manager, | ||
| 6250 | GDK_DEVICE_TYPE_MASTER); | ||
| 6251 | GdkDevice *device; | ||
| 6252 | GList *tem; | ||
| 6253 | for (tem = devices; tem; tem = tem->next) | ||
| 6254 | { | ||
| 6255 | device = GDK_DEVICE (tem->data); | ||
| 6256 | |||
| 6257 | if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) | ||
| 6258 | { | ||
| 6259 | gdk_event_set_device (xg_event, device); | ||
| 6260 | break; | ||
| 6261 | } | ||
| 6262 | } | ||
| 6263 | |||
| 6264 | g_list_free (devices); | ||
| 6265 | #endif | ||
| 6266 | |||
| 6267 | #ifdef HAVE_XINPUT2 | ||
| 6268 | if (xkey->type != GenericEvent) | ||
| 6269 | { | ||
| 6270 | #endif | ||
| 6271 | xg_event->key.hardware_keycode = xkey->xkey.keycode; | ||
| 6272 | |||
| 6273 | #ifdef HAVE_XKB | ||
| 6274 | if (dpyinfo->supports_xkb) | ||
| 6275 | xg_event->key.group = XkbGroupForCoreState (xkey->xkey.state); | ||
| 6276 | #endif | ||
| 6277 | xg_event->key.state = xkey->xkey.state; | ||
| 6278 | gdk_keymap_translate_keyboard_state (keymap, | ||
| 6279 | xkey->xkey.keycode, | ||
| 6280 | xkey->xkey.state, | ||
| 6281 | xg_event->key.group, | ||
| 6282 | &xg_event->key.keyval, | ||
| 6283 | NULL, NULL, &consumed); | ||
| 6284 | xg_add_virtual_mods (dpyinfo, &xg_event->key); | ||
| 6285 | xg_event->key.state &= ~consumed; | ||
| 6286 | #if GTK_CHECK_VERSION (3, 6, 0) | ||
| 6287 | xg_event->key.is_modifier = gdk_x11_keymap_key_is_modifier (keymap, | ||
| 6288 | xg_event->key.hardware_keycode); | ||
| 6289 | #endif | ||
| 6290 | #ifdef HAVE_XINPUT2 | ||
| 6291 | } | ||
| 6292 | else | ||
| 6293 | { | ||
| 6294 | XIDeviceEvent *xev = (XIDeviceEvent *) xkey->xcookie.data; | ||
| 6295 | |||
| 6296 | xg_event->key.hardware_keycode = xev->detail; | ||
| 6297 | xg_event->key.group = xev->group.effective; | ||
| 6298 | xg_event->key.state = xev->mods.effective; | ||
| 6299 | gdk_keymap_translate_keyboard_state (keymap, | ||
| 6300 | xev->detail, | ||
| 6301 | xev->mods.effective, | ||
| 6302 | xg_event->key.group, | ||
| 6303 | &xg_event->key.keyval, | ||
| 6304 | NULL, NULL, &consumed); | ||
| 6305 | xg_add_virtual_mods (dpyinfo, &xg_event->key); | ||
| 6306 | xg_event->key.state &= ~consumed; | ||
| 6307 | xg_event->key.is_modifier = gdk_x11_keymap_key_is_modifier (keymap, | ||
| 6308 | xg_event->key.hardware_keycode); | ||
| 6309 | } | ||
| 6310 | #endif | ||
| 6311 | |||
| 6312 | result = gtk_im_context_filter_keypress (FRAME_X_OUTPUT (frame)->im_context, | ||
| 6313 | &xg_event->key); | ||
| 6314 | |||
| 6315 | gdk_event_free (xg_event); | ||
| 6316 | |||
| 6317 | return result; | ||
| 6318 | } | ||
| 6319 | #endif | ||
| 5966 | #endif /* USE_GTK */ | 6320 | #endif /* USE_GTK */ |
diff --git a/src/gtkutil.h b/src/gtkutil.h index 5a918259280..a1dd281f1d3 100644 --- a/src/gtkutil.h +++ b/src/gtkutil.h | |||
| @@ -217,6 +217,8 @@ extern void xg_print_frames_dialog (Lisp_Object); | |||
| 217 | extern bool xg_is_menu_window (Display *dpy, Window); | 217 | extern bool xg_is_menu_window (Display *dpy, Window); |
| 218 | #endif | 218 | #endif |
| 219 | 219 | ||
| 220 | extern bool xg_filter_key (struct frame *frame, XEvent *xkey); | ||
| 221 | |||
| 220 | /* Mark all callback data that are Lisp_object:s during GC. */ | 222 | /* Mark all callback data that are Lisp_object:s during GC. */ |
| 221 | extern void xg_mark_data (void); | 223 | extern void xg_mark_data (void); |
| 222 | 224 | ||
diff --git a/src/xfns.c b/src/xfns.c index 705fa548a21..073200ff75a 100644 --- a/src/xfns.c +++ b/src/xfns.c | |||
| @@ -2820,16 +2820,33 @@ xic_set_preeditarea (struct window *w, int x, int y) | |||
| 2820 | XVaNestedList attr; | 2820 | XVaNestedList attr; |
| 2821 | XPoint spot; | 2821 | XPoint spot; |
| 2822 | 2822 | ||
| 2823 | spot.x = WINDOW_TO_FRAME_PIXEL_X (w, x) + WINDOW_LEFT_FRINGE_WIDTH (w) + WINDOW_LEFT_MARGIN_WIDTH(w); | 2823 | if (FRAME_XIC (WINDOW_XFRAME (w))) |
| 2824 | spot.y = WINDOW_TO_FRAME_PIXEL_Y (w, y) + FONT_BASE (FRAME_FONT (f)); | 2824 | { |
| 2825 | attr = XVaCreateNestedList (0, XNSpotLocation, &spot, | 2825 | spot.x = WINDOW_TO_FRAME_PIXEL_X (w, x) + WINDOW_LEFT_FRINGE_WIDTH (w) + WINDOW_LEFT_MARGIN_WIDTH(w); |
| 2826 | XNPreeditStartCallback, &Xxic_preedit_start_callback, | 2826 | spot.y = WINDOW_TO_FRAME_PIXEL_Y (w, y) + FONT_BASE (FRAME_FONT (f)); |
| 2827 | XNPreeditDoneCallback, &Xxic_preedit_done_callback, | 2827 | attr = XVaCreateNestedList (0, XNSpotLocation, &spot, |
| 2828 | XNPreeditDrawCallback, &Xxic_preedit_draw_callback, | 2828 | XNPreeditStartCallback, &Xxic_preedit_start_callback, |
| 2829 | XNPreeditCaretCallback, &Xxic_preedit_caret_callback, | 2829 | XNPreeditDoneCallback, &Xxic_preedit_done_callback, |
| 2830 | NULL); | 2830 | XNPreeditDrawCallback, &Xxic_preedit_draw_callback, |
| 2831 | XSetICValues (FRAME_XIC (f), XNPreeditAttributes, attr, NULL); | 2831 | XNPreeditCaretCallback, &Xxic_preedit_caret_callback, |
| 2832 | XFree (attr); | 2832 | NULL); |
| 2833 | XSetICValues (FRAME_XIC (f), XNPreeditAttributes, attr, NULL); | ||
| 2834 | XFree (attr); | ||
| 2835 | } | ||
| 2836 | #ifdef USE_GTK | ||
| 2837 | GdkRectangle rect; | ||
| 2838 | rect.x = (WINDOW_TO_FRAME_PIXEL_X (w, x) | ||
| 2839 | + WINDOW_LEFT_FRINGE_WIDTH (w) | ||
| 2840 | + WINDOW_LEFT_MARGIN_WIDTH (w)); | ||
| 2841 | rect.y = (WINDOW_TO_FRAME_PIXEL_Y (w, y) | ||
| 2842 | + FRAME_TOOLBAR_HEIGHT (f) | ||
| 2843 | + FRAME_MENUBAR_HEIGHT (f)); | ||
| 2844 | rect.width = w->phys_cursor_width; | ||
| 2845 | rect.height = w->phys_cursor_height; | ||
| 2846 | |||
| 2847 | gtk_im_context_set_cursor_location (FRAME_X_OUTPUT (f)->im_context, | ||
| 2848 | &rect); | ||
| 2849 | #endif | ||
| 2833 | } | 2850 | } |
| 2834 | 2851 | ||
| 2835 | 2852 | ||
diff --git a/src/xterm.c b/src/xterm.c index b284fdd3123..9b4bd4b8db2 100644 --- a/src/xterm.c +++ b/src/xterm.c | |||
| @@ -147,6 +147,17 @@ bool use_xim = true; | |||
| 147 | bool use_xim = false; /* configure --without-xim */ | 147 | bool use_xim = false; /* configure --without-xim */ |
| 148 | #endif | 148 | #endif |
| 149 | 149 | ||
| 150 | #ifdef USE_GTK | ||
| 151 | /* GTK can't tolerate a call to `handle_interrupt' inside an event | ||
| 152 | signal handler, but we have to store input events inside the | ||
| 153 | handler for native input to work. | ||
| 154 | |||
| 155 | This acts as a `hold_quit', and it is stored in the keyboard buffer | ||
| 156 | (thereby causing the call to `handle_interrupt') after the GTK | ||
| 157 | signal handler exits and control returns to XTread_socket. */ | ||
| 158 | struct input_event xg_pending_quit_event = { .kind = NO_EVENT }; | ||
| 159 | #endif | ||
| 160 | |||
| 150 | /* Non-zero means that a HELP_EVENT has been generated since Emacs | 161 | /* Non-zero means that a HELP_EVENT has been generated since Emacs |
| 151 | start. */ | 162 | start. */ |
| 152 | 163 | ||
| @@ -4931,7 +4942,8 @@ x_new_focus_frame (struct x_display_info *dpyinfo, struct frame *frame) | |||
| 4931 | a FOCUS_IN_EVENT into *BUFP. */ | 4942 | a FOCUS_IN_EVENT into *BUFP. */ |
| 4932 | 4943 | ||
| 4933 | static void | 4944 | static void |
| 4934 | x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct frame *frame, struct input_event *bufp) | 4945 | x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct frame *frame, |
| 4946 | struct input_event *bufp) | ||
| 4935 | { | 4947 | { |
| 4936 | if (type == FocusIn) | 4948 | if (type == FocusIn) |
| 4937 | { | 4949 | { |
| @@ -4947,7 +4959,15 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra | |||
| 4947 | 4959 | ||
| 4948 | #ifdef HAVE_X_I18N | 4960 | #ifdef HAVE_X_I18N |
| 4949 | if (FRAME_XIC (frame)) | 4961 | if (FRAME_XIC (frame)) |
| 4950 | XSetICFocus (FRAME_XIC (frame)); | 4962 | XSetICFocus (FRAME_XIC (frame)); |
| 4963 | #ifdef USE_GTK | ||
| 4964 | GtkWidget *widget; | ||
| 4965 | |||
| 4966 | gtk_im_context_focus_in (FRAME_X_OUTPUT (frame)->im_context); | ||
| 4967 | widget = FRAME_GTK_OUTER_WIDGET (frame); | ||
| 4968 | gtk_im_context_set_client_window (FRAME_X_OUTPUT (frame)->im_context, | ||
| 4969 | gtk_widget_get_window (widget)); | ||
| 4970 | #endif | ||
| 4951 | #endif | 4971 | #endif |
| 4952 | } | 4972 | } |
| 4953 | else if (type == FocusOut) | 4973 | else if (type == FocusOut) |
| @@ -4966,6 +4986,10 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra | |||
| 4966 | #ifdef HAVE_X_I18N | 4986 | #ifdef HAVE_X_I18N |
| 4967 | if (FRAME_XIC (frame)) | 4987 | if (FRAME_XIC (frame)) |
| 4968 | XUnsetICFocus (FRAME_XIC (frame)); | 4988 | XUnsetICFocus (FRAME_XIC (frame)); |
| 4989 | #ifdef USE_GTK | ||
| 4990 | gtk_im_context_focus_out (FRAME_X_OUTPUT (frame)->im_context); | ||
| 4991 | gtk_im_context_set_client_window (FRAME_X_OUTPUT (frame)->im_context, NULL); | ||
| 4992 | #endif | ||
| 4969 | #endif | 4993 | #endif |
| 4970 | if (frame->pointer_invisible) | 4994 | if (frame->pointer_invisible) |
| 4971 | XTtoggle_invisible_pointer (frame, false); | 4995 | XTtoggle_invisible_pointer (frame, false); |
| @@ -8229,10 +8253,52 @@ x_filter_event (struct x_display_info *dpyinfo, XEvent *event) | |||
| 8229 | XFilterEvent because that's the one for which the IC | 8253 | XFilterEvent because that's the one for which the IC |
| 8230 | was created. */ | 8254 | was created. */ |
| 8231 | 8255 | ||
| 8232 | struct frame *f1 = x_any_window_to_frame (dpyinfo, | 8256 | struct frame *f1; |
| 8233 | event->xclient.window); | ||
| 8234 | 8257 | ||
| 8235 | return XFilterEvent (event, f1 ? FRAME_X_WINDOW (f1) : None); | 8258 | #if defined HAVE_XINPUT2 && defined USE_GTK |
| 8259 | bool xinput_event = false; | ||
| 8260 | if (dpyinfo->supports_xi2 | ||
| 8261 | && event->type == GenericEvent | ||
| 8262 | && (event->xgeneric.extension | ||
| 8263 | == dpyinfo->xi2_opcode) | ||
| 8264 | && (event->xgeneric.evtype | ||
| 8265 | == XI_KeyPress)) | ||
| 8266 | { | ||
| 8267 | f1 = x_any_window_to_frame (dpyinfo, | ||
| 8268 | ((XIDeviceEvent *) | ||
| 8269 | event->xcookie.data)->event); | ||
| 8270 | xinput_event = true; | ||
| 8271 | } | ||
| 8272 | else | ||
| 8273 | #endif | ||
| 8274 | f1 = x_any_window_to_frame (dpyinfo, | ||
| 8275 | event->xclient.window); | ||
| 8276 | |||
| 8277 | #ifdef USE_GTK | ||
| 8278 | if (!x_gtk_use_native_input | ||
| 8279 | && !dpyinfo->prefer_native_input) | ||
| 8280 | { | ||
| 8281 | #endif | ||
| 8282 | return XFilterEvent (event, f1 ? FRAME_X_WINDOW (f1) : None); | ||
| 8283 | #ifdef USE_GTK | ||
| 8284 | } | ||
| 8285 | else if (f1 && (event->type == KeyPress | ||
| 8286 | #ifdef HAVE_XINPUT2 | ||
| 8287 | || xinput_event | ||
| 8288 | #endif | ||
| 8289 | )) | ||
| 8290 | { | ||
| 8291 | bool result; | ||
| 8292 | |||
| 8293 | block_input (); | ||
| 8294 | result = xg_filter_key (f1, event); | ||
| 8295 | unblock_input (); | ||
| 8296 | |||
| 8297 | return result; | ||
| 8298 | } | ||
| 8299 | |||
| 8300 | return 0; | ||
| 8301 | #endif | ||
| 8236 | } | 8302 | } |
| 8237 | #endif | 8303 | #endif |
| 8238 | 8304 | ||
| @@ -10679,12 +10745,23 @@ handle_one_xevent (struct x_display_info *dpyinfo, | |||
| 10679 | xkey.keycode = xev->detail; | 10745 | xkey.keycode = xev->detail; |
| 10680 | xkey.same_screen = True; | 10746 | xkey.same_screen = True; |
| 10681 | 10747 | ||
| 10748 | #ifdef USE_GTK | ||
| 10749 | if ((!x_gtk_use_native_input | ||
| 10750 | && x_filter_event (dpyinfo, (XEvent *) &xkey)) | ||
| 10751 | || (x_gtk_use_native_input | ||
| 10752 | && x_filter_event (dpyinfo, event))) | ||
| 10753 | { | ||
| 10754 | *finish = X_EVENT_DROP; | ||
| 10755 | goto XI_OTHER; | ||
| 10756 | } | ||
| 10757 | #else | ||
| 10682 | if (x_filter_event (dpyinfo, (XEvent *) &xkey)) | 10758 | if (x_filter_event (dpyinfo, (XEvent *) &xkey)) |
| 10683 | { | 10759 | { |
| 10684 | *finish = X_EVENT_DROP; | 10760 | *finish = X_EVENT_DROP; |
| 10685 | goto XI_OTHER; | 10761 | goto XI_OTHER; |
| 10686 | } | 10762 | } |
| 10687 | #endif | 10763 | #endif |
| 10764 | #endif | ||
| 10688 | 10765 | ||
| 10689 | #ifdef HAVE_XKB | 10766 | #ifdef HAVE_XKB |
| 10690 | if (dpyinfo->xkb_desc) | 10767 | if (dpyinfo->xkb_desc) |
| @@ -11421,6 +11498,20 @@ XTread_socket (struct terminal *terminal, struct input_event *hold_quit) | |||
| 11421 | if (current_finish == X_EVENT_GOTO_OUT) | 11498 | if (current_finish == X_EVENT_GOTO_OUT) |
| 11422 | break; | 11499 | break; |
| 11423 | } | 11500 | } |
| 11501 | |||
| 11502 | /* Now see if `xg_pending_quit_event' was set. */ | ||
| 11503 | if (xg_pending_quit_event.kind != NO_EVENT) | ||
| 11504 | { | ||
| 11505 | /* Check that the frame is still valid. It could have been | ||
| 11506 | deleted between now and the time the event was recorded. */ | ||
| 11507 | if (FRAME_LIVE_P (XFRAME (xg_pending_quit_event.frame_or_window))) | ||
| 11508 | /* Store that event into hold_quit and clear the pending quit | ||
| 11509 | event. */ | ||
| 11510 | *hold_quit = xg_pending_quit_event; | ||
| 11511 | |||
| 11512 | /* If the frame is invalid, just clear the event as well. */ | ||
| 11513 | xg_pending_quit_event.kind = NO_EVENT; | ||
| 11514 | } | ||
| 11424 | #endif /* USE_GTK */ | 11515 | #endif /* USE_GTK */ |
| 11425 | 11516 | ||
| 11426 | /* On some systems, an X bug causes Emacs to get no more events | 11517 | /* 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, | |||
| 11726 | 11817 | ||
| 11727 | #ifdef HAVE_X_I18N | 11818 | #ifdef HAVE_X_I18N |
| 11728 | if (w == XWINDOW (f->selected_window)) | 11819 | if (w == XWINDOW (f->selected_window)) |
| 11729 | if (FRAME_XIC (f)) | 11820 | xic_set_preeditarea (w, x, y); |
| 11730 | xic_set_preeditarea (w, x, y); | ||
| 11731 | #endif | 11821 | #endif |
| 11732 | } | 11822 | } |
| 11733 | 11823 | ||
| @@ -15299,6 +15389,10 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) | |||
| 15299 | dpyinfo->preferred_xim_style = STYLE_OFFTHESPOT; | 15389 | dpyinfo->preferred_xim_style = STYLE_OFFTHESPOT; |
| 15300 | else if (!strcmp (SSDATA (value), "root")) | 15390 | else if (!strcmp (SSDATA (value), "root")) |
| 15301 | dpyinfo->preferred_xim_style = STYLE_ROOT; | 15391 | dpyinfo->preferred_xim_style = STYLE_ROOT; |
| 15392 | #ifdef USE_GTK | ||
| 15393 | else if (!strcmp (SSDATA (value), "native")) | ||
| 15394 | dpyinfo->prefer_native_input = true; | ||
| 15395 | #endif | ||
| 15302 | } | 15396 | } |
| 15303 | #endif | 15397 | #endif |
| 15304 | } | 15398 | } |
| @@ -15846,4 +15940,10 @@ always uses gtk_window_move and ignores the value of this variable. */); | |||
| 15846 | This option is only effective when Emacs is built with XInput 2 | 15940 | This option is only effective when Emacs is built with XInput 2 |
| 15847 | support. */); | 15941 | support. */); |
| 15848 | Vx_scroll_event_delta_factor = make_float (1.0); | 15942 | Vx_scroll_event_delta_factor = make_float (1.0); |
| 15943 | |||
| 15944 | DEFVAR_BOOL ("x-gtk-use-native-input", x_gtk_use_native_input, | ||
| 15945 | doc: /* Non-nil means to use GTK for input method support. | ||
| 15946 | This provides better support for some modern input methods, and is | ||
| 15947 | only effective when Emacs is built with GTK. */); | ||
| 15948 | x_gtk_use_native_input = false; | ||
| 15849 | } | 15949 | } |
diff --git a/src/xterm.h b/src/xterm.h index a796f69ddc1..fc47fe0c6ea 100644 --- a/src/xterm.h +++ b/src/xterm.h | |||
| @@ -527,6 +527,10 @@ struct x_display_info | |||
| 527 | int xkb_event_type; | 527 | int xkb_event_type; |
| 528 | XkbDescPtr xkb_desc; | 528 | XkbDescPtr xkb_desc; |
| 529 | #endif | 529 | #endif |
| 530 | |||
| 531 | #ifdef USE_GTK | ||
| 532 | bool prefer_native_input; | ||
| 533 | #endif | ||
| 530 | }; | 534 | }; |
| 531 | 535 | ||
| 532 | #ifdef HAVE_X_I18N | 536 | #ifdef HAVE_X_I18N |
| @@ -643,6 +647,8 @@ struct x_output | |||
| 643 | GtkTooltip *ttip_widget; | 647 | GtkTooltip *ttip_widget; |
| 644 | GtkWidget *ttip_lbl; | 648 | GtkWidget *ttip_lbl; |
| 645 | GtkWindow *ttip_window; | 649 | GtkWindow *ttip_window; |
| 650 | |||
| 651 | GtkIMContext *im_context; | ||
| 646 | #endif /* USE_GTK */ | 652 | #endif /* USE_GTK */ |
| 647 | 653 | ||
| 648 | /* If >=0, a bitmap index. The indicated bitmap is used for the | 654 | /* If >=0, a bitmap index. The indicated bitmap is used for the |
| @@ -1340,6 +1346,10 @@ extern void x_session_close (void); | |||
| 1340 | #define STYLE_NONE (XIMPreeditNothing | XIMStatusNothing) | 1346 | #define STYLE_NONE (XIMPreeditNothing | XIMStatusNothing) |
| 1341 | #endif | 1347 | #endif |
| 1342 | 1348 | ||
| 1349 | #ifdef USE_GTK | ||
| 1350 | extern struct input_event xg_pending_quit_event; | ||
| 1351 | #endif | ||
| 1352 | |||
| 1343 | /* Is the frame embedded into another application? */ | 1353 | /* Is the frame embedded into another application? */ |
| 1344 | 1354 | ||
| 1345 | #define FRAME_X_EMBEDDED_P(f) (FRAME_X_OUTPUT(f)->explicit_parent != 0) | 1355 | #define FRAME_X_EMBEDDED_P(f) (FRAME_X_OUTPUT(f)->explicit_parent != 0) |