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()) { 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/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 } 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; }