Blame SOURCES/0001-magnifier-Request-window-relative-coordinates-for-fo.patch

553d37
From ea7e7acd45e428cc17306de2bf65730c90d7e118 Mon Sep 17 00:00:00 2001
553d37
From: Sebastian Keller <skeller@gnome.org>
553d37
Date: Mon, 23 May 2022 23:01:23 +0200
553d37
Subject: [PATCH] magnifier: Request window-relative coordinates for
553d37
 focus/caret events
553d37
553d37
Absolute screen coordinates are impossible for Wayland clients to
553d37
provide, because the clients don't know where the window is positioned.
553d37
Some clients, such as the ones using GTK 3 were providing window
553d37
relative coordinates even when screen coordinates were requested,
553d37
while others, such as GTK 4 clients, were just returning an error for
553d37
caret events or also window-relative coordinates for focus events.
553d37
553d37
So for this to work on Wayland we have to request window-relative
553d37
coordinates and translate them to the current focus window.
553d37
553d37
To ensure the correct coordinates, we have to only consider events
553d37
coming from the current focus window. All other events are filtered out
553d37
now. As a side effect this also fixes the magnifier always jumping
553d37
to a terminal cursor whenever there was some output, even if the window
553d37
was not focused.
553d37
553d37
This also needs some special handling for events coming from the shell
553d37
itself, which should not be translated to the focus window either. As
553d37
another side effect this fixes another bug that was caused by these
553d37
events already including scaling and getting scaled again.
553d37
553d37
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5509
553d37
Part-of:
553d37
<https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2301>
553d37
---
553d37
 js/ui/magnifier.js | 77 +++++++++++++++++++++++++++++++++++++++++-----
553d37
 1 file changed, 70 insertions(+), 7 deletions(-)
553d37
553d37
diff --git a/js/ui/magnifier.js b/js/ui/magnifier.js
553d37
index 4c2e88f1a..9813664be 100644
553d37
--- a/js/ui/magnifier.js
553d37
+++ b/js/ui/magnifier.js
553d37
@@ -789,21 +789,81 @@ var ZoomRegion = class ZoomRegion {
553d37
         }
553d37
     }
553d37
 
553d37
+    _convertExtentsToScreenSpace(accessible, extents) {
553d37
+        const toplevelWindowTypes = new Set([
553d37
+            Atspi.Role.FRAME,
553d37
+            Atspi.Role.DIALOG,
553d37
+            Atspi.Role.WINDOW,
553d37
+        ]);
553d37
+
553d37
+        try {
553d37
+            let app = null;
553d37
+            let parentWindow = null;
553d37
+            let iter = accessible;
553d37
+            while (iter) {
553d37
+                if (iter.get_role() === Atspi.Role.APPLICATION) {
553d37
+                    app = iter;
553d37
+                    /* This is the last Accessible we are interested in */
553d37
+                    break;
553d37
+                } else if (toplevelWindowTypes.has(iter.get_role())) {
553d37
+                    parentWindow = iter;
553d37
+                }
553d37
+                iter = iter.get_parent();
553d37
+            }
553d37
+
553d37
+            /* We don't want to translate our own events to the focus window.
553d37
+             * They are also already scaled by clutter before being sent, so
553d37
+             * we don't need to do that here either. */
553d37
+            if (app && app.get_name() === 'gnome-shell')
553d37
+                return extents;
553d37
+
553d37
+            /* Only events from the focused widget of the focused window. Some
553d37
+             * widgets seem to claim to have focus when the window does not so
553d37
+             * check both. */
553d37
+            const windowActive = parentWindow &&
553d37
+                parentWindow.get_state_set().contains(Atspi.StateType.ACTIVE);
553d37
+            const accessibleFocused =
553d37
+                accessible.get_state_set().contains(Atspi.StateType.FOCUSED);
553d37
+            if (!windowActive || !accessibleFocused)
553d37
+                return null;
553d37
+        } catch (e) {
553d37
+            throw new Error(`Failed to validate parent window: ${e}`);
553d37
+        }
553d37
+
553d37
+        const focusWindowRect = global.display.focus_window?.get_frame_rect();
553d37
+        if (!focusWindowRect)
553d37
+            return null;
553d37
+
553d37
+        const scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
553d37
+        const screenSpaceExtents = new Atspi.Rect({
553d37
+            x: focusWindowRect.x + (scaleFactor * extents.x),
553d37
+            y: focusWindowRect.y + (scaleFactor * extents.y),
553d37
+            width: scaleFactor * extents.width,
553d37
+            height: scaleFactor * extents.height,
553d37
+        });
553d37
+
553d37
+        return screenSpaceExtents;
553d37
+    }
553d37
+
553d37
     _updateFocus(caller, event) {
553d37
         let component = event.source.get_component_iface();
553d37
         if (!component || event.detail1 != 1)
553d37
             return;
553d37
         let extents;
553d37
         try {
553d37
-            extents = component.get_extents(Atspi.CoordType.SCREEN);
553d37
+            extents = component.get_extents(Atspi.CoordType.WINDOW);
553d37
+            extents = this._convertExtentsToScreenSpace(event.source, extents);
553d37
+            if (!extents)
553d37
+                return;
553d37
         } catch (e) {
553d37
             log(`Failed to read extents of focused component: ${e.message}`);
553d37
             return;
553d37
         }
553d37
 
553d37
-        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
553d37
-        let [xFocus, yFocus] = [(extents.x + (extents.width / 2)) * scaleFactor,
553d37
-                                (extents.y + (extents.height / 2)) * scaleFactor];
553d37
+        const [xFocus, yFocus] = [
553d37
+            extents.x + (extents.width / 2),
553d37
+            extents.y + (extents.height / 2),
553d37
+        ];
553d37
 
553d37
         if (this._xFocus !== xFocus || this._yFocus !== yFocus) {
553d37
             [this._xFocus, this._yFocus] = [xFocus, yFocus];
553d37
@@ -817,14 +877,17 @@ var ZoomRegion = class ZoomRegion {
553d37
             return;
553d37
         let extents;
553d37
         try {
553d37
-            extents = text.get_character_extents(text.get_caret_offset(), 0);
553d37
+            extents = text.get_character_extents(text.get_caret_offset(),
553d37
+                Atspi.CoordType.WINDOW);
553d37
+            extents = this._convertExtentsToScreenSpace(text, extents);
553d37
+            if (!extents)
553d37
+                return;
553d37
         } catch (e) {
553d37
             log(`Failed to read extents of text caret: ${e.message}`);
553d37
             return;
553d37
         }
553d37
 
553d37
-        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
553d37
-        let [xCaret, yCaret] = [extents.x * scaleFactor, extents.y * scaleFactor];
553d37
+        const [xCaret, yCaret] = [extents.x, extents.y];
553d37
 
553d37
         // Ignore event(s) if the caret size is none (0x0). This happens a lot if
553d37
         // the cursor offset can't be translated into a location. This is a work
553d37
-- 
553d37
2.38.1
553d37