diff --git a/src/core/flameshot.cpp b/src/core/flameshot.cpp index 3eb1432668..2d9db670e0 100644 --- a/src/core/flameshot.cpp +++ b/src/core/flameshot.cpp @@ -160,6 +160,9 @@ void Flameshot::screen(CaptureRequest req, const int screenNumber) } else { screen = qApp->screens()[screenNumber]; } + + // TODO: Switch to the DesktopCapture, ScreenGrabber is deprecated + // It is still used her, but does not properly work QPixmap p(ScreenGrabber().grabScreen(screen, ok)); if (ok) { QRect geometry = ScreenGrabber().screenGeometry(screen); diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 23c868a46e..64de9aaa64 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources( PRIVATE abstractlogger.h filenamehandler.h screengrabber.h + DesktopCapturer.h systemnotification.h valuehandler.h strfparse.h @@ -14,6 +15,7 @@ target_sources( PRIVATE abstractlogger.cpp filenamehandler.cpp screengrabber.cpp + DesktopCapturer.cpp confighandler.cpp systemnotification.cpp valuehandler.cpp diff --git a/src/utils/DesktopCapturer.cpp b/src/utils/DesktopCapturer.cpp new file mode 100644 index 0000000000..cc6ce07583 --- /dev/null +++ b/src/utils/DesktopCapturer.cpp @@ -0,0 +1,216 @@ +#include "DesktopCapturer.h" + +#include +#include +#include +#include +#include +#include + +DesktopCapturer::DesktopCapturer() + : m_screenToDraw(nullptr) +{ + reset(); +} + +void DesktopCapturer::reset() +{ + m_composite = false; + m_geometry = QRect(0, 0, 0, 0); + m_areas.clear(); +} + +QSize DesktopCapturer::screenSize() const +{ + return m_geometry.size(); +} + +QPoint DesktopCapturer::topLeft() const +{ + return m_geometry.topLeft(); +} + +QPoint DesktopCapturer::topLeftScaledToScreen() const +{ + return screenToDraw()->geometry().topLeft() / + screenToDraw()->devicePixelRatio(); +} + +QRect DesktopCapturer::geometry() +{ + // Get Top Left and Bottom Right + QPoint maxPoint(INT_MIN, INT_MIN); + QPoint topLeft = QPoint(INT_MAX, INT_MAX); + for (QScreen const* screen : QGuiApplication::screens()) { + QRect geo = screen->geometry(); + int const width = + static_cast(geo.width() * screen->devicePixelRatio()); + int const height = + static_cast(geo.height() * screen->devicePixelRatio()); + int const maxX = width + geo.x(); + int const maxY = height + geo.y(); + + // Get Top Left + if (geo.x() < topLeft.x()) { + topLeft.setX(geo.x()); + } + if (geo.y() < topLeft.y()) { + topLeft.setY(geo.y()); + } + + // Get Bottom Right + if (maxX > maxPoint.x()) { + maxPoint.setX(maxX); + } + if (maxY > maxPoint.y()) { + maxPoint.setY(maxY); + } + } + + // Get Desktop size + m_geometry.setX(topLeft.x()); + m_geometry.setY(topLeft.y()); + m_geometry.setWidth(maxPoint.x() - topLeft.x()); + m_geometry.setHeight(maxPoint.y() - topLeft.y()); + + return m_geometry; +} + +QPixmap DesktopCapturer::captureDesktopComposite() +{ + m_screenToDraw = primaryScreen(); + qreal screenToDrawDpr = screenToDraw()->devicePixelRatio(); + + // Calculate screen geometry + geometry(); + + // Create Desktop image + QPixmap desktop(screenSize()); + desktop.fill(Qt::black); + + // Draw composite screenshot + QPainter painter(&desktop); + for (QScreen* screen : QGuiApplication::screens()) { + QRect geo = screen->geometry(); + QPixmap pix = screen->grabWindow(0); + + // Composite screenshot should have pixel ratio 1 to draw all screen + // with different ratios. + pix.setDevicePixelRatio(1); + + // Calculate the offset of the current screen + // from the top left corner of the composite screen + geo.setX(geo.x() - topLeft().x()); + geo.setY(geo.y() - topLeft().y()); + + painter.drawPixmap(geo.x(), geo.y(), pix); + + // Prepare areas + // Everything, including location, should be in logical pixels + // of the screen to draw a grabbed area (primary screen) + QRect areaRect = + QRect(static_cast(geo.x() / screenToDrawDpr), + static_cast(geo.y() / screenToDrawDpr), + static_cast(pix.width() / screenToDrawDpr), + static_cast(pix.height() / screenToDrawDpr)); + m_areas.append(areaRect); + } + painter.end(); + + // Set pixmap DevicePixelRatio of the screen where it should be drawn. + desktop.setDevicePixelRatio(screenToDraw()->devicePixelRatio()); + + return desktop; +} + +QPixmap DesktopCapturer::captureDesktopAtCursorPos() +{ + // Active is where the mouse cursor is, it can be not an active screen + QScreen* screen = screenAtCursorPos(); + QPixmap pix; + if (screen == nullptr) { + return pix; + } + pix = screen->grabWindow(0); + m_geometry = screen->geometry(); + + if (!isComposite()) { + QRect geometry = m_geometry; + geometry.moveTo(0, 0); + m_areas.append(geometry); + } + + m_geometry.setWidth( + static_cast(m_geometry.width() * screen->devicePixelRatio())); + m_geometry.setHeight( + static_cast(m_geometry.height() * screen->devicePixelRatio())); + return pix; +} + +QScreen* DesktopCapturer::screenAtCursorPos() +{ + // Get the current global position of the mouse cursor. + // This position is in the virtual desktop coordinate system, which spans + // all screens. + const QPoint mousePos = QCursor::pos(); + m_screenToDraw = primaryScreen(); + + // Iterate through all screens available to the application. + for (QScreen* screen : QGuiApplication::screens()) { + // Get the screen's geometry in the virtual desktop coordinate system. + // This is the rectangle that defines the screen's position and size. + + // Check if the screen's geometry contains the mouse cursor's position. + if (screen->geometry().contains(mousePos)) { + m_screenToDraw = screen; + break; + } + } + + // Return nullptr if the cursor is not found on any screen. + return m_screenToDraw; +} + +QPixmap DesktopCapturer::captureDesktop(bool composite) +{ + QPixmap desktop; + reset(); +#ifdef Q_OS_MAC + composite = false; +#elif defined(Q_OS_LINUX) + m_composite = composite; +#endif + if (composite) { + desktop = captureDesktopComposite(); + } else { + desktop = captureDesktopAtCursorPos(); + } + return desktop; +} + +QScreen* DesktopCapturer::screenToDraw() const +{ + return m_screenToDraw; +} + +const QList& DesktopCapturer::areas() const +{ + return m_areas; +} + +QScreen* DesktopCapturer::primaryScreen() +{ +#if (defined(Q_OS_LINUX) || defined(Q_OS_UNIX)) + // At least in Gnome+XOrg, the last screen is actually the first screen + // and all calculations are started from it, not from the PrimaryScreen. + // return QGuiApplication::screens().last(); + return QGuiApplication::primaryScreen(); +#else + return QGuiApplication::primaryScreen(); +#endif +} + +bool DesktopCapturer::isComposite() const +{ + return m_composite; +} \ No newline at end of file diff --git a/src/utils/DesktopCapturer.h b/src/utils/DesktopCapturer.h new file mode 100644 index 0000000000..6a516da504 --- /dev/null +++ b/src/utils/DesktopCapturer.h @@ -0,0 +1,94 @@ +#ifndef DESKTOP_CAPTURER_H +#define DESKTOP_CAPTURER_H + +#include +#include +#include +#include +#include +#include + +/** + * @class DesktopCapturer + * @brief Provides functionality to capture screenshots of the desktop. + * + * This class can capture either a composite screenshot of all screens + * or a screenshot of the single screen where the mouse cursor is located. + */ +class DesktopCapturer : public QObject +{ + Q_OBJECT + +public: + DesktopCapturer(); + ~DesktopCapturer() = default; + + /** + * @brief Resets the internal geometry data. + */ + void reset(); + + /** + * @brief Gets the size of the captured desktop area. + * @return The size as a QSize object. + */ + QSize screenSize() const; + + /** + * @brief Gets the top-left coordinate of the captured desktop area. + * @return The top-left point as a QPoint object. + */ + QPoint topLeft() const; + + QPoint topLeftScaledToScreen() const; + + /** + * @brief Calculates and returns the geometry of the entire virtual desktop. + * @return The geometry as a QRect object. + */ + QRect geometry(); + + /** + * @brief Captures a composite screenshot of all desktops. + * @return A QPixmap containing the combined desktop image. + */ + QPixmap captureDesktopComposite(); + + /** + * @brief Captures a screenshot of the desktop at the mouse cursor's + * position. + * @return A QPixmap of the screen where the cursor is located. + */ + QPixmap captureDesktopAtCursorPos(); + + /** + * @brief Main function to capture the desktop based on the composite flag. + * @param composite If true, captures a composite screenshot; otherwise, + * captures the screen at the cursor. + * @return The captured QPixmap. + */ + QPixmap captureDesktop(bool composite = true); + + QScreen* screenToDraw() const; + + const QList& areas() const; + + QScreen* screenAtCursorPos(); + bool isComposite() const; + +private: + /** + * @brief Finds the QScreen instance where the mouse cursor is currently + * located. + * @return A pointer to the QScreen object. + */ + + QScreen* primaryScreen(); + + QRect m_geometry; + QScreen* m_screenToDraw; + QVector m_areas; + bool m_composite; +}; + +#endif // DESKTOP_CAPTURER_H diff --git a/src/utils/screengrabber.cpp b/src/utils/screengrabber.cpp index 6f33dd57de..53b5860eae 100644 --- a/src/utils/screengrabber.cpp +++ b/src/utils/screengrabber.cpp @@ -13,22 +13,25 @@ #include #include -#if !(defined(Q_OS_MACOS) || defined(Q_OS_WIN)) +#include + +#if defined(Q_OS_LINUX) #include "request.h" #include -#include -#include #include #include #endif +// TODO: This should be removed after the complete switch to the DesktopCapture +// It is still used (but does not properly work) for non-fullscreen captures + ScreenGrabber::ScreenGrabber(QObject* parent) : QObject(parent) {} void ScreenGrabber::generalGrimScreenshot(bool& ok, QPixmap& res) { -#if !(defined(Q_OS_MACOS) || defined(Q_OS_WIN)) +#if defined(Q_OS_LINUX) if (!ConfigHandler().useGrimAdapter()) { return; } @@ -59,8 +62,7 @@ void ScreenGrabber::generalGrimScreenshot(bool& ok, QPixmap& res) void ScreenGrabber::freeDesktopPortal(bool& ok, QPixmap& res) { - -#if !(defined(Q_OS_MACOS) || defined(Q_OS_WIN)) +#if defined(Q_OS_LINUX) QDBusInterface screenshotInterface( QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"), @@ -153,7 +155,7 @@ QPixmap ScreenGrabber::grabEntireDesktop(bool& ok) currentScreen->geometry().height())); screenPixmap.setDevicePixelRatio(currentScreen->devicePixelRatio()); return screenPixmap; -#elif defined(Q_OS_LINUX) || defined(Q_OS_UNIX) +#elif defined(Q_OS_LINUX) if (m_info.waylandDetected()) { QPixmap res; // handle screenshot based on DE @@ -204,7 +206,7 @@ QPixmap ScreenGrabber::grabEntireDesktop(bool& ok) return res; } #endif -#if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) || defined(Q_OS_WIN) + // #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) || defined(Q_OS_WIN) QRect geometry = desktopGeometry(); // Qt6 fix: Create a composite image from all screens to handle @@ -221,8 +223,14 @@ QPixmap ScreenGrabber::grabEntireDesktop(bool& ok) -r.y() / primaryScreen->devicePixelRatio(), geometry.width(), geometry.height()); + + QString downloadsPath = + QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + QString filePath = downloadsPath + "/screenshot.png"; + desktop.save(filePath, "PNG"); + return desktop; -#endif + // #endif } QRect ScreenGrabber::screenGeometry(QScreen* screen) @@ -269,16 +277,31 @@ QRect ScreenGrabber::desktopGeometry() { QRect geometry; + qreal dpr = 1.0; +#ifdef Q_OS_WIN + QScreen* primaryScreen = QGuiApplication::primaryScreen(); + dpr = primaryScreen->devicePixelRatio(); + qWarning() << "ScreenGrabber::desktopGeometry() - (primaryScreen) dpr =" + << dpr; +#endif + for (QScreen* const screen : QGuiApplication::screens()) { QRect scrRect = screen->geometry(); + qWarning() << "ScreenGrabber::desktopGeometry() - scrRect =" << scrRect; // Qt6 fix: Don't divide by devicePixelRatio for multi-monitor setups // This was causing coordinate offset issues in dual monitor // configurations // But it still has a screen position in real pixels, not logical ones - qreal dpr = screen->devicePixelRatio(); +#ifdef Q_OS_LINUX + dpr = screen->devicePixelRatio(); +#endif + scrRect.moveTo(QPointF(scrRect.x() / dpr, scrRect.y() / dpr).toPoint()); + qWarning() << "ScreenGrabber::desktopGeometry() - scrRect (scaled = " + << dpr << ") =" << scrRect; geometry = geometry.united(scrRect); } + qWarning() << "ScreenGrabber::desktopGeometry() - geometry =" << geometry; return geometry; } diff --git a/src/utils/screengrabber.h b/src/utils/screengrabber.h index c1e6eebdd9..b19ea4163a 100644 --- a/src/utils/screengrabber.h +++ b/src/utils/screengrabber.h @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors +// TODO: This should be removed after the complete switch to the DesktopCapture +// It is still used (but does not properly work) for non-fullscreen captures + #pragma once #include "src/utils/desktopinfo.h" diff --git a/src/widgets/capture/buttonhandler.cpp b/src/widgets/capture/buttonhandler.cpp index 4624fdcfad..2aa21028e8 100644 --- a/src/widgets/capture/buttonhandler.cpp +++ b/src/widgets/capture/buttonhandler.cpp @@ -76,7 +76,7 @@ void ButtonHandler::updatePosition(const QRect& selection) // Copy of the selection area for internal modifications m_selection = intersectWithAreas(selection); updateBlockedSides(); - ensureSelectionMinimunSize(); + ensureSelectionMinimumSize(); // Indicates the actual button to be moved int elemIndicator = 0; @@ -290,8 +290,8 @@ void ButtonHandler::expandSelection() void ButtonHandler::positionButtonsInside(int index) { - // Position the buttons in the botton-center of the main but inside of the - // selection. + // Position the buttons at the bottom center of the main area, + // but inside the selection. QRect mainArea = m_selection; mainArea = intersectWithAreas(mainArea); const int buttonsPerRow = (mainArea.width()) / (m_buttonExtendedSize); @@ -312,7 +312,7 @@ void ButtonHandler::positionButtonsInside(int index) m_buttonsAreInside = true; } -void ButtonHandler::ensureSelectionMinimunSize() +void ButtonHandler::ensureSelectionMinimumSize() { // Detect if a side is smaller than a button in order to prevent collision // and redimension the base area the the base size of a single button per diff --git a/src/widgets/capture/buttonhandler.h b/src/widgets/capture/buttonhandler.h index ca59971e09..79119a1af4 100644 --- a/src/widgets/capture/buttonhandler.h +++ b/src/widgets/capture/buttonhandler.h @@ -73,7 +73,7 @@ public slots: void updateBlockedSides(); void expandSelection(); void positionButtonsInside(int index); - void ensureSelectionMinimunSize(); + void ensureSelectionMinimumSize(); void moveButtonsToPoints(const QVector& points, int& index); void adjustHorizontalCenter(QPoint& center); }; diff --git a/src/widgets/capture/capturewidget.cpp b/src/widgets/capture/capturewidget.cpp index fd8667869a..8bca4bde08 100644 --- a/src/widgets/capture/capturewidget.cpp +++ b/src/widgets/capture/capturewidget.cpp @@ -9,15 +9,16 @@ // Luca Gugelmann released under the GNU LGPL // -#include "capturewidget.h" +#if defined(FLAMESHOT_DEBUG_CAPTURE) +#include +#include +#endif #include "abstractlogger.h" +#include "capturewidget.h" #include "copytool.h" #include "src/config/cacheutils.h" #include "src/core/flameshot.h" #include "src/core/qguiappcurrentscreen.h" -#include "src/utils/screengrabber.h" -#include "src/utils/screenshotsaver.h" -#include "src/utils/systemnotification.h" #include "src/widgets/capture/colorpicker.h" #include "src/widgets/capture/hovereventfilter.h" #include "src/widgets/capture/modificationcommand.h" @@ -75,7 +76,8 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req, , m_xywhDisplay(false) , m_existingObjectIsChanged(false) , m_startMove(false) - + , m_ovelayMessage(nullptr) + , m_panelToggleButton(nullptr) { m_undoStack.setUndoLimit(ConfigHandler().undoLimit()); m_context.circleCount = 1; @@ -100,127 +102,10 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req, m_contrastUiColor = m_config.contrastUiColor(); setMouseTracking(true); initContext(fullScreen, req); -#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS)) - // Top left of the whole set of screens - QPoint topLeft(0, 0); -#endif - if (fullScreen) { - // Grab Screenshot - bool ok = true; - m_context.screenshot = ScreenGrabber().grabEntireDesktop(ok); - if (!ok) { - AbstractLogger::error() << tr("Unable to capture screen"); - this->close(); - } - m_context.origScreenshot = m_context.screenshot; - -#if defined(Q_OS_WIN) -// Call cmake with -DFLAMESHOT_DEBUG_CAPTURE=ON to enable easier debugging -#if !defined(FLAMESHOT_DEBUG_CAPTURE) - setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | - Qt::SubWindow // Hides the taskbar icon - ); -#endif - - for (QScreen* const screen : QGuiApplication::screens()) { - QPoint topLeftScreen = screen->geometry().topLeft(); - - if (topLeftScreen.x() < topLeft.x()) { - topLeft.setX(topLeftScreen.x()); - } - if (topLeftScreen.y() < topLeft.y()) { - topLeft.setY(topLeftScreen.y()); - } - } - move(topLeft); - resize(pixmap().size()); -#elif defined(Q_OS_MACOS) - // Emulate fullscreen mode - // setWindowFlags(Qt::WindowStaysOnTopHint | - // Qt::BypassWindowManagerHint | - // Qt::FramelessWindowHint | - // Qt::NoDropShadowWindowHint | Qt::ToolTip | - // Qt::Popup - // ); - QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); - move(currentScreen->geometry().x(), currentScreen->geometry().y()); - resize(currentScreen->size()); -// LINUX -#else -// Call cmake with -DFLAMESHOT_DEBUG_CAPTURE=ON to enable easier debugging -#if !defined(FLAMESHOT_DEBUG_CAPTURE) - setWindowFlags(Qt::BypassWindowManagerHint | Qt::WindowStaysOnTopHint | - Qt::FramelessWindowHint | Qt::Tool); - // Fix for Qt6 dual monitor offset: position widget to cover entire - // desktop - QRect desktopGeom = ScreenGrabber().desktopGeometry(); - move(desktopGeom.topLeft()); - resize(desktopGeom.size()); -#endif - // Need to move to the top left screen - QPoint topLeft(0, INT_MAX); - for (QScreen* const screen : QGuiApplication::screens()) { - qreal dpr = screen->devicePixelRatio(); - QPoint topLeftScreen = screen->geometry().topLeft() / dpr; - if (topLeftScreen.x() == 0) { - if (topLeftScreen.y() < topLeft.y()) { - topLeft.setY(topLeftScreen.y()); - } - } - } - move(topLeft); -#endif - } - QVector areas; - if (m_context.fullscreen) { - QPoint topLeftOffset = QPoint(0, 0); -#if defined(Q_OS_WIN) - topLeftOffset = topLeft; -#endif - -#if defined(Q_OS_MACOS) - // MacOS works just with one active display, so we need to append - // just one current display and keep multiple displays logic for - // other OS - QRect r; - QScreen* screen = QGuiAppCurrentScreen().currentScreen(); - r = screen->geometry(); - // all calculations are processed according to (0, 0) start - // point so we need to move current object to (0, 0) - r.moveTo(0, 0); - areas.append(r); -#else - // LINUX & WINDOWS - for (QScreen* const screen : QGuiApplication::screens()) { - QRect r = screen->geometry(); - r.moveTo(r.x() / screen->devicePixelRatio(), - r.y() / screen->devicePixelRatio()); - r.moveTo(r.topLeft() - topLeftOffset); - areas.append(r); - } -#endif - } else { - areas.append(rect()); - } - m_buttonHandler = new ButtonHandler(this); - m_buttonHandler->updateScreenRegions(areas); - m_buttonHandler->hide(); - initButtons(); - initSelection(); // button handler must be initialized before - initShortcuts(); // must be called after initSelection - // init magnify - if (m_config.showMagnifier()) { - m_magnifier = new MagnifierWidget( - m_context.screenshot, m_uiColor, m_config.squareMagnifier(), this); - } - - // Init color picker - m_colorPicker = new ColorPicker(this); - // Init notification widget - m_notifierBox = new NotifierBox(this); - initPanel(); + /////////////////////////////////////////////////////////////////////////// + initScreenshotEditor(false); // TODO: Make it more clear why this has moved. In Qt6 some timing related // to constructors / connect signals has changed so if initPanel is called @@ -257,7 +142,9 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req, ConfigHandler::getInstance(), &ConfigHandler::error, this, [=, this]() { m_configError = true; m_configErrorResolved = false; - OverlayMessage::instance()->update(); + if (m_config.showHelp()) { + OverlayMessage::instance()->update(); + } }); connect(ConfigHandler::getInstance(), &ConfigHandler::errorResolved, @@ -265,27 +152,10 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req, [=, this]() { m_configError = false; m_configErrorResolved = true; - OverlayMessage::instance()->update(); + if (m_config.showHelp()) { + OverlayMessage::instance()->update(); + } }); - - // Qt6 has only sizes in logical values, position is in physical values. - // Move Help message to the logical pixel with devicePixelRatio. - QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); - QRect currentScreenGeometry = currentScreen->geometry(); - qreal currentScreenDpr = currentScreen->devicePixelRatio(); - currentScreenGeometry.moveTo( - int(currentScreenGeometry.x() / currentScreenDpr), - int(currentScreenGeometry.y() / currentScreenDpr)); - OverlayMessage::init(this, currentScreenGeometry); - - if (m_config.showHelp()) { - initHelpMessage(); - OverlayMessage::push(m_helpMessage); - } - - initQuitPrompt(); - - updateCursor(); } CaptureWidget::~CaptureWidget() @@ -313,6 +183,143 @@ CaptureWidget::~CaptureWidget() } } +void CaptureWidget::initScreenshotEditor(const bool compositeDesktop) +{ + // Capture Desktop Screen(s) + if (m_selection != nullptr) { + m_selection->close(); + m_selection = nullptr; + } + + setWidgetFlags(); + setWindowOpacity(0.0); + + ///////////////////////////////////////////////////////////////////////////// + m_context.screenshot = m_desktopCapturer.captureDesktop(compositeDesktop); + +#if defined(FLAMESHOT_DEBUG_CAPTURE) + QString desktopPath = + QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + QString filePath = QDir(desktopPath).filePath("debug_screenshot.png"); + m_context.screenshot.save(filePath, "PNG"); +#endif + + if (m_context.screenshot.isNull()) { + AbstractLogger::error() << tr("Unable to capture screen"); + this->close(); + } + m_context.origScreenshot = m_context.screenshot; + + // Resize and move CaptureWidget + if (!compositeDesktop) { + resize(m_desktopCapturer.screenSize() / + m_desktopCapturer.screenToDraw()->devicePixelRatio()); + move(m_desktopCapturer.screenToDraw()->geometry().topLeft()); + } else { + resize(m_desktopCapturer.screenSize()); + QScreen* screenToDraw = m_desktopCapturer.screenToDraw(); + move(screenToDraw->geometry().topLeft() / + screenToDraw->devicePixelRatio()); + } + // Capture Desktop Screen(s) ^^^ + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + //////////////////////////////////////// + // Set Capture Area (drawing and tool buttons area) + QVector areas; + if (m_context.fullscreen) { + areas = m_desktopCapturer.areas(); + } else { + areas.append(rect()); + } + // Set Capture Area (drawing and tool buttons area) ^^^ + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + //////////////////////////////////////// + // Init Controls + m_buttonHandler->updateScreenRegions(areas); + m_buttonHandler->hide(); + + initButtons(); + initSelection(); // button handler must be initialized before + initShortcuts(); // must be called after initSelection + // init magnify + if (m_config.showMagnifier()) { + m_magnifier = new MagnifierWidget( + m_context.screenshot, m_uiColor, m_config.squareMagnifier(), this); + } + + // Init color picker + m_colorPicker = new ColorPicker(this); + // Init notification widget + m_notifierBox = new NotifierBox(this); + + initPanel(); + showHelp(); + initQuitPrompt(); + updateCursor(); + setWindowOpacity(1.0); +} + +void CaptureWidget::moveToActiveScreen() +{ + // hide(); + setWindowOpacity(0.0); + + m_context.screenshot = m_desktopCapturer.captureDesktop(false); + m_context.origScreenshot = m_context.screenshot; + + //////////////////////////////////////// + // Resize and move CaptureWidget + QSize screenSize = + QSizeF(m_desktopCapturer.screenSize() / + m_desktopCapturer.screenToDraw()->devicePixelRatio()) + .toSize(); + resize(screenSize); + move(m_desktopCapturer.screenToDraw()->geometry().topLeft()); + + m_buttonHandler->hide(); + m_buttonHandler->updateScreenRegions(m_desktopCapturer.areas()); + + QRect screenToDrawGeometry = geometry(); + screenToDrawGeometry.moveTo(0, 0); + + showHelp(); + + // Move "Tool Settings" button + m_panelToggleButton->move( + screenToDrawGeometry.x(), + screenToDrawGeometry.y() + + (screenToDrawGeometry.height() / 2 - m_panelToggleButton->width() / 2)); + + // Move Panel + QRect panelRect = rect(); + panelRect.setHeight(screenToDrawGeometry.height()); + panelRect.moveTo(mapFromGlobal(screenToDrawGeometry.topLeft())); + panelRect.setWidth(m_colorPicker->width() * 1.5); + m_panel->hide(); + m_panel->setGeometry(panelRect); + m_panel->move(screenToDrawGeometry.x(), screenToDrawGeometry.y()); + + // Show + setWindowOpacity(1.0); +} + +void CaptureWidget::showHelp() +{ + if (m_config.showHelp()) { + QRect screenToDrawGeometry = geometry(); + screenToDrawGeometry.moveTo(0, 0); + if (m_ovelayMessage != nullptr) { + m_ovelayMessage->close(); + m_ovelayMessage = nullptr; + } + m_ovelayMessage = new OverlayMessage(this, screenToDrawGeometry); + initHelpMessage(); + OverlayMessage::push(m_helpMessage); + } +} + void CaptureWidget::initButtons() { auto allButtonTypes = CaptureToolButton::getIterableButtonTypes(); @@ -822,6 +829,14 @@ int CaptureWidget::selectToolItemAtPos(const QPoint& pos) return activeLayerIndex; } +void CaptureWidget::leaveEvent(QEvent* event) +{ + if (!m_desktopCapturer.isComposite() && !m_selection->isVisible()) { + QTimer::singleShot(50, this, [this]() { moveToActiveScreen(); }); + } + QWidget::leaveEvent(event); +} + void CaptureWidget::mousePressEvent(QMouseEvent* e) { activateWindow(); @@ -843,7 +858,8 @@ void CaptureWidget::mousePressEvent(QMouseEvent* e) } showColorPicker(m_mousePressedPos); return; - } else if (e->button() == Qt::LeftButton) { + } + if (e->button() == Qt::LeftButton) { m_mouseIsClicked = true; // Click using a tool excluding tool MOVE @@ -1162,26 +1178,44 @@ void CaptureWidget::initPanel() #endif } + QRect screenToDrawGeometry = m_desktopCapturer.screenToDraw()->geometry(); + if (m_desktopCapturer.isComposite()) { + // This mode doesn't work correctly. + // It can grab a display without any issues with proper layouts, + // but Qt6 doesn't handle primary screen detection correctly. + // It may change depending on display locations (top/bottom, + // left/right, etc.). + // I didn't find a way to resolve it. + // Things become extremely unpredictable with three displays. + // Sometimes, even without recompilation and changing the display + // location, you may get different results. + QScreen* screenAtCursorPos = m_desktopCapturer.screenAtCursorPos(); + qreal screenToDrawDpr = + m_desktopCapturer.screenToDraw()->devicePixelRatio(); + screenToDrawGeometry.moveTo(screenAtCursorPos->geometry().topLeft() / + screenToDrawDpr); + } else { + screenToDrawGeometry.moveTo(0, 0); + } + if (ConfigHandler().showSidePanelButton()) { - auto* panelToggleButton = + if (m_panelToggleButton != nullptr) { + m_panelToggleButton->close(); + m_panelToggleButton = nullptr; + } + m_panelToggleButton = new OrientablePushButton(tr("Tool Settings"), this); - makeChild(panelToggleButton); - panelToggleButton->setColor(m_uiColor); - panelToggleButton->setOrientation( + makeChild(m_panelToggleButton); + m_panelToggleButton->setColor(m_uiColor); + m_panelToggleButton->setOrientation( OrientablePushButton::VerticalBottomToTop); -#if defined(Q_OS_MACOS) - panelToggleButton->move( - 0, - static_cast(panelRect.height() / 2) - - static_cast(panelToggleButton->width() / 2)); -#else - panelToggleButton->move(panelRect.x(), - panelRect.y() + panelRect.height() / 2 - - panelToggleButton->width() / 2); -#endif - panelToggleButton->setCursor(Qt::ArrowCursor); - (new DraggableWidgetMaker(this))->makeDraggable(panelToggleButton); - connect(panelToggleButton, + m_panelToggleButton->move(screenToDrawGeometry.x(), + screenToDrawGeometry.y() + + (screenToDrawGeometry.height() / 2 - + m_panelToggleButton->width() / 2)); + m_panelToggleButton->setCursor(Qt::ArrowCursor); + (new DraggableWidgetMaker(this))->makeDraggable(m_panelToggleButton); + connect(m_panelToggleButton, &QPushButton::clicked, this, &CaptureWidget::togglePanel); @@ -1190,16 +1224,12 @@ void CaptureWidget::initPanel() m_panel = new UtilityPanel(this); m_panel->hide(); makeChild(m_panel); -#if defined(Q_OS_MACOS) - QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); - panelRect.moveTo(mapFromGlobal(panelRect.topLeft())); - m_panel->setFixedWidth(static_cast(m_colorPicker->width() * 1.5)); - m_panel->setFixedHeight(currentScreen->geometry().height()); -#else - panelRect.moveTo(mapFromGlobal(panelRect.topLeft())); + panelRect.setHeight(screenToDrawGeometry.height()); + panelRect.moveTo(mapFromGlobal(screenToDrawGeometry.topLeft())); panelRect.setWidth(m_colorPicker->width() * 1.5); m_panel->setGeometry(panelRect); -#endif + m_panel->move(screenToDrawGeometry.x(), screenToDrawGeometry.y()); + connect(m_panel, &UtilityPanel::layerChanged, this, @@ -1289,6 +1319,10 @@ void CaptureWidget::showAppUpdateNotification(const QString& appLatestVersion, void CaptureWidget::initSelection() { // Be mindful of the order of statements, so that slots are called properly + if (m_selection != nullptr) { + m_selection->close(); + m_selection = nullptr; + } m_selection = new SelectionWidget(m_uiColor, this); QRect initialSelection = m_context.request.initialSelection(); connect(m_selection, &SelectionWidget::geometryChanged, this, [this]() { @@ -1299,7 +1333,9 @@ void CaptureWidget::initSelection() m_buttonHandler->hide(); updateCursor(); updateSizeIndicator(); - OverlayMessage::pop(); + if (m_config.showHelp()) { + OverlayMessage::pop(); + } }); connect(m_selection, &SelectionWidget::geometrySettled, this, [this]() { if (m_selection->isVisibleTo(this)) { @@ -1316,7 +1352,8 @@ void CaptureWidget::initSelection() } }); connect(m_selection, &SelectionWidget::visibilityChanged, this, [this]() { - if (!m_selection->isVisible() && !m_helpMessage.isEmpty()) { + if (!m_selection->isVisible() && !m_helpMessage.isEmpty() && + m_config.showHelp()) { OverlayMessage::push(m_helpMessage); } }); @@ -1846,8 +1883,6 @@ QPoint CaptureWidget::snapToGrid(const QPoint& point) const { QPoint snapPoint = mapToGlobal(point); - const auto scale{ m_context.screenshot.devicePixelRatio() }; - snapPoint.setX((qRound(snapPoint.x() / double(m_gridSize)) * m_gridSize)); snapPoint.setY((qRound(snapPoint.y() / double(m_gridSize)) * m_gridSize)); @@ -2033,3 +2068,16 @@ void CaptureWidget::drawInactiveRegion(QPainter* painter) painter->setClipRegion(grey); painter->drawRect(-1, -1, rect().width() + 1, rect().height() + 1); } + +void CaptureWidget::setWidgetFlags() +{ +#if defined(Q_OS_WIN) + setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | + Qt::SubWindow // Hides the taskbar icon + ); +#endif +#if defined(Q_OS_LINUX) + setWindowFlags(Qt::BypassWindowManagerHint | Qt::WindowStaysOnTopHint | + Qt::FramelessWindowHint | Qt::Tool); +#endif +} diff --git a/src/widgets/capture/capturewidget.h b/src/widgets/capture/capturewidget.h index a0230fc9bc..3306a6ea54 100644 --- a/src/widgets/capture/capturewidget.h +++ b/src/widgets/capture/capturewidget.h @@ -17,6 +17,7 @@ #include "src/config/generalconf.h" #include "src/tools/capturecontext.h" #include "src/tools/capturetool.h" +#include "src/utils/DesktopCapturer.h" #include "src/utils/confighandler.h" #include "src/widgets/capture/magnifierwidget.h" #include "src/widgets/capture/selectionwidget.h" @@ -36,6 +37,9 @@ class QNetworkReply; class ColorPicker; class NotifierBox; class HoverEventFilter; +class OverlayMessage; +class OrientablePushButton; + #if !defined(DISABLE_UPDATE_CHECKER) class UpdateNotificationWidget; #endif @@ -100,6 +104,7 @@ private slots: protected: void paintEvent(QPaintEvent* paintEvent) override; + void leaveEvent(QEvent* event) override; void mousePressEvent(QMouseEvent* mouseEvent) override; void mouseMoveEvent(QMouseEvent* mouseEvent) override; void mouseReleaseEvent(QMouseEvent* mouseEvent) override; @@ -112,6 +117,10 @@ private slots: void changeEvent(QEvent* changeEvent) override; private: + void showHelp(); + void initScreenshotEditor(bool compositeDesktop); + void moveToActiveScreen(); + void setWidgetFlags(); void pushObjectsStateToUndoStack(); void releaseActiveTool(); void uncheckActiveTool(); @@ -229,4 +238,9 @@ private slots: // Grid bool m_displayGrid{ false }; int m_gridSize{ 10 }; + + // + DesktopCapturer m_desktopCapturer; + OverlayMessage* m_ovelayMessage; + OrientablePushButton* m_panelToggleButton; }; diff --git a/src/widgets/capture/overlaymessage.cpp b/src/widgets/capture/overlaymessage.cpp index 48d8ca7d0b..72aea12fbe 100644 --- a/src/widgets/capture/overlaymessage.cpp +++ b/src/widgets/capture/overlaymessage.cpp @@ -35,11 +35,6 @@ OverlayMessage::OverlayMessage(QWidget* parent, const QRect& targetArea) QWidget::hide(); } -void OverlayMessage::init(QWidget* parent, const QRect& targetArea) -{ - new OverlayMessage(parent, targetArea); -} - /** * @brief Push a message to the message stack. * @param msg Message text formatted as rich text diff --git a/src/widgets/capture/overlaymessage.h b/src/widgets/capture/overlaymessage.h index bfea1d6fe2..1db8fe4e14 100644 --- a/src/widgets/capture/overlaymessage.h +++ b/src/widgets/capture/overlaymessage.h @@ -19,10 +19,11 @@ */ class OverlayMessage : public QLabel { + Q_OBJECT public: - OverlayMessage() = delete; + OverlayMessage(QWidget* parent, const QRect& targetArea); - static void init(QWidget* parent, const QRect& targetArea); + OverlayMessage* init(QWidget* parent, const QRect& targetArea); static void push(const QString& msg); static void pop(); static void setVisibility(bool visible); @@ -37,8 +38,6 @@ class OverlayMessage : public QLabel QColor m_fillColor, m_textColor; static OverlayMessage* m_instance; - OverlayMessage(QWidget* parent, const QRect& center); - void paintEvent(QPaintEvent*) override; QRect boundingRect() const;