File: upstream_51c0880f_wayland-close-popups-upon-window-activation.patch

package info (click to toggle)
kwin 4%3A6.3.6-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 56,600 kB
  • sloc: cpp: 241,670; xml: 3,228; javascript: 2,214; ansic: 775; sh: 67; python: 15; makefile: 8
file content (151 lines) | stat: -rw-r--r-- 5,723 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
From 51c0880f47ecd2e29ceb61e9dd9b30a535d18ff8 Mon Sep 17 00:00:00 2001
From: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
Date: Wed, 9 Jul 2025 12:22:28 +0000
Subject: [PATCH] wayland: close popups upon window activation

Popups should grab all input on Wayland, however there are numerous
(deliberate) ways to get outside the grab, such as a shortcut that opens
a new window or pressing alt-tab.

If a popup is active when the focus changes we are left in a state where
the new window visibly has focus and appears on top of the popup's main
window, but the popup is still visible with an input grab. This is a
broken state.

This patch cancels popups if the focus window changes for whatever
reason.

Cancelling a popup also destroys the window server side rather than
waiting for the client to handle the popupDone so there is no racey
aspect for the next input.

BUG: 497075


(cherry picked from commit 43f7621eb1ef22704c4c12c21c29945bffa37e47)

Co-authored-by: David Edmundson <kde@davidedmundson.co.uk>
---
 autotests/integration/xdgshellwindow_test.cpp | 45 +++++++++++++++++++
 src/popup_input_filter.cpp                    |  7 +++
 src/popup_input_filter.h                      |  2 +-
 3 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/autotests/integration/xdgshellwindow_test.cpp b/autotests/integration/xdgshellwindow_test.cpp
index b7d3946b97f..0903043808d 100644
--- a/autotests/integration/xdgshellwindow_test.cpp
+++ b/autotests/integration/xdgshellwindow_test.cpp
@@ -13,6 +13,7 @@
 #include "decorations/decorationbridge.h"
 #include "decorations/settings.h"
 #include "pointer_input.h"
+
 #include "virtualdesktops.h"
 #include "wayland/clientconnection.h"
 #include "wayland/display.h"
@@ -41,6 +42,8 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <linux/input.h>
+
 #include <csignal>
 
 using namespace KWin;
@@ -111,6 +114,7 @@ private Q_SLOTS:
     void testCloseInactiveModal();
     void testClosePopupOnParentUnmapped();
     void testPopupWithDismissedParent();
+    void testPopupDismissedOnFocusChange();
     void testMinimumSize();
     void testNoMinimumSize();
     void testMaximumSize();
@@ -2417,6 +2421,47 @@ void TestXdgShellWindow::testPopupWithDismissedParent()
     QVERIFY(grandChildDoneSpy.wait());
 }
 
+void TestXdgShellWindow::testPopupDismissedOnFocusChange()
+{
+    // This test verifies that a popup window will be dismissed when the focus changes.
+
+    std::unique_ptr<KWayland::Client::Surface> parentSurface = Test::createSurface();
+    std::unique_ptr<Test::XdgToplevel> parentToplevel = Test::createXdgToplevelSurface(parentSurface.get());
+    std::unique_ptr<KWayland::Client::Pointer> pointer(Test::waylandSeat()->createPointer());
+    Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(200, 200), Qt::cyan);
+    QVERIFY(parent);
+
+    QSignalSpy buttonSpy(pointer.get(), &KWayland::Client::Pointer::buttonStateChanged);
+    input()->pointer()->warp(parent->frameGeometry().center());
+    // simulate press
+    quint32 timestamp = 1;
+    Test::pointerButtonPressed(BTN_LEFT, timestamp++);
+    QVERIFY(buttonSpy.wait());
+
+    std::unique_ptr<Test::XdgPositioner> positioner = Test::createXdgPositioner();
+    positioner->set_size(10, 10);
+    positioner->set_anchor_rect(10, 10, 10, 10);
+
+    std::unique_ptr<KWayland::Client::Surface> childSurface = Test::createSurface();
+    std::unique_ptr<Test::XdgPopup> popup = Test::createXdgPopupSurface(childSurface.get(), parentToplevel->xdgSurface(), positioner.get());
+    popup->grab(*Test::waylandSeat(), buttonSpy.first().first().value<quint32>());
+    QPointer<Window> child = Test::renderAndWaitForShown(childSurface.get(), QSize(10, 10), Qt::cyan);
+    QVERIFY(child);
+
+    QSignalSpy popupDismissedSpy(popup.get(), &Test::XdgPopup::doneReceived);
+
+    // create another toplevel that gets focus
+    std::unique_ptr<KWayland::Client::Surface> otherSurface = Test::createSurface();
+    std::unique_ptr<Test::XdgToplevel> otherToplevel = Test::createXdgToplevelSurface(otherSurface.get());
+    Window *other = Test::renderAndWaitForShown(otherSurface.get(), QSize(200, 200), Qt::cyan);
+    QVERIFY(other);
+
+    workspace()->setActiveWindow(other);
+
+    QVERIFY(popupDismissedSpy.wait());
+    QVERIFY(!child); // and the server-side window closed immediately too
+}
+
 void TestXdgShellWindow::testMinimumSize()
 {
     // NOTE: a minimum size of 20px is forced by the compositor
diff --git a/src/popup_input_filter.cpp b/src/popup_input_filter.cpp
index 50e47d07021..0fd1d602c3e 100644
--- a/src/popup_input_filter.cpp
+++ b/src/popup_input_filter.cpp
@@ -24,6 +24,7 @@ PopupInputFilter::PopupInputFilter()
     , InputEventFilter(InputFilterOrder::Popup)
 {
     connect(workspace(), &Workspace::windowAdded, this, &PopupInputFilter::handleWindowAdded);
+    connect(workspace(), &Workspace::windowActivated, this, &PopupInputFilter::handleWindowFocusChanged);
 }
 
 void PopupInputFilter::handleWindowAdded(Window *window)
@@ -48,6 +49,12 @@ void PopupInputFilter::handleWindowAdded(Window *window)
     }
 }
 
+void PopupInputFilter::handleWindowFocusChanged()
+{
+    // user focussed a window through another mechanism such as a shortcut
+    cancelPopups();
+}
+
 bool PopupInputFilter::pointerButton(PointerButtonEvent *event)
 {
     if (m_popupWindows.isEmpty()) {
diff --git a/src/popup_input_filter.h b/src/popup_input_filter.h
index 0950666fa8b..622f62b5ccf 100644
--- a/src/popup_input_filter.h
+++ b/src/popup_input_filter.h
@@ -27,7 +27,7 @@ public:
 
 private:
     void handleWindowAdded(Window *client);
-
+    void handleWindowFocusChanged();
     void focus(Window *popup);
     void cancelPopups();
 
-- 
GitLab