Skip to content

Commit 3bd2a1f

Browse files
committed
fix: fix lock screen window positioning on multi-monitor X11 setups
On X11 multi-monitor setups (especially with mixed resolutions like 4K + 1080p), lock screen windows could end up incorrectly positioned, with both windows appearing on the primary monitor. This was caused by: 1. QWindow not being associated with the correct QScreen on X11, causing Qt's internal coordinate transformation to use wrong screen parameters. 2. Qt's setGeometry() caching optimization silently skipping the actual XCB configure request when it believed the geometry was already correct, even though the X window had not actually been moved. Changes: - Call windowHandle()->setScreen(m_screen) on X11 (previously only on Wayland) to ensure correct screen association before setting geometry. - Add XCB-level geometry verification in the reset timer callback to detect when Qt's cached geometry doesn't match the actual X window position. - When XCB mismatch is detected, force re-apply geometry by resetting to (0,0,0,0) first to bypass Qt's "geometry unchanged" optimization. - Add detailed diagnostic logging for screen initialization and geometry tracking. X11多屏环境下(尤其是混合分辨率如4K+1080p),锁屏窗口可能定位错误, 两个窗口都显示在主屏上。原因是: 1. X11下QWindow未正确关联到目标QScreen,导致Qt内部坐标转换使用了 错误的屏幕参数。 2. Qt的setGeometry()缓存优化会在它认为geometry已正确时跳过实际的 XCB configure请求,即使X窗口实际并未移动。 修改内容: - 在X11下也调用windowHandle()->setScreen(m_screen)(之前仅Wayland下调用), 确保设置geometry前屏幕关联正确。 - 在定时器回调中增加XCB级别的geometry验证,检测Qt缓存geometry与X窗口 实际位置不一致的情况。 - 当检测到XCB位置不匹配时,先setGeometry(0,0,0,0)再设目标值,破坏Qt的 "geometry未变"优化,强制重新下发XCB configure请求。 - 增加屏幕初始化和geometry追踪的详细诊断日志。 Log: fix lock screen window positioning on multi-monitor X11 setups Pms: BUG-326163
1 parent a711d9f commit 3bd2a1f

2 files changed

Lines changed: 123 additions & 5 deletions

File tree

src/global_util/multiscreenmanager.cpp

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
#include <QJsonDocument>
1212
#include <QJsonObject>
1313

14+
#ifndef ENABLE_DSS_SNIPE
15+
#include <QX11Info>
16+
#else
17+
#include <xcb/xproto.h>
18+
#endif
19+
#include <xcb/xcb.h>
20+
1421
#include "dbusconstant.h"
1522

1623
MultiScreenManager::MultiScreenManager(QObject *parent)
@@ -114,6 +121,11 @@ void MultiScreenManager::onScreenAdded(QPointer<QScreen> screen)
114121
return;
115122
}
116123

124+
qCInfo(DDE_SHELL) << "onScreenAdded processing, screen:" << screen
125+
<< ", name:" << screen->name()
126+
<< ", geometry:" << screen->geometry()
127+
<< ", existing frames count:" << m_frames.size();
128+
117129
QWidget* w = nullptr;
118130
if (m_isCopyMode) {
119131
// 如果m_frames不为空则直接退出
@@ -244,10 +256,53 @@ void MultiScreenManager::onDisplayModeChanged(const QString &)
244256

245257
void MultiScreenManager::checkLockFrameLocation()
246258
{
259+
xcb_connection_t *connection = nullptr;
260+
if (!QGuiApplication::platformName().startsWith("wayland", Qt::CaseInsensitive)) {
261+
#ifndef ENABLE_DSS_SNIPE
262+
connection = QX11Info::connection();
263+
#else
264+
auto *x11App = qGuiApp->nativeInterface<QNativeInterface::QX11Application>();
265+
if (x11App)
266+
connection = x11App->connection();
267+
#endif
268+
}
269+
247270
for (QScreen *screen : m_frames.keys()) {
248271
if (screen) {
249-
qCInfo(DDE_SHELL) << "Check lock frame location, screen:" << screen << ", location:" << screen->geometry()
250-
<< ", lockframe:" << m_frames.value(screen) << ", location:" << m_frames.value(screen)->geometry();
272+
QWidget *frame = m_frames.value(screen);
273+
qCInfo(DDE_SHELL) << "Check lock frame location, screen:" << screen
274+
<< ", screen name:" << screen->name()
275+
<< ", screen geometry:" << screen->geometry()
276+
<< ", lockframe:" << frame
277+
<< ", Qt frame geometry:" << frame->geometry();
278+
279+
// 通过 XCB 检查窗口在 X Server 中的实际位置
280+
// 注意:X11 下窗口位置不乘 DPR(与 RandR 物理坐标一致),窗口大小乘 DPR
281+
if (connection && frame) {
282+
auto cookie = xcb_get_geometry(connection, static_cast<xcb_window_t>(frame->winId()));
283+
auto *reply = xcb_get_geometry_reply(connection, cookie, nullptr);
284+
if (reply) {
285+
QRect xcbGeometry(reply->x, reply->y, reply->width, reply->height);
286+
const qreal dpr = frame->devicePixelRatioF();
287+
QRect expectedPhysical(screen->geometry().x(), screen->geometry().y(),
288+
qRound(screen->geometry().width() * dpr),
289+
qRound(screen->geometry().height() * dpr));
290+
bool positionMatch = (xcbGeometry == expectedPhysical);
291+
qCInfo(DDE_SHELL) << " XCB actual geometry(physical):" << xcbGeometry
292+
<< ", expected(physical):" << expectedPhysical
293+
<< ", dpr:" << dpr
294+
<< ", match:" << positionMatch;
295+
if (!positionMatch) {
296+
qCWarning(DDE_SHELL) << " *** POSITION MISMATCH DETECTED! ***"
297+
<< "XCB geometry:" << xcbGeometry
298+
<< "expected:" << expectedPhysical
299+
<< "for screen" << screen->name();
300+
}
301+
free(reply);
302+
} else {
303+
qCWarning(DDE_SHELL) << " Failed to get XCB geometry for frame:" << frame;
304+
}
305+
}
251306
}
252307
}
253308
}

src/widgets/fullscreenbackground.cpp

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
#include <QTimer>
2222
#include <QWindow>
2323

24+
#ifndef ENABLE_DSS_SNIPE
25+
#include <QX11Info>
26+
#else
27+
#include <xcb/xproto.h>
28+
#endif
29+
#include <xcb/xcb.h>
30+
2431
#include "dbusconstant.h"
2532

2633
Q_LOGGING_CATEGORY(DDE_SS, "dss.active")
@@ -74,9 +81,53 @@ FullScreenBackground::FullScreenBackground(SessionBaseModel *model, QWidget *par
7481
connect(m_resetGeometryTimer, &QTimer::timeout, this, [this] {
7582
const auto &currentGeometry = geometry();
7683
if (currentGeometry != m_geometryRect) {
77-
qCDebug(DDE_SHELL) << "Current geometry:" << currentGeometry <<"setGeometry:" << m_geometryRect;
84+
qCWarning(DDE_SHELL) << "Geometry mismatch detected! Qt cached geometry:" << currentGeometry
85+
<< ", target geometry:" << m_geometryRect << ", this:" << this;
7886
setGeometry(m_geometryRect);
7987
}
88+
89+
// 通过 XCB 检查窗口在 X Server 中的实际位置
90+
// 注意:X11 坐标系中,窗口位置不乘 DPR(与 X RandR 一致),窗口大小乘 DPR
91+
if (!m_model->isUseWayland() && windowHandle()) {
92+
xcb_connection_t *connection = nullptr;
93+
#ifndef ENABLE_DSS_SNIPE
94+
connection = QX11Info::connection();
95+
#else
96+
auto *x11App = qGuiApp->nativeInterface<QNativeInterface::QX11Application>();
97+
if (x11App)
98+
connection = x11App->connection();
99+
#endif
100+
if (connection) {
101+
auto cookie = xcb_get_geometry(connection, static_cast<xcb_window_t>(winId()));
102+
auto *reply = xcb_get_geometry_reply(connection, cookie, nullptr);
103+
if (reply) {
104+
QRect xcbGeometry(reply->x, reply->y, reply->width, reply->height);
105+
const qreal dpr = devicePixelRatioF();
106+
QRect expectedPhysical(m_geometryRect.x(), m_geometryRect.y(),
107+
qRound(m_geometryRect.width() * dpr),
108+
qRound(m_geometryRect.height() * dpr));
109+
if (xcbGeometry != expectedPhysical) {
110+
qCWarning(DDE_SHELL) << "XCB position mismatch! XCB geometry(physical):" << xcbGeometry
111+
<< ", expected(physical):" << expectedPhysical
112+
<< ", target(logical):" << m_geometryRect
113+
<< ", dpr:" << dpr
114+
<< ", this:" << this
115+
<< ", screen:" << (m_screen ? m_screen->name() : "null");
116+
// Qt 的 geometry() 缓存可能已经是正确值,但实际 X 窗口未移动。
117+
// 强制重新绑定 screen 并调用 setGeometry,让 Qt 重新发送 XCB configure request。
118+
if (!m_screen.isNull() && windowHandle()->screen() != m_screen) {
119+
windowHandle()->setScreen(m_screen);
120+
}
121+
// 先 resize 到 0,0 再设目标值,破坏 Qt 的 "geometry 未变" 优化,强制重新下发
122+
setGeometry(0, 0, 0, 0);
123+
setGeometry(m_geometryRect);
124+
} else {
125+
qCInfo(DDE_SHELL) << "XCB position match, no need to set geometry";
126+
}
127+
free(reply);
128+
}
129+
}
130+
}
80131
});
81132

82133
connect(m_model, &SessionBaseModel::shutdownkModeChanged, this, [this] (bool value){
@@ -453,14 +504,20 @@ void FullScreenBackground::updateGeometry()
453504
}
454505

455506
if (!m_screen.isNull()) {
456-
if(m_model->isUseWayland())
507+
// X11 下也需要将 QWindow 关联到正确的 QScreen,否则 Qt 可能将窗口归属到错误的 screen
508+
if (windowHandle() && windowHandle()->screen() != m_screen) {
509+
qCInfo(DDE_SHELL) << "bindWindowToScreen, windowHandle screen:" << windowHandle()->screen()->name()
510+
<< ", target screen:" << m_screen->name() << ", this:" << this;
457511
windowHandle()->setScreen(m_screen);
512+
}
458513
setddeGeometry(m_screen->geometry());
459514

460515
qCInfo(DDE_SHELL) << "Update geometry, screen:" << m_screen
516+
<< ", screen name:" << m_screen->name()
461517
<< ", screen geometry:" << m_screen->geometry()
462518
<< ", lockFrame:" << this
463-
<< ", frame geometry:" << this->geometry();
519+
<< ", frame geometry:" << this->geometry()
520+
<< ", windowHandle screen:" << (windowHandle() ? windowHandle()->screen()->name() : "null");
464521
} else {
465522
qCWarning(DDE_SHELL) << "Screen is nullptr";
466523
}
@@ -735,6 +792,12 @@ bool FullScreenBackground::getScaledBlurImage(const QString &originPath, QString
735792
//增加一个定时器,每隔50ms再设置一次Geometry,避免出现xorg初始化未完成的情况,导致界面显示不全
736793
void FullScreenBackground::setddeGeometry(const QRect &rect)
737794
{
795+
qCInfo(DDE_SHELL) << "setddeGeometry called, this:" << this
796+
<< ", target rect:" << rect
797+
<< ", current geometry:" << geometry()
798+
<< ", screen:" << (m_screen ? m_screen->name() : "null")
799+
<< ", windowHandle screen:" << (windowHandle() ? windowHandle()->screen()->name() : "null")
800+
<< ", dpr:" << devicePixelRatioF();
738801
setGeometry(rect);
739802
m_geometryRect = rect;
740803
m_resetGeometryTimer->start(200);

0 commit comments

Comments
 (0)