Skip to content

Commit 535032b

Browse files
authored
Fix copy failure on GNOME via GUI (flameshot-org#4363)
* Fix copy failure on GNOME My suspicion is that GNOME and specifically Mutter requires specific trusted context for the QtClipboard (from 'kf.guiaddons: Could not init WaylandClipboard, falling back to QtClipboard.') clipboard operation. This means that a visible window must provide the clipboard data. Since the daemon has no visible window, this is a workaround that makes the 'flameshot gui' window handle the copying with a slight delay using QTimer to ensure that GNOME has requested the clipboard data and saved it. This seems like a better solution flameshot-org#4355 as no helper windows are needed, since we already have the 'flameshot gui' window and can use it. I've limited it to GNOME only as I cannot easily test in other environments. Fixes flameshot-org#4116, flameshot-org#4298 * Keep capture window alive until GNOME fetches clipboard data * Move the GNOME clipboard workaround in screenshotsaver.cpp * Log GNOME clipboard timeout and restore success notification * Apply linter fixes
1 parent d54096e commit 535032b

File tree

4 files changed

+119
-0
lines changed

4 files changed

+119
-0
lines changed

src/utils/screenshotsaver.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
#include <QBuffer>
2626
#include <QClipboard>
2727
#include <QFileDialog>
28+
#include <QGuiApplication>
2829
#include <QMessageBox>
2930
#include <QMimeData>
31+
#include <QPointer>
3032
#include <QStandardPaths>
33+
#include <QTimer>
3134
#include <qimagewriter.h>
3235
#include <qmimedatabase.h>
3336
#if defined(Q_OS_MACOS)
@@ -217,6 +220,80 @@ void saveToClipboard(const QPixmap& capture)
217220
}
218221
}
219222

223+
class ClipboardWatcherMimeData : public QMimeData
224+
{
225+
public:
226+
ClipboardWatcherMimeData(const QImage& img, QWidget* owner)
227+
: m_image(img)
228+
, m_owner(owner)
229+
{}
230+
231+
protected:
232+
QStringList formats() const override
233+
{
234+
return { QStringLiteral("image/png"),
235+
QStringLiteral("application/x-qt-image") };
236+
}
237+
238+
QVariant retrieveData(const QString& mimeType,
239+
QMetaType type) const override
240+
{
241+
if (mimeType == QLatin1String("application/x-qt-image")) {
242+
notifyOwner();
243+
return QVariant::fromValue(m_image);
244+
}
245+
if (mimeType == QLatin1String("image/png")) {
246+
QByteArray ba;
247+
QBuffer buffer(&ba);
248+
buffer.open(QIODevice::WriteOnly);
249+
m_image.save(&buffer, "PNG");
250+
notifyOwner();
251+
return ba;
252+
}
253+
auto result = QMimeData::retrieveData(mimeType, type);
254+
if (result.isValid())
255+
notifyOwner();
256+
return result;
257+
}
258+
259+
private:
260+
void notifyOwner() const
261+
{
262+
if (m_notified || m_owner.isNull())
263+
return;
264+
m_notified = true;
265+
AbstractLogger::info() << QObject::tr("Capture saved to clipboard.");
266+
QPointer<QWidget> guard = m_owner;
267+
QTimer::singleShot(0, [guard]() {
268+
if (guard)
269+
guard->close();
270+
});
271+
}
272+
273+
QImage m_image;
274+
mutable bool m_notified{ false };
275+
QPointer<QWidget> m_owner;
276+
};
277+
278+
bool saveToClipboardGnomeWorkaround(const QPixmap& pixmap, QWidget* keepAlive)
279+
{
280+
auto* mimeData = new ClipboardWatcherMimeData(pixmap.toImage(), keepAlive);
281+
QClipboard* clipboard = QGuiApplication::clipboard();
282+
clipboard->setMimeData(mimeData);
283+
284+
keepAlive->hide();
285+
286+
// Safety net: force close after 500ms if compositor never fetches
287+
QTimer::singleShot(500, keepAlive, [keepAlive]() {
288+
qWarning() << "GNOME workaround timed out, compositor did not request "
289+
"clipboard data within 500ms. Force closing.";
290+
if (keepAlive)
291+
keepAlive->close();
292+
});
293+
294+
return true;
295+
}
296+
220297
bool saveToFilesystemGUI(const QPixmap& capture)
221298
{
222299
bool okay = false;

src/utils/screenshotsaver.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma once
55

66
#include <QString>
7+
#include <QWidget>
78

89
class QPixmap;
910

@@ -13,4 +14,6 @@ bool saveToFilesystem(const QPixmap& capture,
1314
QString ShowSaveFileDialog(const QString& title, const QString& directory);
1415
void saveToClipboardMime(const QPixmap& capture, const QString& imageType);
1516
void saveToClipboard(const QPixmap& capture);
17+
// GNOME Wayland: keeps the widget alive until clipboard data is fetched
18+
bool saveToClipboardGnomeWorkaround(const QPixmap& pixmap, QWidget* keepAlive);
1619
bool saveToFilesystemGUI(const QPixmap& capture);

src/widgets/capture/capturewidget.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req,
7575
, m_xywhDisplay(false)
7676
, m_existingObjectIsChanged(false)
7777
, m_startMove(false)
78+
, m_clipboardWorkaroundDone(false)
7879

7980
{
8081
m_undoStack.setUndoLimit(ConfigHandler().undoLimit());
@@ -604,6 +605,41 @@ void CaptureWidget::uncheckActiveTool()
604605
updateCursor();
605606
}
606607

608+
void CaptureWidget::closeEvent(QCloseEvent* event)
609+
{
610+
#if !(defined(Q_OS_MACOS) || defined(Q_OS_WIN))
611+
/* GNOME copy problem workaround, copy
612+
operation seems to work only when there
613+
is a visible window to retrieve the
614+
data from. On GNOME, the GUI should
615+
handle the copy operation, not the
616+
daemon.
617+
*/
618+
const bool copyRequested =
619+
(m_context.request.tasks() & CaptureRequest::COPY);
620+
621+
if (m_captureDone && copyRequested) {
622+
DesktopInfo desktopInfo;
623+
const bool needGnomeWorkaround =
624+
desktopInfo.waylandDetected() &&
625+
desktopInfo.windowManager() == DesktopInfo::GNOME;
626+
627+
if (needGnomeWorkaround && !m_clipboardWorkaroundDone) {
628+
event->ignore();
629+
m_clipboardWorkaroundDone = true;
630+
m_context.request.removeTask(CaptureRequest::COPY);
631+
AbstractLogger::info()
632+
<< "GNOME Wayland detected; keeping capture window alive until "
633+
"clipboard data is fetched.";
634+
saveToClipboardGnomeWorkaround(pixmap(), this);
635+
return;
636+
}
637+
}
638+
#endif
639+
640+
QWidget::closeEvent(event);
641+
}
642+
607643
void CaptureWidget::paintEvent(QPaintEvent* paintEvent)
608644
{
609645
Q_UNUSED(paintEvent)

src/widgets/capture/capturewidget.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ private slots:
110110
void resizeEvent(QResizeEvent* resizeEvent) override;
111111
void moveEvent(QMoveEvent* moveEvent) override;
112112
void changeEvent(QEvent* changeEvent) override;
113+
void closeEvent(QCloseEvent* event) override;
113114

114115
private:
115116
void pushObjectsStateToUndoStack();
@@ -229,4 +230,6 @@ private slots:
229230
// Grid
230231
bool m_displayGrid{ false };
231232
int m_gridSize{ 10 };
233+
234+
bool m_clipboardWorkaroundDone{ false };
232235
};

0 commit comments

Comments
 (0)