Skip to content

Commit 7c9cb05

Browse files
fix: Implement X11 screenshot workaround for Qt6
- Added a workaround using XGetImage for capturing screenshots in X11 environments with Qt6 to address issues with QScreen::grabWindow under high DPI settings. - Updated ScreenGrabber class to include conditional compilation for X11 support and integrated the new screenshot method. - Modified main application logic to disable high DPI scaling when using the X11 workaround, ensuring consistent behavior across different display setups. This update improves screenshot reliability in multi-screen and high DPI scenarios on X11. bug: https://pms.uniontech.com/bug-view-342909.html
1 parent 9d53dc3 commit 7c9cb05

4 files changed

Lines changed: 122 additions & 3 deletions

File tree

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ build/
66
*.qm
77
*.pro.user*
88

9-
# AI config
9+
AI config
1010
.cursor/
1111
.cursorindexingignore
12-
.specstory/
12+
.specstory/
13+
x11_paint_demo/

src/main.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#include <QFile>
2929
#include <QDir>
3030
#include <QStandardPaths>
31+
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) && defined(QT_QPA_PLATFORM_XCB)
32+
#define USE_X11_GETIMAGE 1
33+
#endif
3134

3235
DWIDGET_USE_NAMESPACE
3336

@@ -131,8 +134,18 @@ int main(int argc, char *argv[])
131134
qCDebug(dsrApp) << "Qt5: Inactive color group disabled.";
132135
#endif
133136

137+
#ifdef USE_X11_GETIMAGE
138+
// Qt6 X11 workaround: 禁用高 DPI 缩放
139+
// Qt6 的 QScreen::grabWindow 在多屏+高DPI+非对齐布局的 X11 环境下存在 bug
140+
// 禁用 DPI 缩放后,坐标系和 Qt5 一致,配合 XGetImage 可以正常工作
141+
if (!isWaylandProtocol()) {
142+
qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
143+
qCDebug(dsrApp) << "Qt6 X11: High DPI scaling disabled (workaround for grabWindow bug).";
144+
}
145+
#else
134146
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
135147
qCDebug(dsrApp) << "High DPI scaling enabled.";
148+
#endif
136149

137150
// 平板模式
138151
// Utils::isTabletEnvironment = DGuiApplicationHelper::isTabletEnvironment();

src/utils/screengrabber.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@
1515
#include <QScreen>
1616
#include <QGuiApplication>
1717
#include <QPainter>
18+
#include <QImage>
1819
#include <algorithm>
1920
#include <QMap>
2021

22+
// X11 headers for XGetImage workaround
23+
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
24+
#include <X11/Xlib.h>
25+
#include <X11/Xutil.h>
26+
#endif
27+
2128
ScreenGrabber::ScreenGrabber(QObject *parent) : QObject(parent)
2229
{
2330
qCDebug(dsrApp) << "ScreenGrabber initialized.";
@@ -103,6 +110,14 @@ QPixmap ScreenGrabber::grabWaylandScreenshot(bool &ok, const QRect &rect, const
103110
QPixmap ScreenGrabber::grabX11Screenshot(bool &ok, const QRect &rect, const qreal devicePixelRatio)
104111
{
105112
qCDebug(dsrApp) << "Grabbing X11 screenshot for rect:" << rect;
113+
114+
#ifdef USE_X11_GETIMAGE
115+
// Qt6 workaround: 使用 XGetImage 直接抓取 X11 根窗口
116+
// Qt6 的 QScreen::grabWindow 在多屏+高DPI+非对齐布局下存在 bug
117+
qCDebug(dsrApp) << "Qt6: Using XGetImage workaround for X11 screenshot";
118+
return grabWithXGetImage(ok, rect);
119+
#else
120+
// Qt5: 使用原有逻辑
106121
const QList<QScreen*> intersectingScreens = findIntersectingScreens(rect);
107122

108123
if (intersectingScreens.isEmpty()) {
@@ -117,6 +132,7 @@ QPixmap ScreenGrabber::grabX11Screenshot(bool &ok, const QRect &rect, const qrea
117132

118133
qCDebug(dsrApp) << "Multiple intersecting screens found, grabbing multiple screens.";
119134
return grabMultipleScreens(ok, rect, intersectingScreens, devicePixelRatio);
135+
#endif
120136
}
121137

122138
/**
@@ -538,3 +554,85 @@ QPixmap ScreenGrabber::grabMultipleScreens(bool &ok, const QRect &rect, const QL
538554
ok = true;
539555
return result;
540556
}
557+
558+
#ifdef USE_X11_GETIMAGE
559+
/**
560+
* @brief Qt6 workaround: 使用 XGetImage 直接抓取 X11 根窗口
561+
* @param ok Output parameter indicating screenshot success
562+
* @param rect The rectangular area to capture (in physical coordinates, since DPI scaling is disabled)
563+
* @return The captured desktop image
564+
*
565+
* Qt6 的 QScreen::grabWindow 在多屏+高DPI+非对齐布局的 X11 环境下存在 bug,
566+
* 返回的整屏截图是花屏/错乱的。此方法绕过 Qt6 的抓图 API,直接使用 X11 原生 API。
567+
*/
568+
QPixmap ScreenGrabber::grabWithXGetImage(bool &ok, const QRect &rect)
569+
{
570+
qCDebug(dsrApp) << "[XGetImage] Starting X11 screenshot for rect:" << rect;
571+
572+
Display *dpy = XOpenDisplay(nullptr);
573+
if (!dpy) {
574+
qCWarning(dsrApp) << "[XGetImage] Failed to open X11 Display";
575+
ok = false;
576+
return QPixmap();
577+
}
578+
579+
Window root = DefaultRootWindow(dpy);
580+
581+
// 获取根窗口尺寸
582+
XWindowAttributes rootAttr;
583+
XGetWindowAttributes(dpy, root, &rootAttr);
584+
qCDebug(dsrApp) << "[XGetImage] X11 root window size:" << rootAttr.width << "x" << rootAttr.height;
585+
586+
// 抓取整个根窗口
587+
XImage *ximg = XGetImage(dpy, root, 0, 0, rootAttr.width, rootAttr.height, AllPlanes, ZPixmap);
588+
if (!ximg) {
589+
qCWarning(dsrApp) << "[XGetImage] XGetImage returned NULL";
590+
XCloseDisplay(dpy);
591+
ok = false;
592+
return QPixmap();
593+
}
594+
595+
qCDebug(dsrApp) << "[XGetImage] XImage captured, size:" << ximg->width << "x" << ximg->height
596+
<< "depth:" << ximg->depth << "bits_per_pixel:" << ximg->bits_per_pixel;
597+
598+
// 将 XImage 转换为 QImage
599+
QImage qimg(ximg->width, ximg->height, QImage::Format_RGB32);
600+
for (int row = 0; row < ximg->height; ++row) {
601+
for (int col = 0; col < ximg->width; ++col) {
602+
unsigned long pixel = XGetPixel(ximg, col, row);
603+
int r = (pixel >> 16) & 0xFF;
604+
int g = (pixel >> 8) & 0xFF;
605+
int b = pixel & 0xFF;
606+
qimg.setPixel(col, row, qRgb(r, g, b));
607+
}
608+
}
609+
610+
XDestroyImage(ximg);
611+
XCloseDisplay(dpy);
612+
613+
// 转换为 QPixmap
614+
QPixmap fullPixmap = QPixmap::fromImage(qimg);
615+
qCDebug(dsrApp) << "[XGetImage] Full pixmap size:" << fullPixmap.size();
616+
617+
// 如果请求的是整个桌面,直接返回
618+
if (rect.isNull() || rect == QRect(0, 0, rootAttr.width, rootAttr.height)) {
619+
qCDebug(dsrApp) << "[XGetImage] Returning full desktop screenshot";
620+
ok = true;
621+
return fullPixmap;
622+
}
623+
624+
// 裁剪请求的区域
625+
QRect cropRect = rect.intersected(QRect(0, 0, rootAttr.width, rootAttr.height));
626+
if (cropRect.isEmpty()) {
627+
qCWarning(dsrApp) << "[XGetImage] Crop rect is empty or outside root window";
628+
ok = false;
629+
return QPixmap();
630+
}
631+
632+
QPixmap result = fullPixmap.copy(cropRect);
633+
qCDebug(dsrApp) << "[XGetImage] Cropped result size:" << result.size();
634+
635+
ok = !result.isNull();
636+
return result;
637+
}
638+
#endif

src/utils/screengrabber.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#include <QPixmap>
1111
#include <QRect>
1212
#include <QList>
13-
13+
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) && defined(QT_QPA_PLATFORM_XCB)
14+
#define USE_X11_GETIMAGE 1
15+
#endif
1416
class QScreen;
1517

1618
class ScreenGrabber : public QObject
@@ -28,6 +30,11 @@ class ScreenGrabber : public QObject
2830
QPixmap grabSingleScreen(bool &ok, const QRect &rect, QScreen *screen, const qreal devicePixelRatio);
2931
QPixmap grabMultipleScreens(bool &ok, const QRect &rect, const QList<QScreen*> &screens, const qreal devicePixelRatio);
3032
QPixmap grabScreenFragment(QScreen *screen, const QRect &intersection, const qreal devicePixelRatio);
33+
34+
#ifdef USE_X11_GETIMAGE
35+
// Qt6 X11 workaround: 使用 XGetImage 绕过 grabWindow 的 bug
36+
QPixmap grabWithXGetImage(bool &ok, const QRect &rect);
37+
#endif
3138
};
3239

3340
#endif // SCREENGRABBER_H

0 commit comments

Comments
 (0)