Skip to content

Commit b5ab25a

Browse files
committed
fix: guard PanelPopup close behavior against transient X11 grab focus changes
修复:防止PanelPopup在X11键盘抓取期间因瞬时失焦被误关闭 1. Detect X11 FocusOut(GRAB)/FocusIn(UNGRAB) in PopupWindow and expose grab-transition signals. 2. Keep close-on-inactive behavior, but defer close during grab transition and cancel when ungrab focus returns. 3. Handle active-state transitions only for the current popup owner instance to avoid cross-instance interference. 1. 在PopupWindow中识别X11的FocusOut(GRAB)/FocusIn(UNGRAB),并向QML暴露抓取过渡信号。 2. 保留失焦关闭语义,但在抓取过渡期间延迟关闭,并在Ungrab后焦点恢复时取消关闭。 3. 仅由当前popup owner实例处理active状态变化,避免共享popupWindow下多个实例互相干扰。 PMS: BUG-301109
1 parent 1d340e6 commit b5ab25a

File tree

3 files changed

+119
-1
lines changed

3 files changed

+119
-1
lines changed

frame/popupwindow.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "popupwindow.h"
66

7+
#include <xcb/xcb.h>
8+
79
DS_BEGIN_NAMESPACE
810
PopupWindow::PopupWindow(QWindow *parent)
911
: QQuickApplicationWindow(parent)
@@ -22,6 +24,12 @@ PopupWindow::PopupWindow(QWindow *parent)
2224

2325
connect(this, &QWindow::screenChanged, this, setMaximumSize);
2426
setMaximumSize();
27+
28+
connect(this, &QWindow::visibleChanged, this, [this]() {
29+
if (!isVisible()) {
30+
setX11GrabFocusTransition(false);
31+
}
32+
});
2533
}
2634

2735
void PopupWindow::mouseReleaseEvent(QMouseEvent *event)
@@ -49,4 +57,42 @@ void PopupWindow::mouseMoveEvent(QMouseEvent *event)
4957
return QQuickApplicationWindow::mouseMoveEvent(event);
5058
}
5159

60+
void PopupWindow::setX11GrabFocusTransition(bool transition)
61+
{
62+
if (m_x11GrabFocusTransition == transition) {
63+
return;
64+
}
65+
66+
m_x11GrabFocusTransition = transition;
67+
Q_EMIT x11GrabFocusTransitionChanged();
68+
}
69+
70+
bool PopupWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result)
71+
{
72+
if (eventType == QByteArrayLiteral("xcb_generic_event_t")) {
73+
auto *event = static_cast<xcb_generic_event_t *>(message);
74+
if (!event) {
75+
return QQuickApplicationWindow::nativeEvent(eventType, message, result);
76+
}
77+
78+
const uint8_t responseType = event->response_type & ~0x80;
79+
if (responseType == XCB_FOCUS_IN || responseType == XCB_FOCUS_OUT) {
80+
auto *focusEvent = reinterpret_cast<xcb_focus_in_event_t *>(event);
81+
if (focusEvent->event == static_cast<xcb_window_t>(winId())) {
82+
if (responseType == XCB_FOCUS_OUT && focusEvent->mode == XCB_NOTIFY_MODE_GRAB) {
83+
setX11GrabFocusTransition(true);
84+
Q_EMIT x11FocusOutByGrab();
85+
} else if (responseType == XCB_FOCUS_IN && focusEvent->mode == XCB_NOTIFY_MODE_UNGRAB) {
86+
Q_EMIT x11FocusInByUngrab();
87+
setX11GrabFocusTransition(false);
88+
} else if (responseType == XCB_FOCUS_IN && focusEvent->mode != XCB_NOTIFY_MODE_GRAB) {
89+
setX11GrabFocusTransition(false);
90+
}
91+
}
92+
}
93+
}
94+
95+
return QQuickApplicationWindow::nativeEvent(eventType, message, result);
96+
}
97+
5298
DS_END_NAMESPACE

frame/popupwindow.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,28 @@ class PopupWindow : public QQuickApplicationWindow
1212
{
1313
Q_OBJECT
1414
Q_PROPERTY(QWindow *transientParent READ transientParent WRITE setTransientParent NOTIFY transientParentChanged)
15+
Q_PROPERTY(bool x11GrabFocusTransition READ x11GrabFocusTransition NOTIFY x11GrabFocusTransitionChanged)
1516
QML_NAMED_ELEMENT(PopupWindow)
1617

1718
public:
1819
PopupWindow(QWindow *parent = nullptr);
20+
bool x11GrabFocusTransition() const { return m_x11GrabFocusTransition; }
1921

2022
protected:
2123
void mouseReleaseEvent(QMouseEvent *event) override;
2224
void mousePressEvent(QMouseEvent *event) override;
2325
void mouseMoveEvent(QMouseEvent *event) override;
26+
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
27+
28+
signals:
29+
void x11GrabFocusTransitionChanged();
30+
void x11FocusOutByGrab();
31+
void x11FocusInByUngrab();
2432

2533
private:
34+
void setX11GrabFocusTransition(bool transition);
2635
bool m_dragging;
2736
bool m_pressing;
37+
bool m_x11GrabFocusTransition = false;
2838
};
2939
DS_END_NAMESPACE

frame/qml/PanelPopup.qml

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Item {
1515
property int popupX: 0
1616
property int popupY: 0
1717
property bool readyBinding: false
18+
property bool grabInactivePending: false
19+
property int grabInactiveTimeout: 200
1820
// WM_NAME, used for kwin.
1921
property string windowTitle: "dde-shell/panelpopup"
2022
width: popup.childrenRect.width
@@ -84,8 +86,24 @@ Item {
8486
popupWindow.requestActivate()
8587
}
8688
}
89+
Timer {
90+
id: grabInactiveTimer
91+
interval: control.grabInactiveTimeout
92+
repeat: false
93+
onTriggered: {
94+
control.grabInactivePending = false
95+
if (!popupWindow || !readyBinding || popupWindow.currentItem !== control || !popup.visible) {
96+
return
97+
}
98+
if (!popupWindow.active) {
99+
control.close()
100+
}
101+
}
102+
}
87103
function close()
88104
{
105+
grabInactivePending = false
106+
grabInactiveTimer.stop()
89107
if (!popupWindow)
90108
return
91109

@@ -103,11 +121,55 @@ Item {
103121
{
104122
if (!popupWindow)
105123
return
124+
if (popupWindow.currentItem !== control || !popup.visible) {
125+
control.grabInactivePending = false
126+
grabInactiveTimer.stop()
127+
return
128+
}
129+
if (popupWindow.active) {
130+
control.grabInactivePending = false
131+
grabInactiveTimer.stop()
132+
return
133+
}
134+
if (control.grabInactivePending || popupWindow.x11GrabFocusTransition) {
135+
return
136+
}
106137
// TODO why activeChanged is not emit.
107-
if (popupWindow && !popupWindow.active) {
138+
if (!popupWindow.active) {
108139
control.close()
109140
}
110141
}
142+
143+
function onX11FocusOutByGrab()
144+
{
145+
if (!popupWindow || !readyBinding || !popup.visible || popupWindow.currentItem !== control) {
146+
return
147+
}
148+
control.grabInactivePending = true
149+
grabInactiveTimer.start()
150+
}
151+
152+
function onX11FocusInByUngrab()
153+
{
154+
if (!popupWindow || popupWindow.currentItem !== control || !control.grabInactivePending) {
155+
return
156+
}
157+
control.grabInactivePending = false
158+
grabInactiveTimer.stop()
159+
160+
Qt.callLater(function() {
161+
if (!popupWindow
162+
|| !readyBinding
163+
|| popupWindow.currentItem !== control
164+
|| !popup.visible
165+
|| control.grabInactivePending) {
166+
return
167+
}
168+
if (!popupWindow.active) {
169+
control.close()
170+
}
171+
})
172+
}
111173
}
112174

113175
Item {

0 commit comments

Comments
 (0)