From 0d2cad6c8f3fa66d4d173cdfa80811057bba6552 Mon Sep 17 00:00:00 2001 From: woongzeyi Date: Thu, 11 Jun 2026 04:06:56 +0800 Subject: [PATCH] gestures: don't disable swipe trackers mid-gesture (GNOME 49/50 overview hang) On GNOME 49+ SwipeTracker.set enabled(false) calls _interrupt() when the tracker is State.SCROLLING, emitting a synthetic end that drives the overview into an illegal state transition (Invalid overview shown transition ...), hanging the overview and freezing the shell. Guard every mid-gesture tracker disable: track an inGesture flag, defer the overview hidden disable to idle (skipped while a gesture is live), and skip the pillSwipeTimer disable during a gesture. Co-Authored-By: Claude Opus 4.8 --- gestures.js | 23 +++++++++++++++++++++-- patches.js | 9 ++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/gestures.js b/gestures.js index ecad1e38..46667fe2 100644 --- a/gestures.js +++ b/gestures.js @@ -16,8 +16,15 @@ const DIRECTIONS = { let vy, time, vState, navigator, direction, signals; let handoffToOverview = false; +// true between BEGIN and END/CANCEL of a PaperWM-managed swipe. Used to defer +// any tracker disabling that would otherwise interrupt a live overview gesture. +let inGesture = false; // 1 is natural scrolling, -1 is unnatural let natural = 1; + +export function isInGesture() { + return inGesture; +} export let gliding = false; // exported let touchpadSettings; @@ -42,9 +49,19 @@ export function enable(extension) { * ensure swipe trackers are reset. */ signals.connect(Main.overview, 'hidden', () => { - if (gestureEnabled()) { - swipeTrackersEnable(false); + if (!gestureEnabled()) { + return; } + // Defer to idle so any in-flight overview swipe animation settles + // (tracker -> State.NONE) before we disable. On GNOME 50 disabling a + // SCROLLING tracker calls _interrupt() -> synthetic 'end' -> illegal + // overview state transition -> stuck/hang. + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + if (!inGesture && gestureEnabled()) { + swipeTrackersEnable(false); + } + return GLib.SOURCE_REMOVE; + }); }); /** @@ -88,6 +105,7 @@ export function enable(extension) { natural = touchpadSettings.get_boolean("natural-scroll") ? 1 : -1; direction = undefined; handoffToOverview = false; + inGesture = true; navigator = Navigator.getNavigator(); navigator.connect('destroy', () => { vState = -1; @@ -147,6 +165,7 @@ export function enable(extension) { return Clutter.EVENT_PROPAGATE; case Clutter.TouchpadGesturePhase.CANCEL: case Clutter.TouchpadGesturePhase.END: + inGesture = false; // If this finger count should be handled by GNOME, never consume // completion here (prevents overview from getting stuck in 4-finger mode). if (shouldPropagate(fingers)) { diff --git a/patches.js b/patches.js index 6bd8a821..a63d3263 100644 --- a/patches.js +++ b/patches.js @@ -16,7 +16,7 @@ import * as WindowManager from 'resource:///org/gnome/shell/ui/windowManager.js' import * as WindowPreview from 'resource:///org/gnome/shell/ui/windowPreview.js'; import * as Screenshot from 'resource:///org/gnome/shell/ui/screenshot.js'; -import { Utils, Tiling, Scratch, Settings, OverviewLayout } from './imports.js'; +import { Utils, Tiling, Scratch, Settings, OverviewLayout, Gestures } from './imports.js'; /** Some of Gnome Shell's default behavior is really sub-optimal when using @@ -153,6 +153,13 @@ export function setupOverrides() { const reset = () => { // gnome windows switch animation time = 250, do that plus a little more pillSwipeTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { + // Don't disable mid-gesture: on GNOME 50 disabling a + // SCROLLING tracker fires _interrupt() -> synthetic 'end' + // -> illegal overview transition -> hang. + if (Gestures.isInGesture?.()) { + pillSwipeTimer = null; + return false; + } swipeTrackers.forEach(t => { t.enabled = false; });