Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/backends/x11/meta-backend-x11.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "clutter/x11/clutter-x11.h"
#include "compositor/compositor-private.h"
#include "core/display-private.h"
#include "core/keybindings-private.h"
#include "meta/meta-cursor-tracker.h"
#include "meta/util.h"

Expand Down Expand Up @@ -425,6 +426,12 @@ handle_host_xevent (MetaBackend *backend,
meta_backend_notify_keymap_layout_group_changed (backend,
layout_group);
}
if (display &&
(xkb_ev->state.changed & (XkbModifierStateMask |
XkbModifierBaseMask)))
meta_display_process_zoom_modifier_state (display,
xkb_ev->state.mods,
xkb_ev->state.time);
break;
default:
break;
Expand Down Expand Up @@ -580,6 +587,14 @@ meta_backend_x11_post_init (MetaBackend *backend)
meta_fatal ("X server doesn't have the XKB extension, version %d.%d or newer\n",
XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION);

/* Make sure modifier state changes are among the selected
* XkbStateNotify details; the zoom modifier pointer grab depends on
* them (see meta_display_process_zoom_modifier_state). Only the
* listed detail bits are affected, existing selections remain. */
XkbSelectEventDetails (priv->xdisplay, XkbUseCoreKbd, XkbStateNotify,
XkbModifierStateMask | XkbModifierBaseMask,
XkbModifierStateMask | XkbModifierBaseMask);

META_BACKEND_CLASS (meta_backend_x11_parent_class)->post_init (backend);

monitor_manager = meta_backend_get_monitor_manager (backend);
Expand Down
13 changes: 13 additions & 0 deletions src/core/events.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,24 @@ meta_display_handle_event (MetaDisplay *display,
meta_display_a11y_zoom (display, FALSE);
}

/* Don't let the zoom modifier's release trigger a
* modifier-only keybinding (e.g. Super opening the menu) */
meta_keybindings_cancel_modifier_only (display);

/* On X11, hold the pointer for the duration of the scroll burst
* so smooth-scroll events can't leak to the client under the
* pointer. */
meta_display_zoom_scroll_grab_notify (display,
clutter_event_get_time (event));

bypass_wayland = bypass_clutter = TRUE;
goto out;
}
}

if (event->type == CLUTTER_BUTTON_PRESS)
meta_display_zoom_grab_break (display, clutter_event_get_time (event));

if (event->type != CLUTTER_DEVICE_ADDED &&
event->type != CLUTTER_DEVICE_REMOVED)
{
Expand Down
13 changes: 13 additions & 0 deletions src/core/keybindings-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ typedef struct
/* Alt+click button grabs */
ClutterModifierType window_grab_modifiers;
ClutterModifierType mouse_zoom_modifiers;

/* Active pointer grab held during zoom scroll bursts (X11) */
gboolean zoom_pointer_grabbed;
guint zoom_grab_timeout_id;
} MetaKeyBindingManager;

void meta_display_init_keys (MetaDisplay *display);
Expand All @@ -144,6 +148,15 @@ int meta_keybindings_get_mouse_zoom_modifiers (MetaDisplay *display);
ClutterModifierType meta_display_get_window_grab_modifiers (MetaDisplay *display);

uint meta_keybindings_get_ignored_modifier_mask (MetaDisplay *display);
void meta_keybindings_cancel_modifier_only (MetaDisplay *display);

void meta_display_process_zoom_modifier_state (MetaDisplay *display,
unsigned int mods,
guint32 timestamp);
void meta_display_zoom_scroll_grab_notify (MetaDisplay *display,
guint32 timestamp);
void meta_display_zoom_grab_break (MetaDisplay *display,
guint32 timestamp);

gboolean meta_prefs_add_keybinding (const char *name,
GSettings *settings,
Expand Down
111 changes: 111 additions & 0 deletions src/core/keybindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,12 @@ meta_display_shutdown_keys (MetaDisplay *display)
g_hash_table_destroy (keys->key_bindings_index);
g_hash_table_destroy (keys->key_bindings);

if (keys->zoom_grab_timeout_id != 0)
{
g_source_remove (keys->zoom_grab_timeout_id);
keys->zoom_grab_timeout_id = 0;
}

clear_active_keyboard_layouts (keys);
}

Expand Down Expand Up @@ -2474,6 +2480,7 @@ meta_keybindings_process_event (MetaDisplay *display,
case CLUTTER_BUTTON_RELEASE:
case CLUTTER_TOUCH_BEGIN:
case CLUTTER_TOUCH_END:
case CLUTTER_SCROLL:
modifier_key_only_pressed = FALSE;
return FALSE;

Expand Down Expand Up @@ -4207,6 +4214,110 @@ meta_keybindings_get_ignored_modifier_mask (MetaDisplay *display)
return keys->ignored_modifier_mask;
}

void
meta_keybindings_cancel_modifier_only (MetaDisplay *display)
{
modifier_key_only_pressed = FALSE;
}

#define ZOOM_SCROLL_GRAB_TIMEOUT_MS 1000

/* While zoom scrolling is in progress on X11 we hold an active grab on
* the pointer. The passive button 4/5 grabs only intercept the emulated
* legacy scroll button events; XInput2-aware clients scroll with the
* smooth-scroll XI_Motion events, which are delivered before any passive
* button grab can activate. Only an active device grab keeps them from
* reaching the client under the pointer.
*
* The grab is only held for the duration of a scroll burst (released on
* a short idle timeout, on a button press, or when the zoom modifier is
* released) so that modifier+click interactions keep working. A click
* swallowed by the grab cannot be re-injected: the physical press has
* already registered in the master device's button state, so a synthetic
* press is discarded as a duplicate.
*/

void
meta_display_zoom_grab_break (MetaDisplay *display,
guint32 timestamp)
{
MetaKeyBindingManager *keys = &display->key_binding_manager;

if (keys->zoom_grab_timeout_id != 0)
{
g_source_remove (keys->zoom_grab_timeout_id);
keys->zoom_grab_timeout_id = 0;
}

if (!keys->zoom_pointer_grabbed)
return;

/* If a compositor or window grab took over in the meantime, our
* grab was already replaced; ungrabbing would break theirs. */
if (display->event_route == META_EVENT_ROUTE_NORMAL)
meta_backend_ungrab_device (keys->backend,
META_VIRTUAL_CORE_POINTER_ID,
timestamp);
keys->zoom_pointer_grabbed = FALSE;
}

static gboolean
zoom_scroll_grab_timeout (gpointer data)
{
MetaDisplay *display = data;
MetaKeyBindingManager *keys = &display->key_binding_manager;

keys->zoom_grab_timeout_id = 0;
meta_display_zoom_grab_break (display, META_CURRENT_TIME);
return G_SOURCE_REMOVE;
}

void
meta_display_zoom_scroll_grab_notify (MetaDisplay *display,
guint32 timestamp)
{
MetaKeyBindingManager *keys = &display->key_binding_manager;

if (meta_is_wayland_compositor ())
return;

if (!keys->zoom_pointer_grabbed &&
display->event_route == META_EVENT_ROUTE_NORMAL &&
display->grab_op == META_GRAB_OP_NONE)
{
keys->zoom_pointer_grabbed =
meta_backend_grab_device (keys->backend,
META_VIRTUAL_CORE_POINTER_ID,
timestamp);
}

if (keys->zoom_pointer_grabbed)
{
if (keys->zoom_grab_timeout_id != 0)
g_source_remove (keys->zoom_grab_timeout_id);
keys->zoom_grab_timeout_id = g_timeout_add (ZOOM_SCROLL_GRAB_TIMEOUT_MS,
zoom_scroll_grab_timeout,
display);
}
}

/* Called on XkbStateNotify: release the scroll grab as soon as the zoom
* modifier is no longer held. */
void
meta_display_process_zoom_modifier_state (MetaDisplay *display,
unsigned int mods,
guint32 timestamp)
{
MetaKeyBindingManager *keys = &display->key_binding_manager;

if (!keys->zoom_pointer_grabbed)
return;

if ((mods & ~keys->ignored_modifier_mask) != keys->mouse_zoom_modifiers)
meta_display_zoom_grab_break (display, timestamp);
}


static void
init_builtin_key_bindings (MetaDisplay *display)
{
Expand Down