From 0dbe0fd4104f4a58908324c1ead365461a4daa16 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 5 Aug 2022 10:18:18 +0800 Subject: [PATCH] Improve input extension focus handling with multiple master devices * src/xterm.c (x_cache_xi_devices): Initialize device fields to 0. (xi_handle_focus_change, xi_focus_handle_for_device) (xi_handle_delete_frame): New functions; store focus information per-device instead. (x_detect_focus_change): Handle GenericEvents that way instead. (handle_one_xevent): Don't cache XI devices on DeviceChanged. (x_free_frame_resources): Clear any frame focus information. * src/xterm.h (struct xi_device_t): New fields for focus tracking. Add comments describing fields. --- src/xterm.c | 235 ++++++++++++++++++++++++++++++++++++++++++---------- src/xterm.h | 36 +++++++- 2 files changed, 226 insertions(+), 45 deletions(-) diff --git a/src/xterm.c b/src/xterm.c index 2a453099ee7..ebd27aea069 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -5394,7 +5394,7 @@ xi_populate_device_from_info (struct xi_device_t *xi_device, static void x_cache_xi_devices (struct x_display_info *dpyinfo) { - int ndevices, actual_devices; + int ndevices, actual_devices, i; XIDeviceInfo *infos; actual_devices = 0; @@ -5411,9 +5411,9 @@ x_cache_xi_devices (struct x_display_info *dpyinfo) return; } - dpyinfo->devices = xmalloc (sizeof *dpyinfo->devices * ndevices); + dpyinfo->devices = xzalloc (sizeof *dpyinfo->devices * ndevices); - for (int i = 0; i < ndevices; ++i) + for (i = 0; i < ndevices; ++i) { if (infos[i].enabled) xi_populate_device_from_info (&dpyinfo->devices[actual_devices++], @@ -12446,6 +12446,178 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction, return unbind_to (base, Qnil); } +#ifdef HAVE_XINPUT2 + +/* Since the input extension assigns a keyboard focus to each master + device, there is no longer a 1:1 correspondence between the + selected frame and the focus frame immediately after the keyboard + focus is switched to a given frame. This situation is handled by + keeping track of each master device's focus frame, the time of the + last interaction with that frame, and always keeping the focus on + the most recently selected frame. */ + +static void +xi_handle_focus_change (struct x_display_info *dpyinfo) +{ + struct input_event ie; + struct frame *focus, *new; + struct xi_device_t *device, *source; + ptrdiff_t i; + Time time; +#ifdef USE_GTK + struct x_output *output; + GtkWidget *widget; +#endif + + focus = dpyinfo->x_focus_frame; + new = NULL; + time = 0; + + for (i = 0; i < dpyinfo->num_devices; ++i) + { + device = &dpyinfo->devices[i]; + + if (device->focus_frame + && device->focus_frame_time > time) + { + new = device->focus_frame; + time = device->focus_frame_time; + source = device; + } + + if (device->focus_implicit_frame + && device->focus_implicit_time > time) + { + new = device->focus_implicit_frame; + time = device->focus_implicit_time; + source = device; + } + } + + if (new != focus && focus) + { +#ifdef HAVE_X_I18N + if (FRAME_XIC (focus)) + XUnsetICFocus (FRAME_XIC (focus)); +#endif + +#ifdef USE_GTK + output = FRAME_X_OUTPUT (focus); + + if (x_gtk_use_native_input) + { + gtk_im_context_focus_out (output->im_context); + gtk_im_context_set_client_window (output->im_context, + NULL); + } +#endif + + EVENT_INIT (ie); + ie.kind = FOCUS_OUT_EVENT; + XSETFRAME (ie.frame_or_window, focus); + + kbd_buffer_store_event (&ie); + } + + if (new != focus && new) + { + +#ifdef HAVE_X_I18N + if (FRAME_XIC (new)) + XSetICFocus (FRAME_XIC (new)); +#endif + +#ifdef USE_GTK + output = FRAME_X_OUTPUT (new); + + if (x_gtk_use_native_input) + { + widget = FRAME_GTK_OUTER_WIDGET (new); + + gtk_im_context_focus_in (output->im_context); + gtk_im_context_set_client_window (output->im_context, + gtk_widget_get_window (widget)); + } +#endif + + EVENT_INIT (ie); + ie.kind = FOCUS_IN_EVENT; + ie.device = source->name; + XSETFRAME (ie.frame_or_window, new); + + kbd_buffer_store_event (&ie); + } + + x_new_focus_frame (dpyinfo, new); +} + +static void +xi_focus_handle_for_device (struct x_display_info *dpyinfo, + struct frame *mentioned_frame, + XIEvent *base_event) +{ + struct xi_device_t *device; + XIEnterEvent *event; + + /* XILeaveEvent, XIFocusInEvent, etc are just synonyms for + XIEnterEvent. */ + event = (XIEnterEvent *) base_event; + device = xi_device_from_id (dpyinfo, event->deviceid); + + if (!device) + return; + + switch (event->evtype) + { + case XI_FocusIn: + device->focus_frame = mentioned_frame; + device->focus_frame_time = event->time; + break; + + case XI_FocusOut: + device->focus_frame = NULL; + break; + + case XI_Enter: + if (!event->focus) + break; + + device->focus_implicit_frame = mentioned_frame; + device->focus_implicit_time = event->time; + break; + + case XI_Leave: + if (!event->focus) + break; + + device->focus_implicit_frame = NULL; + break; + } + + xi_handle_focus_change (dpyinfo); +} + +static void +xi_handle_delete_frame (struct x_display_info *dpyinfo, + struct frame *f) +{ + struct xi_device_t *device; + ptrdiff_t i; + + for (i = 0; i < dpyinfo->num_devices; ++i) + { + device = &dpyinfo->devices[i]; + + if (device->focus_frame == f) + device->focus_frame = NULL; + + if (device->focus_implicit_frame == f) + device->focus_implicit_frame = NULL; + } +} + +#endif + /* The focus may have changed. Figure out if it is a real focus change, by checking both FocusIn/Out and Enter/LeaveNotify events. @@ -12478,33 +12650,9 @@ x_detect_focus_change (struct x_display_info *dpyinfo, struct frame *frame, #ifdef HAVE_XINPUT2 case GenericEvent: - { - XIEvent *xi_event = event->xcookie.data; - XIEnterEvent *enter_or_focus = event->xcookie.data; - - struct frame *focus_frame = dpyinfo->x_focus_event_frame; - int focus_state - = focus_frame ? focus_frame->output_data.x->focus_state : 0; - - if (xi_event->evtype == XI_FocusIn - || xi_event->evtype == XI_FocusOut) - x_focus_changed ((xi_event->evtype == XI_FocusIn - ? FocusIn : FocusOut), - ((enter_or_focus->detail - == XINotifyPointer) - ? FOCUS_IMPLICIT : FOCUS_EXPLICIT), - dpyinfo, frame, bufp); - else if ((xi_event->evtype == XI_Enter - || xi_event->evtype == XI_Leave) - && (enter_or_focus->detail != XINotifyInferior) - && enter_or_focus->focus - && !(focus_state & FOCUS_EXPLICIT)) - x_focus_changed ((xi_event->evtype == XI_Enter - ? FocusIn : FocusOut), - FOCUS_IMPLICIT, - dpyinfo, frame, bufp); - break; - } + xi_focus_handle_for_device (dpyinfo, frame, + event->xcookie.data); + break; #endif case FocusIn: @@ -21912,7 +22060,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (n_disabled) { ndevices = 0; - devices = xmalloc (sizeof *devices * dpyinfo->num_devices); + devices = xzalloc (sizeof *devices * dpyinfo->num_devices); for (i = 0; i < dpyinfo->num_devices; ++i) { @@ -21992,7 +22140,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, if (n_disabled) { ndevices = 0; - devices = xmalloc (sizeof *devices * dpyinfo->num_devices); + devices = xzalloc (sizeof *devices * dpyinfo->num_devices); for (i = 0; i < dpyinfo->num_devices; ++i) { @@ -22027,6 +22175,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, dpyinfo->num_devices = ndevices; } + /* Now that the device hierarchy has been changed, + recompute focus. */ + xi_handle_focus_change (dpyinfo); + goto XI_OTHER; } @@ -22044,17 +22196,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, device = xi_device_from_id (dpyinfo, device_changed->deviceid); - if (!device) - { - /* An existing device might have been enabled. */ - x_cache_xi_devices (dpyinfo); - - /* Now try to find the device again, in case it was - just enabled. */ - device = xi_device_from_id (dpyinfo, device_changed->deviceid); - } - - /* If it wasn't enabled, then stop handling this event. */ + /* If the device isn't enabled, then stop handling this + event. A HierarchyChanged event will be sent if it + is enabled afterwards. */ if (!device) goto XI_OTHER; @@ -26219,6 +26363,11 @@ x_free_frame_resources (struct frame *f) block_input (); +#ifdef HAVE_XINPUT2 + /* Remove any record of this frame being focused. */ + xi_handle_delete_frame (dpyinfo, f); +#endif + /* If a display connection is dead, don't try sending more commands to the X server. */ if (dpyinfo->display) diff --git a/src/xterm.h b/src/xterm.h index c1a944d3cd6..7be0f2ede65 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -238,23 +238,50 @@ struct xi_touch_point_t struct xi_device_t { + /* The numerical ID of this device. */ int device_id; + #ifdef HAVE_XINPUT2_1 + /* The number of scroll valuators in `valuators'. */ int scroll_valuator_count; #endif + + /* Whether or not the device is grabbed and its use. */ int grab, use; + #ifdef HAVE_XINPUT2_2 + /* Whether or not this device is a direct touch device. */ bool direct_p; #endif #ifdef HAVE_XINPUT2_1 + /* An array of scroll valuators Emacs knows about. */ struct xi_scroll_valuator_t *valuators; #endif + #ifdef HAVE_XINPUT2_2 + /* An array of in-progress touchscreen events. */ struct xi_touch_point_t *touchpoints; #endif + /* The name of this device. */ Lisp_Object name; + + /* The time at which `focus_frame' became the keyboard focus (only + applies to master devices). */ + Time focus_frame_time; + + /* The frame that is currently this device's keyboard focus, or + NULL. */ + struct frame *focus_frame; + + /* The time at which `focus_frame' became the implicit keyboard + focus. */ + Time focus_implicit_time; + + /* The frame that is currently this device's implicit keyboard + focus, or NULL. */ + struct frame *focus_implicit_frame; }; #endif @@ -482,7 +509,10 @@ struct x_display_info /* The last frame mentioned in a FocusIn or FocusOut event. This is separate from x_focus_frame, because whether or not LeaveNotify events cause us to lose focus depends on whether or not we have - received a FocusIn event for it. */ + received a FocusIn event for it. + + This field is not used when the input extension is being + utilized. */ struct frame *x_focus_event_frame; /* The frame which currently has the visual highlight, and should get @@ -1101,7 +1131,9 @@ struct x_output /* Keep track of focus. May be EXPLICIT if we received a FocusIn for this frame, or IMPLICIT if we received an EnterNotify. - FocusOut and LeaveNotify clears EXPLICIT/IMPLICIT. */ + FocusOut and LeaveNotify clears EXPLICIT/IMPLICIT. + + Not used when the input extension is being utilized. */ int focus_state; /* The offset we need to add to compensate for type A WMs. */ -- 2.39.2