From 9a06db4a92af91ff6487123886a13d430ac454f2 Mon Sep 17 00:00:00 2001 From: Artem Lytkin <146867384+4RH1T3CT0R7@users.noreply.github.com> Date: Fri, 28 Aug 2026 03:46:02 +0300 Subject: [PATCH 1/3] Fix pin size on Windows for scaled screen (#4907) Pinning a capture on a display with 150% scaling produces a pin window half again as large as the region that was selected. #4614 fixed the position of that window for v14.0.0; this is the size. A capture started from the tray icon is fine, because the tray lives in the daemon and createPin() hands the pixmap straight to attachPin(). Any invocation that runs in its own process instead -- `flameshot gui` from a shell, a desktop or AutoHotkey shortcut, D-Bus -- finds FlameshotDaemon::instance() null and serializes the capture to the daemon. QDataStream writes a QPixmap as a plain image, which carries no device pixel ratio, so the daemon reconstructs a pixmap that claims a ratio of 1. PinWidget then lays out device pixels as if they were logical ones and the window comes out too large by the scale factor. The built-in PrintScreen hook is not affected; it runs inside the daemon and takes the in-process path. Send the ratio next to the pixmap so the serialized path ends up with the same pixmap the in-process path already gets. Both transports share the reading half, since the D-Bus adapter and the KDSingleApplication handler each deserialize the message themselves. A message from an older flameshot has no ratio appended; the read runs past the end, the stream reports it, and the pixmap keeps the ratio it already had. Checked on Windows 11, 1024x768 screen, QT_SCALE_FACTOR=1.5, pinning a 400x300 region. The pin window measured 621x471 before and 422x321 after. The capture is 400x300 device pixels either way, so the pin should be that plus the 7pt margin on each side, and the patched size matches the 414x314 measured on an unscaled screen to within the scaled margin. Unscaled displays measured 414x314 both before and after. --- data/dbus/org.flameshot.Flameshot.xml | 3 ++- src/core/flameshotdaemon.cpp | 31 ++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/data/dbus/org.flameshot.Flameshot.xml b/data/dbus/org.flameshot.Flameshot.xml index 9890451650..222ff0a107 100644 --- a/data/dbus/org.flameshot.Flameshot.xml +++ b/data/dbus/org.flameshot.Flameshot.xml @@ -11,7 +11,8 @@ diff --git a/src/core/flameshotdaemon.cpp b/src/core/flameshotdaemon.cpp index 298f00041c..3784beaaf1 100644 --- a/src/core/flameshotdaemon.cpp +++ b/src/core/flameshotdaemon.cpp @@ -39,6 +39,26 @@ #include "core/globalshortcutfilter.h" #endif +namespace { +/** + * @brief Read a pin message written by FlameshotDaemon::createPin. + * + * A QPixmap loses its device pixel ratio when it goes through a QDataStream, + * so it travels next to the pixmap and is restored here. Without it the pin is + * laid out in device pixels and comes out too big on a scaled screen. Messages + * from an older flameshot don't carry it, in which case the pixmap keeps the + * ratio it was created with. + */ +void readPin(QDataStream& stream, QPixmap& pixmap, QRect& geometry) +{ + qreal devicePixelRatio = 0; + stream >> pixmap >> geometry >> devicePixelRatio; + if (stream.status() == QDataStream::Ok && devicePixelRatio > 0) { + pixmap.setDevicePixelRatio(devicePixelRatio); + } +} +} + /** * @brief A way of accessing the flameshot daemon both from the daemon itself, * and from subcommands. @@ -121,13 +141,15 @@ void FlameshotDaemon::createPin(const QPixmap& capture, QRect geometry) QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); + // A QPixmap loses its device pixel ratio when streamed, so send it along. #if defined(USE_KDSINGLEAPPLICATION) && \ (defined(Q_OS_MACOS) || defined(Q_OS_WIN)) auto kdsa = KDSingleApplication(QStringLiteral("org.flameshot.Flameshot")); - stream << QStringLiteral("attachPin") << capture << geometry; + stream << QStringLiteral("attachPin") << capture << geometry + << capture.devicePixelRatio(); kdsa.sendMessage(data); #else - stream << capture << geometry; + stream << capture << geometry << capture.devicePixelRatio(); QDBusMessage m = createMethodCall(QStringLiteral("attachPin")); m << data; call(m); @@ -330,8 +352,7 @@ void FlameshotDaemon::attachPin(const QByteArray& data) QPixmap pixmap; QRect geometry; - stream >> pixmap; - stream >> geometry; + readPin(stream, pixmap, geometry); attachPin(pixmap, geometry); } @@ -474,7 +495,7 @@ void FlameshotDaemon::messageReceivedFromSecondaryInstance( if (methodCall == QStringLiteral("attachPin")) { QPixmap capture; QRect geometry; - stream >> capture >> geometry; + readPin(stream, capture, geometry); // qDebug() << "Pixmap:" << capture; // qDebug() << "Geometry:" << geometry; if (!capture.isNull()) { From c65fb77e8985e6fda95e8a5e5a506c8ed0a0b79d Mon Sep 17 00:00:00 2001 From: Brad Sawyer <2214561+sawy3r@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:46:36 +1000 Subject: [PATCH 2/3] Fix clipboard copy on COSMIC Wayland (closes #4823) (#4902) The GNOME clipboard workaround (keeping the capture window alive so Wayland can serve clipboard data after the window closes) was gated to GNOME only. COSMIC hits the exact same Wayland limitation but never triggered the workaround, so copy-to-clipboard silently did nothing on COSMIC. Extends the check to include COSMIC. Also fixes a race in the workaround itself: it previously closed the window on the first clipboard read of any kind, which could cut off a paste consumer that probes available clipboard types before issuing the real data fetch. It now closes only once the clipboard's current data is no longer ours, and the safety-net timeout is raised from 500ms to 30s, since COSMIC's compositor fetches clipboard data lazily (on actual paste) rather than eagerly grabbing it like GNOME's mutter does. Verified on Pop!_OS 24.04 with COSMIC 1.0.0. --- src/utils/screenshotsaver.cpp | 34 ++++++++++++++++++++------- src/widgets/capture/capturewidget.cpp | 15 ++++++------ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/utils/screenshotsaver.cpp b/src/utils/screenshotsaver.cpp index ae5a14c3e0..5c7fefe7de 100644 --- a/src/utils/screenshotsaver.cpp +++ b/src/utils/screenshotsaver.cpp @@ -238,11 +238,6 @@ class ClipboardWatcherMimeData : public QMimeData return; m_notified = true; AbstractLogger::info() << QObject::tr("Capture saved to clipboard."); - QPointer guard = m_owner; - QTimer::singleShot(0, [guard]() { - if (guard) - guard->close(); - }); } QImage m_image; @@ -258,10 +253,31 @@ bool saveToClipboardGnomeWorkaround(const QPixmap& pixmap, QWidget* keepAlive) keepAlive->hide(); - // Safety net: force close after 500ms if compositor never fetches - QTimer::singleShot(500, keepAlive, [keepAlive]() { - qWarning() << "GNOME workaround timed out, compositor did not request " - "clipboard data within 500ms. Force closing."; + // Close once something else takes clipboard ownership, rather than + // after the first read: some paste consumers probe the clipboard + // (e.g. checking available types) before issuing the real fetch, and + // closing on that first read would cut them off before it arrives. + // dataChanged also fires for our own setMimeData() call above (the + // Wayland clipboard claim is confirmed asynchronously by the + // compositor), so we only close once the clipboard's current data is + // no longer ours, rather than closing on the first emission. + QObject::connect(clipboard, + &QClipboard::dataChanged, + keepAlive, + [clipboard, mimeData, keepAlive]() { + if (clipboard->mimeData() == mimeData) + return; + if (keepAlive) + keepAlive->close(); + }); + + // Safety net: force close if nothing ever fetches the data. GNOME's + // mutter grabs clipboard offers eagerly on copy, so 500ms was enough + // there, but compositors that fetch lazily (e.g. COSMIC) only request + // the data once the user actually pastes, which can take much longer. + QTimer::singleShot(30000, keepAlive, [keepAlive]() { + qWarning() << "Clipboard workaround timed out, compositor did not " + "request clipboard data within 30s. Force closing."; if (keepAlive) keepAlive->close(); }); diff --git a/src/widgets/capture/capturewidget.cpp b/src/widgets/capture/capturewidget.cpp index 8c2107b1fd..fd19fc987a 100644 --- a/src/widgets/capture/capturewidget.cpp +++ b/src/widgets/capture/capturewidget.cpp @@ -625,10 +625,10 @@ void CaptureWidget::uncheckActiveTool() void CaptureWidget::closeEvent(QCloseEvent* event) { #if !(defined(Q_OS_MACOS) || defined(Q_OS_WIN)) - /* GNOME copy problem workaround, copy + /* Wayland copy problem workaround: the copy operation seems to work only when there is a visible window to retrieve the - data from. On GNOME, the GUI should + data from. On GNOME and COSMIC, the GUI should handle the copy operation, not the daemon. */ @@ -637,17 +637,18 @@ void CaptureWidget::closeEvent(QCloseEvent* event) if (m_captureDone && copyRequested) { DesktopInfo desktopInfo; - const bool needGnomeWorkaround = + const bool needClipboardWorkaround = desktopInfo.waylandDetected() && - desktopInfo.windowManager() == DesktopInfo::GNOME; + (desktopInfo.windowManager() == DesktopInfo::GNOME || + desktopInfo.windowManager() == DesktopInfo::COSMIC); - if (needGnomeWorkaround && !m_clipboardWorkaroundDone) { + if (needClipboardWorkaround && !m_clipboardWorkaroundDone) { event->ignore(); m_clipboardWorkaroundDone = true; m_context.request.removeTask(CaptureRequest::COPY); AbstractLogger::info() - << "GNOME Wayland detected; keeping capture window alive until " - "clipboard data is fetched."; + << "GNOME/COSMIC Wayland detected; keeping capture window " + "alive until clipboard data is fetched."; saveToClipboardGnomeWorkaround(pixmap(), this); return; } From 9250bc3579edb5a9f0124ca33633f4f651730b12 Mon Sep 17 00:00:00 2001 From: Fernie Date: Thu, 27 Aug 2026 18:47:07 -0600 Subject: [PATCH 3/3] async-notification (#4759) * async-notification The previous synchronous callWithArgumentList stalled the main thread for the QtDBus reply timeout (~25s) after every capture, freezing further captures until it returned. Probably related to Issue #2425. * Fix missing closing curly brace * clang-format --------- Co-authored-by: Mehrad Mahmoudian --- src/utils/systemnotification.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/utils/systemnotification.cpp b/src/utils/systemnotification.cpp index 04aeadb02a..361ce86726 100644 --- a/src/utils/systemnotification.cpp +++ b/src/utils/systemnotification.cpp @@ -90,8 +90,16 @@ void SystemNotification::sendMessage(const QString& text, << QStringList() // actions << hintsMap // hints << timeout; // timeout - m_interface->callWithArgumentList( - QDBus::AutoDetect, QStringLiteral("Notify"), args); + // Fire-and-forget: an asynchronous call never blocks the event loop, + // even when no notification daemon is registered on the session bus + // (e.g. bare startx / tiling WM sessions). The previous synchronous + // callWithArgumentList stalled the main thread for the QtDBus reply + // timeout (~25s) after every capture, freezing further captures until + // it returned. + if (m_interface != nullptr) { + m_interface->asyncCallWithArgumentList(QStringLiteral("Notify"), + args); + } } #endif }