From fe0787c218e732ac04ebabf7154aeb6387a5747c Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Thu, 26 Feb 2026 15:17:05 +0800 Subject: [PATCH] fix: fix lock screen window positioning on multi-monitor X11 setups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/global_util/multiscreenmanager.cpp | 59 +++++++++++++++++++++- src/widgets/fullscreenbackground.cpp | 69 ++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/global_util/multiscreenmanager.cpp b/src/global_util/multiscreenmanager.cpp index 98f4097c..418bf73e 100644 --- a/src/global_util/multiscreenmanager.cpp +++ b/src/global_util/multiscreenmanager.cpp @@ -11,6 +11,13 @@ #include #include +#ifndef ENABLE_DSS_SNIPE +#include +#else +#include +#endif +#include + #include "dbusconstant.h" MultiScreenManager::MultiScreenManager(QObject *parent) @@ -114,6 +121,11 @@ void MultiScreenManager::onScreenAdded(QPointer screen) return; } + qCInfo(DDE_SHELL) << "onScreenAdded processing, screen:" << screen + << ", name:" << screen->name() + << ", geometry:" << screen->geometry() + << ", existing frames count:" << m_frames.size(); + QWidget* w = nullptr; if (m_isCopyMode) { // 如果m_frames不为空则直接退出 @@ -244,10 +256,53 @@ void MultiScreenManager::onDisplayModeChanged(const QString &) void MultiScreenManager::checkLockFrameLocation() { + xcb_connection_t *connection = nullptr; + if (!QGuiApplication::platformName().startsWith("wayland", Qt::CaseInsensitive)) { +#ifndef ENABLE_DSS_SNIPE + connection = QX11Info::connection(); +#else + auto *x11App = qGuiApp->nativeInterface(); + if (x11App) + connection = x11App->connection(); +#endif + } + for (QScreen *screen : m_frames.keys()) { if (screen) { - qCInfo(DDE_SHELL) << "Check lock frame location, screen:" << screen << ", location:" << screen->geometry() - << ", lockframe:" << m_frames.value(screen) << ", location:" << m_frames.value(screen)->geometry(); + QWidget *frame = m_frames.value(screen); + qCInfo(DDE_SHELL) << "Check lock frame location, screen:" << screen + << ", screen name:" << screen->name() + << ", screen geometry:" << screen->geometry() + << ", lockframe:" << frame + << ", Qt frame geometry:" << frame->geometry(); + + // 通过 XCB 检查窗口在 X Server 中的实际位置 + // 注意:X11 下窗口位置不乘 DPR(与 RandR 物理坐标一致),窗口大小乘 DPR + if (connection && frame) { + auto cookie = xcb_get_geometry(connection, static_cast(frame->winId())); + auto *reply = xcb_get_geometry_reply(connection, cookie, nullptr); + if (reply) { + QRect xcbGeometry(reply->x, reply->y, reply->width, reply->height); + const qreal dpr = frame->devicePixelRatioF(); + QRect expectedPhysical(screen->geometry().x(), screen->geometry().y(), + qRound(screen->geometry().width() * dpr), + qRound(screen->geometry().height() * dpr)); + bool positionMatch = (xcbGeometry == expectedPhysical); + qCInfo(DDE_SHELL) << " XCB actual geometry(physical):" << xcbGeometry + << ", expected(physical):" << expectedPhysical + << ", dpr:" << dpr + << ", match:" << positionMatch; + if (!positionMatch) { + qCWarning(DDE_SHELL) << " *** POSITION MISMATCH DETECTED! ***" + << "XCB geometry:" << xcbGeometry + << "expected:" << expectedPhysical + << "for screen" << screen->name(); + } + free(reply); + } else { + qCWarning(DDE_SHELL) << " Failed to get XCB geometry for frame:" << frame; + } + } } } } diff --git a/src/widgets/fullscreenbackground.cpp b/src/widgets/fullscreenbackground.cpp index 0aafcf6f..05c18d1d 100644 --- a/src/widgets/fullscreenbackground.cpp +++ b/src/widgets/fullscreenbackground.cpp @@ -21,6 +21,13 @@ #include #include +#ifndef ENABLE_DSS_SNIPE +#include +#else +#include +#endif +#include + #include "dbusconstant.h" Q_LOGGING_CATEGORY(DDE_SS, "dss.active") @@ -74,9 +81,53 @@ FullScreenBackground::FullScreenBackground(SessionBaseModel *model, QWidget *par connect(m_resetGeometryTimer, &QTimer::timeout, this, [this] { const auto ¤tGeometry = geometry(); if (currentGeometry != m_geometryRect) { - qCDebug(DDE_SHELL) << "Current geometry:" << currentGeometry <<"setGeometry:" << m_geometryRect; + qCWarning(DDE_SHELL) << "Geometry mismatch detected! Qt cached geometry:" << currentGeometry + << ", target geometry:" << m_geometryRect << ", this:" << this; setGeometry(m_geometryRect); } + + // 通过 XCB 检查窗口在 X Server 中的实际位置 + // 注意:X11 坐标系中,窗口位置不乘 DPR(与 X RandR 一致),窗口大小乘 DPR + if (!m_model->isUseWayland() && windowHandle()) { + xcb_connection_t *connection = nullptr; +#ifndef ENABLE_DSS_SNIPE + connection = QX11Info::connection(); +#else + auto *x11App = qGuiApp->nativeInterface(); + if (x11App) + connection = x11App->connection(); +#endif + if (connection) { + auto cookie = xcb_get_geometry(connection, static_cast(winId())); + auto *reply = xcb_get_geometry_reply(connection, cookie, nullptr); + if (reply) { + QRect xcbGeometry(reply->x, reply->y, reply->width, reply->height); + const qreal dpr = devicePixelRatioF(); + QRect expectedPhysical(m_geometryRect.x(), m_geometryRect.y(), + qRound(m_geometryRect.width() * dpr), + qRound(m_geometryRect.height() * dpr)); + if (xcbGeometry != expectedPhysical) { + qCWarning(DDE_SHELL) << "XCB position mismatch! XCB geometry(physical):" << xcbGeometry + << ", expected(physical):" << expectedPhysical + << ", target(logical):" << m_geometryRect + << ", dpr:" << dpr + << ", this:" << this + << ", screen:" << (m_screen ? m_screen->name() : "null"); + // Qt 的 geometry() 缓存可能已经是正确值,但实际 X 窗口未移动。 + // 强制重新绑定 screen 并调用 setGeometry,让 Qt 重新发送 XCB configure request。 + if (!m_screen.isNull() && windowHandle()->screen() != m_screen) { + windowHandle()->setScreen(m_screen); + } + // 先 resize 到 0,0 再设目标值,破坏 Qt 的 "geometry 未变" 优化,强制重新下发 + setGeometry(0, 0, 0, 0); + setGeometry(m_geometryRect); + } else { + qCInfo(DDE_SHELL) << "XCB position match, no need to set geometry"; + } + free(reply); + } + } + } }); connect(m_model, &SessionBaseModel::shutdownkModeChanged, this, [this] (bool value){ @@ -453,14 +504,20 @@ void FullScreenBackground::updateGeometry() } if (!m_screen.isNull()) { - if(m_model->isUseWayland()) + // X11 下也需要将 QWindow 关联到正确的 QScreen,否则 Qt 可能将窗口归属到错误的 screen + if (windowHandle() && windowHandle()->screen() != m_screen) { + qCInfo(DDE_SHELL) << "bindWindowToScreen, windowHandle screen:" << windowHandle()->screen()->name() + << ", target screen:" << m_screen->name() << ", this:" << this; windowHandle()->setScreen(m_screen); + } setddeGeometry(m_screen->geometry()); qCInfo(DDE_SHELL) << "Update geometry, screen:" << m_screen + << ", screen name:" << m_screen->name() << ", screen geometry:" << m_screen->geometry() << ", lockFrame:" << this - << ", frame geometry:" << this->geometry(); + << ", frame geometry:" << this->geometry() + << ", windowHandle screen:" << (windowHandle() ? windowHandle()->screen()->name() : "null"); } else { qCWarning(DDE_SHELL) << "Screen is nullptr"; } @@ -735,6 +792,12 @@ bool FullScreenBackground::getScaledBlurImage(const QString &originPath, QString //增加一个定时器,每隔50ms再设置一次Geometry,避免出现xorg初始化未完成的情况,导致界面显示不全 void FullScreenBackground::setddeGeometry(const QRect &rect) { + qCInfo(DDE_SHELL) << "setddeGeometry called, this:" << this + << ", target rect:" << rect + << ", current geometry:" << geometry() + << ", screen:" << (m_screen ? m_screen->name() : "null") + << ", windowHandle screen:" << (windowHandle() ? windowHandle()->screen()->name() : "null") + << ", dpr:" << devicePixelRatioF(); setGeometry(rect); m_geometryRect = rect; m_resetGeometryTimer->start(200);