Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -68,6 +68,8 @@ XmlGui Wayland ) +# for QuickEditor +find_package(KF5Screen REQUIRED) # optional components Index: src/CMakeLists.txt =================================================================== --- src/CMakeLists.txt +++ src/CMakeLists.txt @@ -88,6 +88,7 @@ KF5::GlobalAccel KF5::XmlGui KF5::WaylandClient + KF5::Screen ) if(XCB_FOUND) Index: src/Platforms/Platform.h =================================================================== --- src/Platforms/Platform.h +++ src/Platforms/Platform.h @@ -36,7 +36,8 @@ CurrentScreen = 0x02, ActiveWindow = 0x04, WindowUnderCursor = 0x08, - TransientWithParent = 0x10 + TransientWithParent = 0x10, + AllScreensNativeSize= 0x20, }; using GrabModes = QFlags; Q_FLAG(GrabModes) Index: src/Platforms/PlatformKWinWayland.h =================================================================== --- src/Platforms/PlatformKWinWayland.h +++ src/Platforms/PlatformKWinWayland.h @@ -45,4 +45,6 @@ void startReadImage(int theReadPipe); template void doGrabHelper(const QString &theGrabMethod, ArgType theArgument); template void callDBus(const QString &theGrabMethod, ArgType theArgument, int theWriteFile); + template void doGrabHelper(const QString &theGrabMethod, ArgType theArgument, ArgType2 theSecondArgument); + template void callDBus(const QString &theGrabMethod, ArgType theArgument, ArgType2 theSecondArgument, int theWriteFile); }; Index: src/Platforms/PlatformKWinWayland.cpp =================================================================== --- src/Platforms/PlatformKWinWayland.cpp +++ src/Platforms/PlatformKWinWayland.cpp @@ -84,9 +84,10 @@ Platform::GrabModes PlatformKWinWayland::supportedGrabModes() const { - Platform::GrabModes lSupportedModes({ GrabMode::AllScreens, GrabMode::WindowUnderCursor }); + Platform::GrabModes lSupportedModes({ Platform::GrabMode::CurrentScreen, GrabMode::WindowUnderCursor }); if (QApplication::screens().count() > 1) { - lSupportedModes |= Platform::GrabMode::CurrentScreen; + lSupportedModes |= Platform::GrabMode::AllScreens; + lSupportedModes |= Platform::GrabMode::AllScreensNativeSize; } return lSupportedModes; } @@ -144,14 +145,13 @@ void PlatformKWinWayland::doGrab(ShutterMode theShutterMode, GrabMode theGrabMode, bool theIncludePointer, bool theIncludeDecorations) { switch(theGrabMode) { - case GrabMode::AllScreens: { - doGrabHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer); - return; - } - case GrabMode::CurrentScreen: { - doGrabHelper(QStringLiteral("screenshotScreen"), theIncludePointer); - return; - } + case GrabMode::AllScreens: + return doGrabHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer, false); + case GrabMode::AllScreensNativeSize: + return doGrabHelper(QStringLiteral("screenshotFullscreen"), theIncludePointer, true); + break; + case GrabMode::CurrentScreen: + return doGrabHelper(QStringLiteral("screenshotScreen"), theIncludePointer); case GrabMode::WindowUnderCursor: { int lOpMask = theIncludeDecorations ? 1 : 0; if (theIncludePointer) { @@ -215,3 +215,25 @@ close(lPipeFds[1]); } + +template +void PlatformKWinWayland::callDBus(const QString &theGrabMethod, ArgType theArgument, ArgType2 theSecondArgument, int theWriteFile) +{ + QDBusInterface lInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); + lInterface.asyncCall(theGrabMethod, QVariant::fromValue(QDBusUnixFileDescriptor(theWriteFile)), theArgument, theSecondArgument ); +} + +template +void PlatformKWinWayland::doGrabHelper(const QString &theGrabMethod, ArgType theArgument, ArgType2 theSecondArgument) +{ + int lPipeFds[2]; + if (pipe2(lPipeFds, O_CLOEXEC|O_NONBLOCK) != 0) { + emit newScreenshotFailed(); + return; + } + + callDBus(theGrabMethod, theArgument, theSecondArgument, lPipeFds[1]); + startReadImage(lPipeFds[0]); + + close(lPipeFds[1]); +} Index: src/Platforms/PlatformNull.cpp =================================================================== --- src/Platforms/PlatformNull.cpp +++ src/Platforms/PlatformNull.cpp @@ -36,7 +36,7 @@ Platform::GrabModes PlatformNull::supportedGrabModes() const { - return { GrabMode::AllScreens | GrabMode::CurrentScreen | GrabMode::ActiveWindow | GrabMode::WindowUnderCursor | GrabMode::TransientWithParent }; + return { GrabMode::AllScreens | GrabMode::CurrentScreen | GrabMode::ActiveWindow | GrabMode::WindowUnderCursor | GrabMode::TransientWithParent | GrabMode::AllScreensNativeSize }; } Platform::ShutterModes PlatformNull::supportedShutterModes() const Index: src/Platforms/PlatformXcb.cpp =================================================================== --- src/Platforms/PlatformXcb.cpp +++ src/Platforms/PlatformXcb.cpp @@ -689,6 +689,7 @@ { switch(theGrabMode) { case GrabMode::AllScreens: + case GrabMode::AllScreensNativeSize: grabAllScreens(theIncludePointer); break; case GrabMode::CurrentScreen: Index: src/QuickEditor/QuickEditor.h =================================================================== --- src/QuickEditor/QuickEditor.h +++ src/QuickEditor/QuickEditor.h @@ -26,9 +26,38 @@ #include #include #include +#include + +#include class QMouseEvent; + +class ComparableQPoint : public QPoint +{ +public: + ComparableQPoint(QPoint& point): QPoint(point.x(), point.y()) + {} + + ComparableQPoint(QPoint point): QPoint(point.x(), point.y()) + {} + + ComparableQPoint(): QPoint() + {} + + // utility class that allows using QMap to sort its keys when they are QPoint + bool operator<(const ComparableQPoint &other) const { + return x() < other.x() || y() < other.y(); + } +}; + +struct OuputInfo { + qreal dpr; + QSize newSize; + QPoint originPos; + QBrush brush; +}; + namespace KWayland { namespace Client { class PlasmaShell; @@ -41,8 +70,13 @@ public: - explicit QuickEditor(const QPixmap &thePixmap, KWayland::Client::PlasmaShell *plasmashell, QWidget *parent = nullptr); - virtual ~QuickEditor() = default; + explicit QuickEditor(const QPixmap &thePixmap, KWayland::Client::PlasmaShell *plasmashell, KScreen::ConfigPtr mScreenConfiguration, QWidget *parent = nullptr); + ~QuickEditor() override { + + } + + public Q_SLOTS: + void onScreenConfigurationReceived(const KScreen::ConfigPtr mScreenConfiguration); private: @@ -129,7 +163,8 @@ int mBottomHelpGridLeftWidth; MouseState mMouseDragState; QPixmap mPixmap; - qreal dprI; + qreal devicePixelRatio; + qreal devicePixelRatioI; QPointF mMousePos; bool mMagnifierAllowed; bool mShowMagnifier; @@ -139,16 +174,26 @@ bool mDisableArrowKeys; QRect mPrimaryScreenGeo; int mbottomHelpLength; + QRegion mScreenRegion; + bool inWayland = false; // Midpoints of handles QVector mHandlePositions = QVector {8}; // Radius of handles is either handleRadiusMouse or handleRadiusTouch int mHandleRadius; + KScreen::ConfigPtr mScreenConfiguration; + QMap mRects; Q_SIGNALS: void grabDone(const QPixmap &thePixmap); void grabCancelled(); + +private: + + QMap computeCoordinatesAfterScaling(QMap> outputsRect); + + void preparePaint(); }; #endif // QUICKEDITOR_H Index: src/QuickEditor/QuickEditor.cpp =================================================================== --- src/QuickEditor/QuickEditor.cpp +++ src/QuickEditor/QuickEditor.cpp @@ -25,8 +25,11 @@ #include #include +#include #include "QuickEditor.h" #include "settings.h" +#include +#include const int QuickEditor::handleRadiusMouse = 9; const int QuickEditor::handleRadiusTouch = 12; @@ -53,8 +56,9 @@ const int QuickEditor::magPixels = 16; const int QuickEditor::magOffset = 32; -QuickEditor::QuickEditor(const QPixmap &thePixmap, KWayland::Client::PlasmaShell *plasmashell, QWidget *parent) : +QuickEditor::QuickEditor(const QPixmap &thePixmap, KWayland::Client::PlasmaShell *plasmashell, KScreen::ConfigPtr screenConfiguration, QWidget *parent) : QWidget(parent), + mScreenConfiguration(screenConfiguration), mMaskColor(QColor::fromRgbF(0, 0, 0, 0.15)), mStrokeColor(palette().highlight().color()), mCrossColor(QColor::fromRgbF(mStrokeColor.redF(), mStrokeColor.greenF(), mStrokeColor.blueF(), 0.7)), @@ -89,8 +93,12 @@ setAttribute(Qt::WA_StaticContents); setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Popup | Qt::WindowStaysOnTopHint); - dprI = 1.0 / devicePixelRatioF(); - setGeometry(0, 0, static_cast(mPixmap.width() * dprI), static_cast(mPixmap.height() * dprI)); + inWayland = plasmashell != nullptr; + + devicePixelRatio = plasmashell ? 1.0 : devicePixelRatioF(); + devicePixelRatioI = 1.0 / devicePixelRatio; + + setGeometry(0, 0, static_cast(mPixmap.width() * devicePixelRatioI), static_cast(mPixmap.height() * devicePixelRatioI)); // TODO This is a hack until a better interface is available if (plasmashell) { using namespace KWayland::Client; @@ -108,10 +116,10 @@ QRect cropRegion = QRect(savedRect[0], savedRect[1], savedRect[2], savedRect[3]); if (!cropRegion.isEmpty()) { mSelection = QRectF( - cropRegion.x() * dprI, - cropRegion.y() * dprI, - cropRegion.width() * dprI, - cropRegion.height() * dprI + cropRegion.x() * devicePixelRatioI, + cropRegion.y() * devicePixelRatioI, + cropRegion.width() * devicePixelRatioI, + cropRegion.height() * devicePixelRatioI ).intersected(geometry()); } setMouseCursor(QCursor::pos()); @@ -136,24 +144,36 @@ } layoutBottomHelpText(); + if (screenConfiguration) { + preparePaint(); + } + update(); } void QuickEditor::acceptSelection() { if (!mSelection.isEmpty()) { - const qreal dpr = devicePixelRatioF(); QRect scaledCropRegion = QRect( - qRound(mSelection.x() * dpr), - qRound(mSelection.y() * dpr), - qRound(mSelection.width() * dpr), - qRound(mSelection.height() * dpr) + qRound(mSelection.x() * devicePixelRatio), + qRound(mSelection.y() * devicePixelRatio), + qRound(mSelection.width() * devicePixelRatio), + qRound(mSelection.height() * devicePixelRatio) ); Settings::setCropRegion({scaledCropRegion.x(), scaledCropRegion.y(), scaledCropRegion.width(), scaledCropRegion.height()}); emit grabDone(mPixmap.copy(scaledCropRegion)); } } +void QuickEditor::onScreenConfigurationReceived(KScreen::ConfigPtr screenConfiguration) +{ + mScreenConfiguration = screenConfiguration; + + preparePaint(); + // repaint once we have screen information + update(); +} + void QuickEditor::keyPressEvent(QKeyEvent* event) { const auto modifiers = event->modifiers(); @@ -175,12 +195,12 @@ break; } const qreal step = (shiftPressed ? 1 : magnifierLargeStep); - const int newPos = boundsUp(qRound(mSelection.top() * devicePixelRatioF() - step), false); + const int newPos = boundsUp(qRound(mSelection.top() * devicePixelRatio - step), false); if (modifiers & Qt::AltModifier) { - mSelection.setBottom(dprI * newPos + mSelection.height()); + mSelection.setBottom(devicePixelRatioI * newPos + mSelection.height()); mSelection = mSelection.normalized(); } else { - mSelection.moveTop(dprI * newPos); + mSelection.moveTop(devicePixelRatioI * newPos); } update(); break; @@ -191,11 +211,11 @@ break; } const qreal step = (shiftPressed ? 1 : magnifierLargeStep); - const int newPos = boundsRight(qRound(mSelection.left() * devicePixelRatioF() + step), false); + const int newPos = boundsRight(qRound(mSelection.left() * devicePixelRatio + step), false); if (modifiers & Qt::AltModifier) { - mSelection.setRight(dprI * newPos + mSelection.width()); + mSelection.setRight(devicePixelRatioI * newPos + mSelection.width()); } else { - mSelection.moveLeft(dprI * newPos); + mSelection.moveLeft(devicePixelRatioI * newPos); } update(); break; @@ -206,11 +226,11 @@ break; } const qreal step = (shiftPressed ? 1 : magnifierLargeStep); - const int newPos = boundsDown(qRound(mSelection.top() * devicePixelRatioF() + step), false); + const int newPos = boundsDown(qRound(mSelection.top() * devicePixelRatio + step), false); if (modifiers & Qt::AltModifier) { - mSelection.setBottom(dprI * newPos + mSelection.height()); + mSelection.setBottom(devicePixelRatioI * newPos + mSelection.height()); } else { - mSelection.moveTop(dprI * newPos); + mSelection.moveTop(devicePixelRatioI * newPos); } update(); break; @@ -221,12 +241,12 @@ break; } const qreal step = (shiftPressed ? 1 : magnifierLargeStep); - const int newPos = boundsLeft(qRound(mSelection.left() * devicePixelRatioF() - step), false); + const int newPos = boundsLeft(qRound(mSelection.left() * devicePixelRatio - step), false); if (modifiers & Qt::AltModifier) { - mSelection.setRight(dprI * newPos + mSelection.width()); + mSelection.setRight(devicePixelRatioI * newPos + mSelection.width()); mSelection = mSelection.normalized(); } else { - mSelection.moveLeft(dprI * newPos); + mSelection.moveLeft(devicePixelRatioI * newPos); } update(); break; @@ -251,7 +271,7 @@ if (newTopLeftX < 0) { if (mouse) { // tweak startPos to prevent rectangle from getting stuck - mStartPos.setX(mStartPos.x() + newTopLeftX * dprI); + mStartPos.setX(mStartPos.x() + newTopLeftX * devicePixelRatioI); } newTopLeftX = 0; } @@ -261,27 +281,23 @@ int QuickEditor::boundsRight(int newTopLeftX, const bool mouse) { - // the max X coordinate of the top left point - const int realMaxX = qRound((width() - mSelection.width()) * devicePixelRatioF()); + const int realMaxX = qRound((width() - mSelection.width()) * devicePixelRatio); const int xOffset = newTopLeftX - realMaxX; if (xOffset > 0) { if (mouse) { - mStartPos.setX(mStartPos.x() + xOffset * dprI); + mStartPos.setX(mStartPos.x() + xOffset * devicePixelRatioI); } newTopLeftX = realMaxX; } return newTopLeftX; - - - } int QuickEditor::boundsUp(int newTopLeftY, const bool mouse) { if (newTopLeftY < 0) { if (mouse) { - mStartPos.setY(mStartPos.y() + newTopLeftY * dprI); + mStartPos.setY(mStartPos.y() + newTopLeftY * devicePixelRatioI); } newTopLeftY = 0; } @@ -292,11 +308,11 @@ int QuickEditor::boundsDown(int newTopLeftY, const bool mouse) { // the max Y coordinate of the top left point - const int realMaxY = qRound((height() - mSelection.height()) * devicePixelRatioF()); + const int realMaxY = qRound((height() - mSelection.height()) * devicePixelRatio); const int yOffset = newTopLeftY - realMaxY; if (yOffset > 0) { if (mouse) { - mStartPos.setY(mStartPos.y() + yOffset * dprI); + mStartPos.setY(mStartPos.y() + yOffset * devicePixelRatioI); } newTopLeftY = realMaxY; } @@ -307,9 +323,9 @@ void QuickEditor::mousePressEvent(QMouseEvent* event) { if(event->source() == Qt::MouseEventNotSynthesized) { - mHandleRadius = handleRadiusMouse; + mHandleRadius = handleRadiusMouse; } else { - mHandleRadius = handleRadiusTouch; + mHandleRadius = handleRadiusTouch; } if (event->button() & Qt::LeftButton) { @@ -380,18 +396,18 @@ mSelection.setRect( afterX ? mStartPos.x() : pos.x(), afterY ? mStartPos.y() : pos.y(), - qAbs(pos.x() - mStartPos.x()) + (afterX ? dprI : 0), - qAbs(pos.y() - mStartPos.y()) + (afterY ? dprI : 0) + qAbs(pos.x() - mStartPos.x()) + (afterX ? devicePixelRatioI : 0), + qAbs(pos.y() - mStartPos.y()) + (afterY ? devicePixelRatioI : 0) ); update(); break; } case MouseState::Outside: { mSelection.setRect( qMin(pos.x(), mStartPos.x()), qMin(pos.y(), mStartPos.y()), - qAbs(pos.x() - mStartPos.x()) + dprI, - qAbs(pos.y() - mStartPos.y()) + dprI + qAbs(pos.x() - mStartPos.x()) + devicePixelRatioI, + qAbs(pos.y() - mStartPos.y()) + devicePixelRatioI ); update(); break; @@ -403,7 +419,7 @@ mSelection.x(), afterY ? mStartPos.y() : pos.y(), mSelection.width(), - qAbs(pos.y() - mStartPos.y()) + (afterY ? dprI : 0) + qAbs(pos.y() - mStartPos.y()) + (afterY ? devicePixelRatioI : 0) ); update(); break; @@ -414,7 +430,7 @@ mSelection.setRect( afterX ? mStartPos.x() : pos.x(), mSelection.y(), - qAbs(pos.x() - mStartPos.x()) + (afterX ? dprI : 0), + qAbs(pos.x() - mStartPos.x()) + (afterX ? devicePixelRatioI : 0), mSelection.height() ); update(); @@ -426,22 +442,20 @@ // move the rectangle with moves it out of bounds, // in which case we adjust the diff to not let that happen - const qreal dpr = devicePixelRatioF(); // new top left point of the rectangle - QPoint newTopLeft = ((pos - mStartPos + mInitialTopLeft) * dpr).toPoint(); + QPoint newTopLeft = ((pos - mStartPos + mInitialTopLeft) * devicePixelRatio).toPoint(); - int newTopLeftX = boundsLeft(newTopLeft.x()); - if (newTopLeftX != 0) { - newTopLeftX = boundsRight(newTopLeftX); - } + auto newRect = QRect(newTopLeft, mSelection.size().toSize() * devicePixelRatio); - int newTopLeftY = boundsUp(newTopLeft.y()); - if (newTopLeftY != 0) { - newTopLeftY = boundsDown(newTopLeftY); + auto screenBoundingRect = mScreenRegion.boundingRect(); + screenBoundingRect = QRect(screenBoundingRect.topLeft(), screenBoundingRect.size()); + if (!screenBoundingRect.contains(newRect)) { + // Keep the item inside the scene screen region bounding rect. + newTopLeft.setX(qMin(screenBoundingRect.right() - newRect.width(), qMax(newTopLeft.x(), screenBoundingRect.left()))); + newTopLeft.setY(qMin(screenBoundingRect.bottom() - newRect.height(), qMax(newTopLeft.y(), screenBoundingRect.top()))); } - const auto newTopLeftF = QPointF(newTopLeftX * dprI, newTopLeftY * dprI); - + const auto newTopLeftF = QPointF(newTopLeft.x() * devicePixelRatioI, newTopLeft.y() * devicePixelRatioI); mSelection.moveTo(newTopLeftF); update(); break; @@ -458,10 +472,9 @@ const auto button = event->button(); if (button == Qt::LeftButton) { mDisableArrowKeys = false; - if(mMouseDragState == MouseState::Inside) { + if (mMouseDragState == MouseState::Inside) { setCursor(Qt::OpenHandCursor); - } - else if(mMouseDragState == MouseState::Outside && mReleaseToCapture) { + } else if (mMouseDragState == MouseState::Outside && mReleaseToCapture) { event->accept(); mMouseDragState = MouseState::None; return acceptSelection(); @@ -483,19 +496,129 @@ } } -void QuickEditor::paintEvent(QPaintEvent*) +QMap QuickEditor::computeCoordinatesAfterScaling(QMap> outputsRect) { + QMap translationMap; + + for (auto i = outputsRect.keyBegin(); i != outputsRect.keyEnd(); ++i) { + translationMap.insert(*i, *i); + } + + for (auto i = outputsRect.constBegin(); i != outputsRect.constEnd(); ++i) { + const auto p = i.key(); + const auto size = i.value().second; + const auto dpr = i.value().first; + if (!qFuzzyCompare(dpr, 1.0)) { + // must update all coordinates of next rects + int newWidth = size.width(); + int newHeight = size.height(); + + int deltaX = newWidth - (size.width()); + int deltaY = newHeight - (size.height()); + + // for the next size + for (auto i2 = outputsRect.constFind(p); i2 != outputsRect.constEnd(); ++i2) { + + auto point = i2.key(); + auto finalPoint = translationMap.value(point); + + if (point.x() >= newWidth + p.x() - deltaX) { + finalPoint.setX(finalPoint.x() + deltaX); + } + if (point.y() >= newHeight + p.y() - deltaY) { + finalPoint.setY(finalPoint.y() + deltaY); + } + // update final position point with the necessary deltas + translationMap.insert(point, finalPoint); + } + } + } + + // make the new values effective + QMap result; + for (auto i = translationMap.keyBegin(); i != translationMap.keyEnd(); ++i) { + const auto key = *i; + const auto v = outputsRect.take(key); + const qreal dpr = v.first; + const QSize size = v.second; + result.insert(translationMap.value(key), {dpr, size, key, QBrush()}); + } + return result; +} + + +void QuickEditor::preparePaint() +{ + mScreenRegion = QRegion(); + + QMap> outputsRect; + + for (const auto output : mScreenConfiguration->outputs()) { + + const ComparableQPoint originPos = output->pos(); + const QSize logicalSize = output->logicalSize().toSize(); + + outputsRect.insert(ComparableQPoint(originPos), + QPair( + inWayland ? output->scale(): devicePixelRatio, + inWayland ? logicalSize * devicePixelRatio : logicalSize )); + } + + mRects = QuickEditor::computeCoordinatesAfterScaling(outputsRect); + + for (auto i = mRects.begin(); i != mRects.end(); ++i) { + + const auto dpr = i.value().dpr; + const auto dprI = 1.0 / dpr; + + const auto virtualScreenRect = QRect(i.value().originPos, i.value().newSize); + mScreenRegion = mScreenRegion.united(virtualScreenRect); + + const auto intersect = QRect(i.key(), i.value().newSize); + + // must this out of paintEvent, using a pixmapCache/brush in OuputInfo + auto pix = mPixmap.copy(intersect); + + QBrush brush(pix); + brush.setTransform(QTransform().scale(dprI, dprI)); + + i.value().brush = brush; + } +} + +void QuickEditor::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + // Must do a paint Per screen in Wayland to respect the devicePixelRatio per screen + if (mScreenConfiguration.isNull()) { + return; + } + QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing); - QBrush brush(mPixmap); - brush.setTransform(QTransform().scale(dprI, dprI)); - painter.setBackground(brush); painter.eraseRect(geometry()); + + for (auto i = mRects.begin(); i != mRects.end(); ++i) { + const auto dpr = i.value().dpr; + + QRect virtualScreenRect = QRect(i.value().originPos / dpr, i.value().newSize * dpr); + if (inWayland) { + virtualScreenRect = QRect(virtualScreenRect.topLeft(), virtualScreenRect.size() / dpr); + } + + painter.setBrushOrigin(i.value().originPos / dpr); + painter.fillRect(virtualScreenRect, i.value().brush); + } + if (!mSelection.size().isEmpty() || mMouseDragState != MouseState::None) { - painter.fillRect(mSelection, mStrokeColor); const QRectF innerRect = mSelection.adjusted(1, 1, -1, -1); if (innerRect.width() > 0 && innerRect.height() > 0) { - painter.eraseRect(mSelection.adjusted(1, 1, -1, -1)); + painter.setPen(mStrokeColor); + painter.drawLine(mSelection.topLeft(), mSelection.topRight()); + painter.drawLine(mSelection.bottomRight(), mSelection.topRight()); + painter.drawLine(mSelection.bottomRight(), mSelection.bottomLeft()); + painter.drawLine(mSelection.bottomLeft(), mSelection.topLeft()); } QRectF top(0, 0, width(), mSelection.top()); @@ -540,8 +663,8 @@ contentWidth = qMax(contentWidth, mBottomHelpGridLeftWidth + maxRightWidth + bottomHelpBoxPairSpacing); contentHeight += (i != bottomHelpMaxLength ? bottomHelpBoxMarginBottom : 0); } - mBottomHelpContentPos.setX((mPrimaryScreenGeo.width() - contentWidth) / 2 + mPrimaryScreenGeo.x()); - mBottomHelpContentPos.setY(height() - contentHeight - 8); + mBottomHelpContentPos.setX((mPrimaryScreenGeo.width() - contentWidth) / 2 + mPrimaryScreenGeo.x() / devicePixelRatio); + mBottomHelpContentPos.setY((mPrimaryScreenGeo.height() + mPrimaryScreenGeo.y() / devicePixelRatio) - contentHeight - 8 ); mBottomHelpGridLeftWidth += mBottomHelpContentPos.x(); mBottomHelpBorderBox.setRect( mBottomHelpContentPos.x() - bottomHelpBoxPaddingX, @@ -671,7 +794,7 @@ void QuickEditor::drawMagnifier(QPainter &painter) { const int pixels = 2 * magPixels + 1; - int magX = static_cast(mMousePos.x() * devicePixelRatioF() - magPixels); + int magX = static_cast(mMousePos.x() * devicePixelRatio - magPixels); int offsetX = 0; if (magX < 0) { offsetX = magX; @@ -683,7 +806,7 @@ magX = maxX; } } - int magY = static_cast(mMousePos.y() * devicePixelRatioF() - magPixels); + int magY = static_cast(mMousePos.y() * devicePixelRatio - magPixels); int offsetY = 0; if (magY < 0) { offsetY = magY; @@ -726,7 +849,8 @@ painter.fillRect(geometry(), mMaskColor); painter.setFont(mMidHelpTextFont); QRect textSize = painter.boundingRect(QRect(), Qt::AlignCenter, mMidHelpText); - QPoint pos((mPrimaryScreenGeo.width() - textSize.width()) / 2 + mPrimaryScreenGeo.x(), (height() - textSize.height()) / 2); + QPoint pos((mPrimaryScreenGeo.width() - textSize.width()) / 2 + mPrimaryScreenGeo.x(), + (mPrimaryScreenGeo.height() - textSize.height()) / 2 + mPrimaryScreenGeo.y()); painter.setBrush(mLabelBackgroundColor); QPen pen(mLabelForegroundColor); @@ -744,8 +868,7 @@ // - vertically centered inside the selection if the box is not covering the a large part of selection // - on top of the selection if the selection x position fits the box height plus some margin // - at the bottom otherwise - const qreal dpr = devicePixelRatioF(); - QString selectionSizeText = ki18n("%1×%2").subs(qRound(mSelection.width() * dpr)).subs(qRound(mSelection.height() * dpr)).toString(); + QString selectionSizeText = ki18n("%1×%2").subs(qRound(mSelection.width() * devicePixelRatio)).subs(qRound(mSelection.height() * devicePixelRatio)).toString(); const QRect selectionSizeTextRect = painter.boundingRect(QRect(), 0, selectionSizeText); const int selectionBoxWidth = selectionSizeTextRect.width() + selectionBoxPaddingX * 2; @@ -841,21 +964,21 @@ }; //Rectangle can be resized when border is dragged, if it's big enough - if(mSelection.width() >= 100 && mSelection.height() >= 100) { - if(inRange(mSelection.x(), mSelection.x() + mSelection.width(), pos.x())) { - if(withinThreshold(pos.y() - mSelection.y(), borderDragAreaSize)) { - return MouseState::Top; - } else if(withinThreshold(pos.y() - mSelection.y() - mSelection.height(), borderDragAreaSize)) { - return MouseState::Bottom; + if (mSelection.width() >= 100 && mSelection.height() >= 100) { + if (inRange(mSelection.x(), mSelection.x() + mSelection.width(), pos.x())) { + if (withinThreshold(pos.y() - mSelection.y(), borderDragAreaSize)) { + return MouseState::Top; + } else if (withinThreshold(pos.y() - mSelection.y() - mSelection.height(), borderDragAreaSize)) { + return MouseState::Bottom; + } } - } - if(inRange(mSelection.y(), mSelection.y() + mSelection.height(), pos.y())) { - if(withinThreshold(pos.x() - mSelection.x(), borderDragAreaSize)) { - return MouseState::Left; - } else if(withinThreshold(pos.x() - mSelection.x() - mSelection.width(), borderDragAreaSize)) { - return MouseState::Right; + if (inRange(mSelection.y(), mSelection.y() + mSelection.height(), pos.y())) { + if (withinThreshold(pos.x() - mSelection.x(), borderDragAreaSize)) { + return MouseState::Left; + } else if (withinThreshold(pos.x() - mSelection.x() - mSelection.width(), borderDragAreaSize)) { + return MouseState::Right; + } } - } } if (mSelection.contains(pos)) { return MouseState::Inside; Index: src/SpectacleCore.h =================================================================== --- src/SpectacleCore.h +++ src/SpectacleCore.h @@ -67,6 +67,7 @@ void allDone(); void filenameChanged(const QString &filename); void grabFailed(); + void screenConfigurationReceived(const KScreen::ConfigPtr mScreenConfiguration); public Q_SLOTS: @@ -96,4 +97,5 @@ bool mIsGuiInited; bool mCopyToClipboard; KWayland::Client::PlasmaShell *mWaylandPlasmashell; + KScreen::ConfigPtr mScreenConfiguration = nullptr; }; Index: src/SpectacleCore.cpp =================================================================== --- src/SpectacleCore.cpp +++ src/SpectacleCore.cpp @@ -35,6 +35,9 @@ #include #include +#include +#include + #include #include #include @@ -146,8 +149,15 @@ Settings::setCropRegion({0, 0, 0, 0}); } - switch (mStartMode) { + // load screen configuration, necessary to get scaling per screen info for QuickEditor + connect(new KScreen::GetConfigOperation(), &KScreen::ConfigOperation::finished, this, + [this](KScreen::ConfigOperation *op) { + mScreenConfiguration = qobject_cast(op)->config(); + + screenConfigurationReceived(mScreenConfiguration); + }); + switch (mStartMode) { case StartMode::DBus: qApp->setQuitOnLastWindowClosed(false); break; @@ -285,9 +295,10 @@ if (lExportManager->captureMode() == Spectacle::CaptureMode::RectangularRegion) { if(!mQuickEditor) { - mQuickEditor = std::make_unique(thePixmap, mWaylandPlasmashell); + mQuickEditor = std::make_unique(thePixmap, mWaylandPlasmashell, mScreenConfiguration); connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated); connect(mQuickEditor.get(), &QuickEditor::grabCancelled, this, &SpectacleCore::screenshotFailed); + connect(this, &SpectacleCore::screenConfigurationReceived, mQuickEditor.get(), &QuickEditor::onScreenConfigurationReceived); mQuickEditor->show(); return; } else { @@ -475,8 +486,9 @@ case Spectacle::CaptureMode::InvalidChoice: return Platform::GrabMode::InvalidChoice; case Spectacle::CaptureMode::AllScreens: - case Spectacle::CaptureMode::RectangularRegion: return Platform::GrabMode::AllScreens; + case Spectacle::CaptureMode::RectangularRegion: + return Platform::GrabMode::AllScreensNativeSize; case Spectacle::CaptureMode::TransientWithParent: return Platform::GrabMode::TransientWithParent; case Spectacle::CaptureMode::CurrentScreen: