Skip to content
Draft
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
2 changes: 2 additions & 0 deletions packaging/macos/Info.plist.in
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@
<true/>
<key>LSUIElement</key>
<true/>
<key>NSScreenCaptureUsageDescription</key>
<string>Flameshot needs screen recording permission to capture screenshots.</string>
</dict>
</plist>
9 changes: 7 additions & 2 deletions src/core/flameshot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,13 @@ CaptureWidget* Flameshot::gui(const CaptureRequest& req)
#ifdef Q_OS_WIN
m_captureWindow->show();
#elif defined(Q_OS_MACOS)
// In "Emulate fullscreen mode"
m_captureWindow->showFullScreen();
// Use frameless window instead of showFullScreen() to avoid
// creating a new macOS Space which hides all other windows.
// Qt::Tool avoids a Dock icon and Mission Control interference.
m_captureWindow->setWindowFlags(Qt::FramelessWindowHint |
Qt::WindowStaysOnTopHint |
Qt::Tool);
m_captureWindow->show();
m_captureWindow->activateWindow();
m_captureWindow->raise();
#else
Expand Down
106 changes: 82 additions & 24 deletions src/utils/screengrabber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
#include "utils/systemnotification.h"

#include <QApplication>
#include <QCoreApplication>
#include <QDir>
#include <QEventLoop>
#include <QFile>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QImageReader>
Expand Down Expand Up @@ -219,9 +222,15 @@ QPixmap ScreenGrabber::grabEntireDesktop(bool& ok, int preSelectedMonitor)
ok = false;
return QPixmap();
}
const QRect geom = currentScreen->geometry();
screenshot = currentScreen->grabWindow(
wid, geom.x(), geom.y(), geom.width(), geom.height());
// Use macOS native screencapture for reliable capture that includes
// all visible windows. Falls back to Qt's grabWindow() on failure.
screenshot = macosNativeScreenshot(currentScreen);
if (screenshot.isNull()) {
// Fallback to Qt method
const QRect geom = currentScreen->geometry();
screenshot = currentScreen->grabWindow(
wid, geom.x(), geom.y(), geom.width(), geom.height());
}
screenshot.setDevicePixelRatio(currentScreen->devicePixelRatio());
return screenshot;

Expand Down Expand Up @@ -266,28 +275,34 @@ QPixmap ScreenGrabber::grabFullDesktop(bool& ok)
QPixmap screenshot;

#if defined(Q_OS_MACOS)
// On macOS, composite all screens into a single pixmap.
const QList<QScreen*> screens = QGuiApplication::screens();
QRect totalGeom;
for (QScreen* s : screens) {
totalGeom = totalGeom.united(s->geometry());
}
qreal maxDpr = 1.0;
for (QScreen* s : screens) {
maxDpr = qMax(maxDpr, s->devicePixelRatio());
}
screenshot = QPixmap(qRound(totalGeom.width() * maxDpr),
qRound(totalGeom.height() * maxDpr));
screenshot.setDevicePixelRatio(maxDpr);
screenshot.fill(Qt::black);
QPainter painter(&screenshot);
for (QScreen* s : screens) {
QRect geom = s->geometry();
QPixmap p = s->grabWindow(0);
QPoint offset = geom.topLeft() - totalGeom.topLeft();
painter.drawPixmap(offset, p);
// Use macOS native screencapture for reliable full desktop capture.
// Falls back to Qt compositing method on failure.
QScreen* primaryScreen = QGuiApplication::primaryScreen();
screenshot = macosNativeScreenshot(primaryScreen);
if (screenshot.isNull()) {
// Fallback: composite all screens into a single pixmap using Qt.
const QList<QScreen*> screens = QGuiApplication::screens();
QRect totalGeom;
for (QScreen* s : screens) {
totalGeom = totalGeom.united(s->geometry());
}
qreal maxDpr = 1.0;
for (QScreen* s : screens) {
maxDpr = qMax(maxDpr, s->devicePixelRatio());
}
screenshot = QPixmap(qRound(totalGeom.width() * maxDpr),
qRound(totalGeom.height() * maxDpr));
screenshot.setDevicePixelRatio(maxDpr);
screenshot.fill(Qt::black);
QPainter painter(&screenshot);
for (QScreen* s : screens) {
QRect geom = s->geometry();
QPixmap p = s->grabWindow(0);
QPoint offset = geom.topLeft() - totalGeom.topLeft();
painter.drawPixmap(offset, p);
}
painter.end();
}
painter.end();
#elif defined(Q_OS_LINUX)
if (!m_info.waylandDetected() && ConfigHandler().useX11LegacyScreenshot()) {
qWarning() << "Using deprecated legacy X11 screenshot method. "
Expand Down Expand Up @@ -713,3 +728,46 @@ QPixmap ScreenGrabber::x11LegacyScreenshot()

return desktop;
}

#if defined(Q_OS_MACOS)
QPixmap ScreenGrabber::macosNativeScreenshot(QScreen* screen)
{
// Use macOS native screencapture command for reliable screen capture.
// This avoids issues with CGWindowListCreateImage deprecation and
// permission problems on newer macOS versions where QScreen::grabWindow()
// may only return the desktop wallpaper without any application windows.
QString tmpPath =
QDir::tempPath() + "/flameshot_capture_" +
QString::number(QCoreApplication::applicationPid()) + ".png";

QStringList args;
args << "-x" // no sound
<< "-C" // capture cursor
<< "-t" << "png" << tmpPath;

QProcess process;
process.start("screencapture", args);
if (!process.waitForFinished(5000)) {
AbstractLogger::warning()
<< tr("macOS screencapture command timed out");
return QPixmap();
}

if (process.exitCode() != 0) {
AbstractLogger::warning()
<< tr("macOS screencapture command failed with exit code %1")
.arg(process.exitCode());
return QPixmap();
}

QPixmap result(tmpPath);
QFile::remove(tmpPath);

if (result.isNull()) {
AbstractLogger::warning()
<< tr("Failed to load screenshot from macOS screencapture");
}

return result;
}
#endif
3 changes: 3 additions & 0 deletions src/utils/screengrabber.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class ScreenGrabber : public QObject
QPixmap cropToMonitor(const QPixmap& fullScreenshot, int monitorIndex);
QPixmap windowsScreenshot(int wid);
QPixmap x11LegacyScreenshot();
#if defined(Q_OS_MACOS)
QPixmap macosNativeScreenshot(QScreen* screen);
#endif

DesktopInfo m_info;
QPixmap Screenshot;
Expand Down
74 changes: 40 additions & 34 deletions src/utils/screenshotsaver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <QByteArray>
#include <QDebug>
#include <QDir>
#include <QImageWriter>
#include <QPixmap>
#include <QProcess>
Expand Down Expand Up @@ -111,44 +112,44 @@ QString ShowSaveFileDialog(const QString& title, const QString& directory)
}
}

void saveJpegToClipboardMacOS(const QPixmap& capture)
#if defined(Q_OS_MACOS)
void saveToClipboardMacOS(const QPixmap& capture)
{
// Convert QPixmap to JPEG data
QByteArray jpegData;
QBuffer buffer(&jpegData);
buffer.open(QIODevice::WriteOnly);

QImageWriter imageWriter(&buffer, "jpeg");

// Set JPEG quality to whatever is in settings
imageWriter.setQuality(ConfigHandler().jpegQuality());
if (!imageWriter.write(capture.toImage())) {
qWarning() << "Failed to write image to JPEG format.";
return;
}

// Save JPEG data to a temporary file
// Save capture as PNG to a temporary file, then use osascript to copy
// to clipboard. This is more reliable than QClipboard::setPixmap()
// on macOS, which fails when called during widget destruction or when
// the app is losing focus.
QTemporaryFile tempFile;
tempFile.setFileTemplate(QDir::tempPath() + "/flameshot_clip_XXXXXX.png");
if (!tempFile.open()) {
qWarning() << "Failed to open temporary file for writing.";
qWarning() << "Failed to open temporary file for clipboard.";
return;
}
if (!capture.save(&tempFile, "PNG")) {
qWarning() << "Failed to write PNG to temporary file.";
return;
}
tempFile.write(jpegData);
tempFile.close();

// Use osascript to copy the contents of the file to clipboard
// Use osascript to copy PNG data to the system clipboard
QProcess process;
QString script =
QString("set the clipboard to (read (POSIX file \"%1\") as «class PNGf»)")
QString(
"set the clipboard to "
"(read (POSIX file \"%1\") as «class PNGf»)")
.arg(tempFile.fileName());
process.start("osascript", QStringList() << "-e" << script);
if (!process.waitForFinished()) {
qWarning() << "Failed to execute AppleScript.";
if (!process.waitForFinished(5000)) {
qWarning() << "AppleScript clipboard copy timed out.";
} else if (process.exitCode() != 0) {
qWarning() << "AppleScript clipboard copy failed:"
<< process.readAllStandardError();
}

// Clean up
tempFile.remove();
}
#endif

void saveToClipboardMime(const QPixmap& capture, const QString& imageType)
{
Expand Down Expand Up @@ -205,24 +206,29 @@ void saveToClipboard(const QPixmap& capture)
} else {
AbstractLogger() << QObject::tr("Capture saved to clipboard.");
}
#if defined(Q_OS_MACOS)
// On macOS, always use native osascript to copy to clipboard.
// QClipboard::setPixmap() is unreliable on macOS because:
// 1. It fails when called during widget destruction (WA_DeleteOnClose)
// 2. The app may lose focus before macOS finalizes the clipboard transfer
// Using osascript ensures the system clipboard is set independently
// of Qt's window lifecycle.
saveToClipboardMacOS(capture);
#elif defined(Q_OS_LINUX) || defined(Q_OS_UNIX)
if (ConfigHandler().useJpgForClipboard()) {
#ifdef Q_OS_MACOS
saveJpegToClipboardMacOS(capture);
#else
saveToClipboardMime(capture, "jpeg");
#endif
} else if (DesktopInfo().waylandDetected()) {
saveToClipboardMime(capture, "png");
} else {
// Need to send message before copying to clipboard
#if defined(Q_OS_LINUX) || defined(Q_OS_UNIX)
if (DesktopInfo().waylandDetected()) {
saveToClipboardMime(capture, "png");
} else {
QApplication::clipboard()->setPixmap(capture);
}
QApplication::clipboard()->setPixmap(capture);
}
#else
if (ConfigHandler().useJpgForClipboard()) {
saveToClipboardMime(capture, "jpeg");
} else {
QApplication::clipboard()->setPixmap(capture);
#endif
}
#endif
}

class ClipboardWatcherMimeData : public QMimeData
Expand Down
8 changes: 6 additions & 2 deletions src/widgets/capture/capturewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,12 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req,
}
#elif defined(Q_OS_MACOS)
QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen();
move(currentScreen->geometry().x(), currentScreen->geometry().y());
resize(currentScreen->size());
QRect screenGeom = currentScreen->geometry();
move(screenGeom.topLeft());
resize(screenGeom.size());
if (windowHandle()) {
windowHandle()->setScreen(currentScreen);
}
// LINUX
#else
// Call cmake with -DFLAMESHOT_DEBUG_CAPTURE=ON to enable easier debugging
Expand Down