Blob Blame History Raw
From 73000f25e578b3ce6654fdf0d3da2ec3d9b95dd2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@redhat.com>
Date: Tue, 2 Nov 2021 09:20:11 +0100
Subject: [PATCH] desktop-icons: Fix stuck grab issue with rubber banding

The desktop icons extension can get into a state where the desktop no longer
takes mouse input.

This happens if a user starts a rubber banding operation and then drags
the mouse to somewhere on screen that has a pop up menu, and then pops
the menu up.

This commit addresses the bug by limiting the grab actor to the
backgrounds, and by explicitly ending the rubber banding operation
when one of the icons own menus is shown.

One side effect of limiting the grab actor to the backgrounds, is the
rubber banding code never gets to see motion outside of the backgrounds
anymore. In order to keep drag operations feeling fluid when the user moves
toward the edge of the screen, this commit also overrides the
grab helpers captured-event handler so those motion events keep coming.

We also start to end the rubber band if for any reason the grab it had
was released.
---
 extensions/desktop-icons/desktopGrid.js    |   1 +
 extensions/desktop-icons/desktopManager.js | 109 ++++++++++++---------
 extensions/desktop-icons/fileItem.js       |   1 +
 3 files changed, 67 insertions(+), 44 deletions(-)

diff --git a/extensions/desktop-icons/desktopGrid.js b/extensions/desktop-icons/desktopGrid.js
index 002803c..c7846bf 100644
--- a/extensions/desktop-icons/desktopGrid.js
+++ b/extensions/desktop-icons/desktopGrid.js
@@ -388,6 +388,7 @@ var DesktopGrid = GObject.registerClass({
     }
 
     _openMenu(x, y) {
+        Extension.desktopManager.endRubberBand();
         Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0);
         this._submenu.menu.removeAll();
         let templates = Extension.templateManager.getTemplates();
diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js
index 10e3ce0..08bc82b 100644
--- a/extensions/desktop-icons/desktopManager.js
+++ b/extensions/desktop-icons/desktopManager.js
@@ -81,6 +81,7 @@ var DesktopManager = GObject.registerClass({
         this._unixMode = null;
         this._writableByOthers = null;
         this._discreteGpuAvailable = false;
+        this._rubberBandActive = false;
 
         this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', () => this._recreateDesktopIcons());
         this._rubberBand = new St.Widget({ style_class: 'rubber-band' });
@@ -94,6 +95,20 @@ var DesktopManager = GObject.registerClass({
         this._mountRemovedId = this._mountMonitor.connect('mount-removed', (monitor, mount) => {
             this._recreateDesktopIcons(); });
 
+        let origCapturedEvent = this._grabHelper.onCapturedEvent;
+        this._grabHelper.onCapturedEvent = (event) => {
+            if (event.type() === Clutter.EventType.MOTION) {
+                /* We handle motion events from a captured event handler so we
+                 * we can see motion over actors that are on other parts of the
+                 * stage.
+                 */
+                this._handleMotion(event);
+                return Clutter.EVENT_STOP;
+            }
+
+            return origCapturedEvent.bind(this._grabHelper)(event);
+        };
+
         this._addDesktopIcons();
         this._monitorDesktopFolder();
 
@@ -133,57 +148,67 @@ var DesktopManager = GObject.registerClass({
         this._rubberBandInitialY = y;
         this._updateRubberBand(x, y);
         this._rubberBand.show();
-        this._grabHelper.grab({ actor: global.stage });
+        this._rubberBandActive = true;
+        this._grabHelper.grab({
+            actor: Main.layoutManager._backgroundGroup,
+            onUngrab: () => this.endRubberBand(false),
+        });
         Extension.lockActivitiesButton = true;
         this._stageReleaseEventId = global.stage.connect('button-release-event', (actor, event) => {
             this.endRubberBand();
         });
         this._rubberBandId = global.stage.connect('motion-event', (actor, event) => {
-            /* In some cases, when the user starts a rubberband selection and ends it
-             * (by releasing the left button) over a window instead of doing it over
-             * the desktop, the stage doesn't receive the "button-release" event.
-             * This happens currently with, at least, Dash to Dock extension, but
-             * it probably also happens with other applications or extensions.
-             * To fix this, we also end the rubberband selection if we detect mouse
-             * motion in the stage without the left button pressed during a
-             * rubberband selection.
-             *  */
-            let button = event.get_state();
-            if (!(button & Clutter.ModifierType.BUTTON1_MASK)) {
-                this.endRubberBand();
-                return;
-            }
-            [x, y] = event.get_coords();
-            this._updateRubberBand(x, y);
-            let x0, y0, x1, y1;
-            if (x >= this._rubberBandInitialX) {
-                x0 = this._rubberBandInitialX;
-                x1 = x;
-            } else {
-                x1 = this._rubberBandInitialX;
-                x0 = x;
-            }
-            if (y >= this._rubberBandInitialY) {
-                y0 = this._rubberBandInitialY;
-                y1 = y;
-            } else {
-                y1 = this._rubberBandInitialY;
-                y0 = y;
-            }
-            for (let [fileUri, fileItem] of this._fileItems) {
-                fileItem.emit('selected', true, true,
-                              fileItem.intersectsWith(x0, y0, x1 - x0, y1 - y0));
-            }
         });
     }
 
-    endRubberBand() {
+    _handleMotion(event) {
+        /* In some cases, when the user starts a rubberband selection and ends it
+         * (by releasing the left button) over a window instead of doing it over
+         * the desktop, the stage doesn't receive the "button-release" event.
+         * This happens currently with, at least, Dash to Dock extension, but
+         * it probably also happens with other applications or extensions.
+         * To fix this, we also end the rubberband selection if we detect mouse
+         * motion in the stage without the left button pressed during a
+         * rubberband selection.
+         *  */
+        let button = event.get_state();
+        if (!(button & Clutter.ModifierType.BUTTON1_MASK)) {
+            this.endRubberBand();
+            return;
+        }
+        let [x, y] = event.get_coords();
+        this._updateRubberBand(x, y);
+        let x0, y0, x1, y1;
+        if (x >= this._rubberBandInitialX) {
+            x0 = this._rubberBandInitialX;
+            x1 = x;
+        } else {
+            x1 = this._rubberBandInitialX;
+            x0 = x;
+        }
+        if (y >= this._rubberBandInitialY) {
+            y0 = this._rubberBandInitialY;
+            y1 = y;
+        } else {
+            y1 = this._rubberBandInitialY;
+            y0 = y;
+        }
+        for (let [fileUri, fileItem] of this._fileItems) {
+            fileItem.emit('selected', true, true,
+                fileItem.intersectsWith(x0, y0, x1 - x0, y1 - y0));
+        }
+    }
+
+    endRubberBand(ungrab=true) {
+        if (!this._rubberBandActive)
+             return;
+
+        this._rubberBandActive = false;
         this._rubberBand.hide();
         Extension.lockActivitiesButton = false;
-        this._grabHelper.ungrab();
-        global.stage.disconnect(this._rubberBandId);
+        if (ungrab)
+            this._grabHelper.ungrab();
         global.stage.disconnect(this._stageReleaseEventId);
-        this._rubberBandId = 0;
         this._stageReleaseEventId = 0;
 
         this._selection = new Set([...this._selection, ...this._currentSelection]);
@@ -825,10 +850,6 @@ var DesktopManager = GObject.registerClass({
             global.stage.disconnect(this._stageReleaseEventId);
         this._stageReleaseEventId = 0;
 
-        if (this._rubberBandId)
-            global.stage.disconnect(this._rubberBandId);
-        this._rubberBandId = 0;
-
         this._rubberBand.destroy();
 
         if (this._queryFileInfoCancellable)
diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js
index 1e8ea89..37ee54d 100644
--- a/extensions/desktop-icons/fileItem.js
+++ b/extensions/desktop-icons/fileItem.js
@@ -747,6 +747,7 @@ var FileItem = GObject.registerClass({
     }
 
     _onPressButton(actor, event) {
+        Extension.desktopManager.endRubberBand();
         this._updateClickState(event);
         let button = event.get_button();
         if (button == 3) {
-- 
2.31.1