Skip to content

Commit 69838ee

Browse files
fix(shot): relay XRecord events when Dock grab blocks Qt input on X11
When a Dock right-click menu is open, Qt's XGrabPointer routes all pointer events to the Dock process. The screenshot overlay never receives them via the normal Qt event path. Fix by relaying XRecord events (which intercept at the protocol level, unaffected by grabs) through QApplication::sendEvent in four handlers: - onMouseMove: continuously relay during hover phase so window auto-highlight updates as the mouse moves - onMouseDrag: relay during drag phase so the selection rectangle tracks the mouse - onMousePress: relay the initial click so the drag state is entered - onMouseRelease: relay the release so the toolbar appears State flags (isFirstPressButton, isFirstReleaseButton, m_currentCursor) are used as dedup guards: QueuedConnection ensures XRecord signals arrive after Qt's synchronous event filter, so if Qt already handled the event the guard condition is already true and the relay is skipped. Log: 修复Dock右键菜单存在时截图工具鼠标事件被X11 Grab拦截的问题 PMS: BUG-330895 Influence: 修复后在Dock右键菜单打开状态下触发截图,窗口悬停检测、 框选拖拽、工具栏显示均可正常工作
1 parent 08a38b0 commit 69838ee

4 files changed

Lines changed: 88 additions & 15 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ AI config
1414
.npm-cache/
1515
.claude/
1616
.trellis/
17+
.codegraph/

src/main_window.cpp

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,10 @@ void MainWindow::initAttributes()
491491
m_connectionThreadObject->initConnection();
492492
#endif
493493
} else {
494-
// x11自动识别窗口
495-
Utils::getAllWindowInfo(static_cast<quint32>(this->winId()), m_screenWidth, m_screenHeight, windowRects, windowNames);
494+
// x11: 窗口列表延迟到 showFullScreen 之后通过 refetchWindowInfo() 获取
495+
// 避免右键菜单等弹出窗口残留在 windowRects 中
496+
windowRects.clear();
497+
windowNames.clear();
496498
}
497499
// 构建截屏工具栏按钮 by zyg
498500
m_toolBar = new ToolBar(this);
@@ -599,18 +601,38 @@ void MainWindow::initAttributes()
599601
this,
600602
SLOT(onLockScreenEvent(QDBusMessage)));
601603

602-
if (!isFirstMove && !Utils::isWaylandMode) {
603-
qCDebug(dsrApp) << "发送鼠标事件!";
604-
QMouseEvent *mouseMove =
605-
new QMouseEvent(QEvent::MouseMove, this->cursor().pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
606-
QApplication::sendEvent(this, mouseMove);
607-
delete mouseMove;
608-
}
604+
// 模拟鼠标事件延迟到 refetchWindowInfo() 中,与窗口列表获取同步
609605

610606
qCInfo(dsrApp) << __LINE__ << __FUNCTION__ << "属性初始化已完成";
611607
}
612608
}
613609

610+
void MainWindow::refetchWindowInfo()
611+
{
612+
qCDebug(dsrApp) << "refetchWindowInfo() called.";
613+
if (Utils::isWaylandMode || Utils::isTreelandMode) {
614+
// Wayland 下窗口列表通过异步回调 waylandwindowinfo() 获取,无需在此处理
615+
return;
616+
}
617+
618+
windowRects.clear();
619+
windowNames.clear();
620+
Utils::getAllWindowInfo(static_cast<quint32>(this->winId()), m_screenWidth, m_screenHeight, windowRects, windowNames);
621+
qCDebug(dsrApp) << "refetchWindowInfo: got" << windowRects.size() << "windows";
622+
623+
if (!isFirstMove) {
624+
qCDebug(dsrApp) << "发送鼠标事件!";
625+
sendSimulatedMouseEvent(QEvent::MouseMove, this->cursor().pos(), Qt::NoButton, Qt::NoButton);
626+
}
627+
}
628+
629+
void MainWindow::sendSimulatedMouseEvent(QEvent::Type type, const QPoint &pos,
630+
Qt::MouseButton button, Qt::MouseButtons buttons)
631+
{
632+
QScopedPointer<QMouseEvent> event(new QMouseEvent(type, pos, button, buttons, Qt::NoModifier));
633+
QApplication::sendEvent(this, event.data());
634+
}
635+
614636
void MainWindow::forceX11WindowPosition()
615637
{
616638
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@@ -2268,6 +2290,7 @@ void MainWindow::topWindow()
22682290
this->initLaunchMode("screenShot");
22692291
this->showFullScreen();
22702292
this->initResource();
2293+
refetchWindowInfo();
22712294

22722295
// wayland 模式下不进入以下步骤
22732296
if (Utils::isWaylandMode) {
@@ -2486,7 +2509,8 @@ void MainWindow::savePath(const QString &path)
24862509
this->initLaunchMode("screenShot");
24872510
this->showFullScreen();
24882511
this->initResource();
2489-
2512+
refetchWindowInfo();
2513+
24902514
qCDebug(dsrApp) << "savePath end";
24912515
}
24922516

@@ -2512,6 +2536,7 @@ void MainWindow::startScreenshotFor3rd(const QString &path)
25122536
this->initLaunchMode("screenShot");
25132537
this->showFullScreen();
25142538
this->initResource();
2539+
refetchWindowInfo();
25152540
m_shotWithPath = true; // 自带路径
25162541
m_noNotify = true; // 关闭通知
25172542
qCDebug(dsrApp) << "startScreenshotFor3rd end";
@@ -2525,6 +2550,7 @@ void MainWindow::noNotify()
25252550
this->initLaunchMode("screenShot");
25262551
this->showFullScreen();
25272552
this->initResource();
2553+
refetchWindowInfo();
25282554
}
25292555

25302556
void MainWindow::initBackground()
@@ -6010,6 +6036,16 @@ void MainWindow::onMouseDrag(int x, int y)
60106036
if (status::record == m_functionType) {
60116037
showDragFeedback(x, y);
60126038
}
6039+
6040+
// 截图模式:拖拽阶段(按下左键后移动)XRecord 发出 mouseDrag 而非 mouseMove。
6041+
// Dock Grab 存在时 Qt event filter 收不到拖拽移动事件,框选区域无法更新。
6042+
// 同样用坐标去重避免正常情况下双重触发。
6043+
if (status::shot == m_functionType && isFirstPressButton && !isFirstReleaseButton) {
6044+
QPoint pos(x, y);
6045+
if (m_currentCursor != pos) {
6046+
sendSimulatedMouseEvent(QEvent::MouseMove, pos, Qt::NoButton, Qt::LeftButton);
6047+
}
6048+
}
60136049
}
60146050

60156051
// 通过x11从底层获取鼠标按压事件
@@ -6023,6 +6059,15 @@ void MainWindow::onMousePress(int x, int y)
60236059
} else if (m_initScroll && status::scrollshot == m_functionType) {
60246060
scrollShotMouseClickEvent(x, y);
60256061
}
6062+
6063+
// 截图模式:Dock 右键菜单持有 X11 Pointer Grab 时,按下事件会被路由到
6064+
// Dock 而非截图工具,Qt event filter 收不到。通过 XRecord 补发,确保截图
6065+
// 工具能正确进入拖拽状态(isFirstPressButton = true)。
6066+
// 利用 QueuedConnection 的时序:Qt 同步处理已在前,此处排队到后——若 Qt
6067+
// 已处理(isFirstPressButton == true),条件不成立,不重复触发。
6068+
if (status::shot == m_functionType && !isFirstPressButton) {
6069+
sendSimulatedMouseEvent(QEvent::MouseButtonPress, QPoint(x, y), Qt::LeftButton, Qt::LeftButton);
6070+
}
60266071
}
60276072

60286073
// 通过x11从底层获取鼠标释放事件
@@ -6034,6 +6079,14 @@ void MainWindow::onMouseRelease(int x, int y)
60346079
if (status::record == m_functionType) {
60356080
showReleaseFeedback(x, y);
60366081
}
6082+
6083+
// 截图模式:与 onMousePress 对应——Dock Grab 导致释放事件也路由到 Dock,
6084+
// Qt event filter 收不到,工具栏无法显示。通过 XRecord 补发释放事件。
6085+
// 同样利用 QueuedConnection 时序避免重复:Qt 已处理则 isFirstReleaseButton
6086+
// 为 true,条件不成立;isFirstPressButton 为 true 确保有匹配的按下事件。
6087+
if (status::shot == m_functionType && isFirstPressButton && !isFirstReleaseButton) {
6088+
sendSimulatedMouseEvent(QEvent::MouseButtonRelease, QPoint(x, y), Qt::LeftButton, Qt::LeftButton);
6089+
}
60376090
}
60386091

60396092
// 通过x11从底层获取鼠标移动事件
@@ -6050,11 +6103,18 @@ void MainWindow::onMouseMove(int x, int y)
60506103
}
60516104
}
60526105

6053-
// 启动截图或者录屏后第一次鼠标移动时需要通过此方法,后面都不会在进入此方法
6054-
if (!isFirstMove) {
6055-
QMouseEvent *mouseMove;
6056-
mouseMove = new QMouseEvent(QEvent::MouseMove, QPoint(x, y), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
6057-
QApplication::sendEvent(QWidget::focusWidget(), mouseMove);
6106+
// 截图悬停阶段(未开始框选)通过 XRecord 持续补发鼠标移动事件。
6107+
// Dock Grab 存在时 Qt 正常事件通路被阻断,XRecord 在协议层拦截不受影响。
6108+
// 拖拽阶段由 onMouseDrag 负责补发,此处只覆盖悬停阶段(!isFirstPressButton)。
6109+
// 非截图模式保持原有逻辑:仅补发第一次(!isFirstMove)。
6110+
// m_currentCursor 在 mouseMoveEF 开头被更新:坐标已匹配说明 Qt 正常处理过,
6111+
// 无需重复补发,避免正常情况下双重触发。
6112+
bool inShotHoverPhase = status::shot == m_functionType && !isFirstPressButton;
6113+
if (!isFirstMove || inShotHoverPhase) {
6114+
QPoint pos(x, y);
6115+
if (m_currentCursor != pos) {
6116+
sendSimulatedMouseEvent(QEvent::MouseMove, pos, Qt::NoButton, Qt::NoButton);
6117+
}
60586118
}
60596119
}
60606120

src/main_window.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,14 @@ class MainWindow : public QMainWindow
359359
void initSaveShortcut();
360360

361361
void initLaunchMode(const QString &launchMode);
362+
363+
/**
364+
* @brief 在截图窗口 showFullScreen 之后重新获取窗口列表
365+
* 解决右键菜单等弹出窗口残留在 windowRects 中的问题
366+
*/
367+
void refetchWindowInfo();
368+
void sendSimulatedMouseEvent(QEvent::Type type, const QPoint &pos,
369+
Qt::MouseButton button, Qt::MouseButtons buttons);
362370
//void delayScreenshot(double num);
363371
void fullScreenshot();
364372

src/screenshot.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ void Screenshot::startScreenshot()
4949
qCDebug(dsrApp) << "Showing window in full screen mode for non-Wayland.";
5050
}
5151
m_window.createWinId();
52+
m_window.refetchWindowInfo();
5253
qCDebug(dsrApp) << "Window ID created.";
5354
//平板模式截图录屏
5455
if (Utils::isTabletEnvironment) {
@@ -102,6 +103,7 @@ void Screenshot::delayScreenshot(double num)
102103
m_window.showFullScreen();
103104
m_window.initResource();
104105
m_window.createWinId();
106+
m_window.refetchWindowInfo();
105107
qCDebug(dsrApp) << "Screenshot window initialized and shown after delay.";
106108
});
107109
}
@@ -133,6 +135,7 @@ void Screenshot::OcrScreenshot()
133135
m_window.initLaunchMode("screenOcr");
134136
m_window.showFullScreen();
135137
m_window.createWinId();
138+
m_window.refetchWindowInfo();
136139
qCDebug(dsrApp) << "OCR screenshot window initialized and shown.";
137140
#else
138141
qCDebug(dsrApp) << "OCR_SCROLL_FLAGE_ON is not defined, skipping OCR screenshot initialization.";
@@ -152,6 +155,7 @@ void Screenshot::ScrollScreenshot()
152155
m_window.initLaunchMode("screenScroll");
153156
m_window.showFullScreen();
154157
m_window.createWinId();
158+
m_window.refetchWindowInfo();
155159
qCDebug(dsrApp) << "Scroll screenshot window initialized and shown.";
156160
} else {
157161
qCDebug(dsrApp) << "Scroll shot exit. Window effects not supported.";

0 commit comments

Comments
 (0)