Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion data/dbus/org.flameshot.Flameshot.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

<!--
attachPin:
@data: Byte array containing the screenshot and geometry information.
@data: Byte array containing the screenshot, geometry information and
the screenshot's device pixel ratio.

Attach a pinned screenshot widget to the daemon.
-->
Expand Down
31 changes: 26 additions & 5 deletions src/core/flameshotdaemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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()) {
Expand Down
34 changes: 25 additions & 9 deletions src/utils/screenshotsaver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,6 @@ class ClipboardWatcherMimeData : public QMimeData
return;
m_notified = true;
AbstractLogger::info() << QObject::tr("Capture saved to clipboard.");
QPointer<QWidget> guard = m_owner;
QTimer::singleShot(0, [guard]() {
if (guard)
guard->close();
});
}

QImage m_image;
Expand All @@ -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();
});
Expand Down
12 changes: 10 additions & 2 deletions src/utils/systemnotification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
15 changes: 8 additions & 7 deletions src/widgets/capture/capturewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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;
}
Expand Down
Loading