Skip to content

Commit 521e042

Browse files
Fix X11 multi-monitor capture on HiDPI (screen cut/shift, inflated canvas)
x11LegacyScreenshot() multiplied the united logical geometry by a single devicePixelRatio and drew each screen at its logical top-left. When the ratio is not 1 and the monitors have different sizes, the composite is wrong. On the xcb backend the virtual-desktop layout uses native (device) pixel offsets, while each screen reports a logical size (its device size divided by its ratio). Multiplying the whole union by one ratio double-counts the offsets: a 7680x3996 desktop is captured as 10560x5373, and the region the GUI overlay selects is cut or shifted onto the wrong monitor. grabWindow(0) already returns the device-pixel pixmap of each screen, and geometry().topLeft() is the native offset on xcb. So draw each pixmap at its native offset using its own physical size, and never scale. The canvas is the union of those device-pixel rectangles. Tested on X11/awesome, nvidia driver, Xft.dpi 168 (ratio 1.75), three monitors of two sizes (3264x1836 and two 3840x2160 in an L shape). Capture is now pixel-aligned to the framebuffer (per-monitor RMSE < 0.02), against 10560x5373 before.
1 parent b2b60c5 commit 521e042

1 file changed

Lines changed: 17 additions & 14 deletions

File tree

src/utils/screengrabber.cpp

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -870,26 +870,29 @@ QPixmap ScreenGrabber::x11LegacyScreenshot()
870870
return p;
871871
}
872872

873-
// Composite all screens using logical geometry.
874-
// On i3 (tested) DPR is uniform so we don't need the per-screen
875-
// physical pixel math that the Windows backend does. Not sure if this is
876-
// true for other DE's like xmonad.
877-
QRect totalGeom;
873+
// Composite in device pixels. On xcb the virtual-desktop layout uses native
874+
// offsets while each screen reports a logical size, so scaling the united
875+
// geometry by one ratio inflates and misplaces it. grabWindow(0) returns the
876+
// device-pixel pixmap and geometry().topLeft() is the native offset, so draw
877+
// each pixmap at its native offset using its own size.
878+
QRect totalRect;
879+
QList<QPair<QRect, QPixmap>> grabs;
880+
grabs.reserve(screens.size());
878881
for (QScreen* s : screens) {
879-
totalGeom = totalGeom.united(s->geometry());
882+
QPixmap p = s->grabWindow(0);
883+
p.setDevicePixelRatio(1.0);
884+
QRect r(s->geometry().topLeft(), p.size());
885+
grabs.append({ r, p });
886+
totalRect = totalRect.united(r);
880887
}
881888

882-
qreal dpr = screens.first()->devicePixelRatio();
883-
QPixmap desktop(qRound(totalGeom.width() * dpr),
884-
qRound(totalGeom.height() * dpr));
885-
desktop.setDevicePixelRatio(dpr);
889+
QPixmap desktop(totalRect.size());
890+
desktop.setDevicePixelRatio(1.0);
886891
desktop.fill(Qt::black);
887892

888893
QPainter painter(&desktop);
889-
for (QScreen* s : screens) {
890-
QPixmap p = s->grabWindow(0);
891-
QPoint offset = s->geometry().topLeft() - totalGeom.topLeft();
892-
painter.drawPixmap(offset, p);
894+
for (const auto& g : grabs) {
895+
painter.drawPixmap(g.first.topLeft() - totalRect.topLeft(), g.second);
893896
}
894897
painter.end();
895898

0 commit comments

Comments
 (0)