Major Controller refactor (#2295)

* Change Controller interface to mimic the CLI

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Remove obsolete handleCaptureTaken

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Extract system tray icon into separate class

The implementation is not complete and full of bugs

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Remove Controller::handleCaptureFailed

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix a QObject connection

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Controller: remove unused includes

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Make check for updates work again

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Move functionality to daemon and tray icon

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Rename SystemTray to TrayIcon

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add missing trayicon.* files

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add missing QDesktopWidget

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix syntax errors

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add missing include

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Include missing QOperatingSystemVersion on Mac

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Move update checking to daemon

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Remove obsolete method Controller::doLater

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Some cleanup

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Rename Controller to Flameshot

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Final touches

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>
This commit is contained in:
Haris Gušić
2022-03-25 15:47:21 +01:00
committed by GitHub
parent e97f733784
commit 54690d5be8
20 changed files with 945 additions and 929 deletions

View File

@@ -2,7 +2,7 @@
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
#include "generalconf.h"
#include "src/core/controller.h"
#include "src/core/flameshot.h"
#include "src/utils/confighandler.h"
#include <QCheckBox>
#include <QComboBox>
@@ -127,7 +127,6 @@ void GeneralConf::showDesktopNotificationChanged(bool checked)
void GeneralConf::checkForUpdatesChanged(bool checked)
{
ConfigHandler().setCheckForUpdates(checked);
Controller::getInstance()->setCheckForUpdatesEnabled(checked);
}
void GeneralConf::allowMultipleGuiInstancesChanged(bool checked)

View File

@@ -1,5 +1,5 @@
target_sources(flameshot PRIVATE
controller.h
flameshot.h
flameshotdaemon.h
flameshotdbusadapter.h
qguiappcurrentscreen.h
@@ -7,7 +7,7 @@ target_sources(flameshot PRIVATE
target_sources(flameshot PRIVATE
capturerequest.cpp
controller.cpp
flameshot.cpp
flameshotdaemon.cpp
flameshotdbusadapter.cpp
qguiappcurrentscreen.cpp

View File

@@ -3,7 +3,7 @@
#include "capturerequest.h"
#include "confighandler.h"
#include "controller.h"
#include "flameshot.h"
#include "imgupload/imguploadermanager.h"
#include "pinwidget.h"
#include "src/utils/screenshotsaver.h"

View File

@@ -1,738 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
#include "controller.h"
#include "flameshotdaemon.h"
#if defined(Q_OS_MACOS)
#include "external/QHotkey/QHotkey"
#endif
#include "abstractlogger.h"
#include "pinwidget.h"
#include "screenshotsaver.h"
#include "src/config/configresolver.h"
#include "src/config/configwindow.h"
#include "src/core/qguiappcurrentscreen.h"
#include "src/tools/imgupload/imguploadermanager.h"
#include "src/tools/imgupload/storages/imguploaderbase.h"
#include "src/utils/confighandler.h"
#include "src/utils/globalvalues.h"
#include "src/utils/history.h"
#include "src/utils/screengrabber.h"
#include "src/widgets/capture/capturetoolbutton.h"
#include "src/widgets/capture/capturewidget.h"
#include "src/widgets/capturelauncher.h"
#include "src/widgets/imguploaddialog.h"
#include "src/widgets/infowindow.h"
#include "src/widgets/uploadhistory.h"
#include <QAction>
#include <QApplication>
#include <QBuffer>
#include <QClipboard>
#include <QDebug>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMenu>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QOperatingSystemVersion>
#include <QSystemTrayIcon>
#include <QThread>
#include <QVersionNumber>
#ifdef Q_OS_WIN
#include "src/core/globalshortcutfilter.h"
#endif
#if defined(Q_OS_MACOS)
#include <QOperatingSystemVersion>
#include <QScreen>
#endif
// Controller is the core component of Flameshot, creates the trayIcon and
// launches the capture widget
Controller::Controller()
: m_captureWindow(nullptr)
, m_trayIcon(nullptr)
, m_trayIconMenu(nullptr)
, m_networkCheckUpdates(nullptr)
, m_showCheckAppUpdateStatus(false)
#if defined(Q_OS_MACOS)
, m_HotkeyScreenshotCapture(nullptr)
, m_HotkeyScreenshotHistory(nullptr)
#endif
{
m_appLatestVersion = QStringLiteral(APP_VERSION).replace("v", "");
QString StyleSheet = CaptureButton::globalStyleSheet();
qApp->setStyleSheet(StyleSheet);
#if defined(Q_OS_MACOS)
// Try to take a test screenshot, MacOS will request a "Screen Recording"
// permissions on the first run. Otherwise it will be hidden under the
// CaptureWidget
QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen();
currentScreen->grabWindow(QApplication::desktop()->winId(), 0, 0, 1, 1);
// set global shortcuts for MacOS
m_HotkeyScreenshotCapture = new QHotkey(
QKeySequence(ConfigHandler().shortcut("TAKE_SCREENSHOT")), true, this);
QObject::connect(m_HotkeyScreenshotCapture,
&QHotkey::activated,
qApp,
[&]() { this->startVisualCapture(); });
m_HotkeyScreenshotHistory = new QHotkey(
QKeySequence(ConfigHandler().shortcut("SCREENSHOT_HISTORY")), true, this);
QObject::connect(m_HotkeyScreenshotHistory,
&QHotkey::activated,
qApp,
[&]() { this->showRecentUploads(); });
#endif
connect(ConfigHandler::getInstance(),
&ConfigHandler::fileChanged,
this,
[this]() {
ConfigHandler config;
if (config.disabledTrayIcon()) {
disableTrayIcon();
} else {
enableTrayIcon();
}
});
if (ConfigHandler().checkForUpdates()) {
getLatestAvailableVersion();
}
}
Controller::~Controller()
{
delete m_trayIconMenu;
}
Controller* Controller::getInstance()
{
static Controller c;
return &c;
}
void Controller::setCheckForUpdatesEnabled(const bool enabled)
{
if (m_appUpdates != nullptr) {
m_appUpdates->setVisible(enabled);
m_appUpdates->setEnabled(enabled);
}
if (enabled) {
getLatestAvailableVersion();
}
}
void Controller::setOrigin(Origin origin)
{
m_origin = origin;
}
Controller::Origin Controller::origin()
{
return m_origin;
}
void Controller::getLatestAvailableVersion()
{
// This features is required for MacOS and Windows user and for Linux users
// who installed Flameshot not from the repository.
m_networkCheckUpdates = new QNetworkAccessManager(this);
QNetworkRequest requestCheckUpdates(QUrl(FLAMESHOT_APP_VERSION_URL));
connect(m_networkCheckUpdates,
&QNetworkAccessManager::finished,
this,
&Controller::handleReplyCheckUpdates);
m_networkCheckUpdates->get(requestCheckUpdates);
// check for updates each 24 hours
doLater(1000 * 60 * 60 * 24, this, [this]() {
if (ConfigHandler().checkForUpdates()) {
this->getLatestAvailableVersion();
}
});
}
/**
* @brief Prompt the user to resolve config errors if necessary.
* @return Whether errors were resolved.
*/
bool Controller::resolveAnyConfigErrors()
{
bool resolved = true;
ConfigHandler config;
if (!config.checkUnrecognizedSettings() || !config.checkSemantics()) {
auto* resolver = new ConfigResolver();
QObject::connect(
resolver, &ConfigResolver::rejected, [resolver, &resolved]() {
resolved = false;
resolver->deleteLater();
if (origin() == CLI) {
exit(1);
}
});
QObject::connect(
resolver, &ConfigResolver::accepted, [resolver, &resolved]() {
resolved = true;
resolver->close();
resolver->deleteLater();
// Ensure that the dialog is closed before starting capture
qApp->processEvents();
});
resolver->exec();
qApp->processEvents();
}
return resolved;
}
void Controller::handleReplyCheckUpdates(QNetworkReply* reply)
{
if (!ConfigHandler().checkForUpdates()) {
return;
}
if (reply->error() == QNetworkReply::NoError) {
QJsonDocument response = QJsonDocument::fromJson(reply->readAll());
QJsonObject json = response.object();
m_appLatestVersion = json["tag_name"].toString().replace("v", "");
QVersionNumber appLatestVersion =
QVersionNumber::fromString(m_appLatestVersion);
QVersionNumber currentVersion = QVersionNumber::fromString(
QStringLiteral(APP_VERSION).replace("v", ""));
if (currentVersion < appLatestVersion) {
m_appLatestUrl = json["html_url"].toString();
QString newVersion =
tr("New version %1 is available").arg(m_appLatestVersion);
if (m_appUpdates != nullptr) {
m_appUpdates->setText(newVersion);
}
if (m_showCheckAppUpdateStatus) {
sendTrayNotification(newVersion, "Flameshot");
QDesktopServices::openUrl(QUrl(m_appLatestUrl));
}
} else if (m_showCheckAppUpdateStatus) {
sendTrayNotification(tr("You have the latest version"),
"Flameshot");
}
} else {
qWarning() << "Failed to get information about the latest version. "
<< reply->errorString();
if (m_showCheckAppUpdateStatus) {
sendTrayNotification(
tr("Failed to get information about the latest version."),
"Flameshot");
}
}
m_showCheckAppUpdateStatus = false;
}
void Controller::appUpdates()
{
if (m_appLatestUrl.isEmpty()) {
m_showCheckAppUpdateStatus = true;
getLatestAvailableVersion();
} else {
QDesktopServices::openUrl(QUrl(m_appLatestUrl));
}
}
void Controller::requestCapture(const CaptureRequest& request)
{
if (!resolveAnyConfigErrors()) {
return;
}
switch (request.captureMode()) {
case CaptureRequest::FULLSCREEN_MODE:
doLater(request.delay(), this, [this, request]() {
startFullscreenCapture(request);
});
break;
case CaptureRequest::SCREEN_MODE: {
int&& number = request.data().toInt();
doLater(request.delay(), this, [this, request, number]() {
startScreenGrab(request, number);
});
break;
}
case CaptureRequest::GRAPHICAL_MODE: {
doLater(request.delay(), this, [this, request]() {
startVisualCapture(request);
});
break;
}
default:
handleCaptureFailed();
break;
}
}
// creation of a new capture in GUI mode
void Controller::startVisualCapture(const CaptureRequest& req)
{
if (!resolveAnyConfigErrors()) {
return;
}
#if defined(Q_OS_MACOS)
// This is required on MacOS because of Mission Control. If you'll switch to
// another Desktop you cannot take a new screenshot from the tray, you have
// to switch back to the Flameshot Desktop manually. It is not obvious and a
// large number of users are confused and report a bug.
if (m_captureWindow != nullptr) {
m_captureWindow->close();
delete m_captureWindow;
m_captureWindow = nullptr;
}
#endif
if (nullptr == m_captureWindow) {
// TODO is this unnecessary now?
int timeout = 5000; // 5 seconds
const int delay = 100;
QWidget* modalWidget = nullptr;
for (; timeout >= 0; timeout -= delay) {
modalWidget = qApp->activeModalWidget();
if (nullptr == modalWidget) {
break;
}
modalWidget->close();
modalWidget->deleteLater();
QThread::msleep(delay);
}
if (0 == timeout) {
QMessageBox::warning(
nullptr, tr("Error"), tr("Unable to close active modal widgets"));
return;
}
m_captureWindow = new CaptureWidget(req);
// m_captureWindow = new CaptureWidget(forcedSavePath, false); //
// debug
#ifdef Q_OS_WIN
m_captureWindow->show();
#elif defined(Q_OS_MACOS)
// In "Emulate fullscreen mode"
m_captureWindow->showFullScreen();
m_captureWindow->activateWindow();
m_captureWindow->raise();
#else
m_captureWindow->showFullScreen();
// m_captureWindow->show(); // For CaptureWidget Debugging under Linux
#endif
if (!m_appLatestUrl.isEmpty() &&
ConfigHandler().ignoreUpdateToVersion().compare(
m_appLatestVersion) < 0) {
m_captureWindow->showAppUpdateNotification(m_appLatestVersion,
m_appLatestUrl);
}
} else {
emit captureFailed();
}
}
void Controller::startScreenGrab(CaptureRequest req, const int screenNumber)
{
if (!resolveAnyConfigErrors()) {
return;
}
bool ok = true;
QScreen* screen;
if (screenNumber < 0) {
QPoint globalCursorPos = QCursor::pos();
#if QT_VERSION > QT_VERSION_CHECK(5, 10, 0)
screen = qApp->screenAt(globalCursorPos);
#else
screen =
qApp->screens()[qApp->desktop()->screenNumber(globalCursorPos)];
#endif
} else {
screen = qApp->screens()[screenNumber];
}
QPixmap p(ScreenGrabber().grabScreen(screen, ok));
if (ok) {
QRect geometry = ScreenGrabber().screenGeometry(screen);
QRect region = req.initialSelection();
if (region.isNull()) {
region = ScreenGrabber().screenGeometry(screen);
} else {
QRect screenGeom = ScreenGrabber().screenGeometry(screen);
screenGeom.moveTopLeft({ 0, 0 });
region = region.intersected(screenGeom);
p = p.copy(region);
}
if (req.tasks() & CaptureRequest::PIN) {
// change geometry for pin task
req.addPinTask(region);
}
exportCapture(p, geometry, req);
} else {
handleCaptureFailed();
}
}
// creation of the configuration window
void Controller::openConfigWindow()
{
if (!resolveAnyConfigErrors()) {
return;
}
if (m_configWindow == nullptr) {
m_configWindow = new ConfigWindow();
m_configWindow->show();
#if defined(Q_OS_MACOS)
m_configWindow->activateWindow();
m_configWindow->raise();
#endif
}
}
// creation of the window of information
void Controller::openInfoWindow()
{
if (m_infoWindow == nullptr) {
m_infoWindow = new InfoWindow();
#if defined(Q_OS_MACOS)
m_infoWindow->activateWindow();
m_infoWindow->raise();
#endif
}
}
void Controller::openLauncherWindow()
{
if (!resolveAnyConfigErrors()) {
return;
}
if (m_launcherWindow == nullptr) {
m_launcherWindow = new CaptureLauncher();
}
m_launcherWindow->show();
#if defined(Q_OS_MACOS)
m_launcherWindow->activateWindow();
m_launcherWindow->raise();
#endif
}
void Controller::initTrayIcon()
{
#if defined(Q_OS_LINUX) || defined(Q_OS_UNIX)
if (!ConfigHandler().disabledTrayIcon()) {
enableTrayIcon();
}
#elif defined(Q_OS_WIN)
enableTrayIcon();
GlobalShortcutFilter* nativeFilter = new GlobalShortcutFilter(this);
qApp->installNativeEventFilter(nativeFilter);
connect(nativeFilter, &GlobalShortcutFilter::printPressed, this, [this]() {
this->requestCapture(CaptureRequest(CaptureRequest::GRAPHICAL_MODE));
});
#endif
}
void Controller::enableTrayIcon()
{
ConfigHandler().setDisabledTrayIcon(false);
if (m_trayIcon != nullptr) {
m_trayIcon->show();
return;
}
if (nullptr == m_trayIconMenu) {
m_trayIconMenu = new QMenu();
Q_ASSERT(m_trayIconMenu);
}
auto* captureAction = new QAction(tr("&Take Screenshot"), this);
connect(captureAction, &QAction::triggered, this, [this]() {
#if defined(Q_OS_MACOS)
auto currentMacOsVersion = QOperatingSystemVersion::current();
if (currentMacOsVersion >= currentMacOsVersion.MacOSBigSur) {
startVisualCapture();
} else {
// It seems it is not relevant for MacOS BigSur (Wait 400 ms to hide
// the QMenu)
doLater(400, this, [this]() { this->startVisualCapture(); });
}
#else
// Wait 400 ms to hide the QMenu
doLater(400, this, [this]() { startVisualCapture(); });
#endif
});
auto* launcherAction = new QAction(tr("&Open Launcher"), this);
connect(launcherAction,
&QAction::triggered,
this,
&Controller::openLauncherWindow);
auto* configAction = new QAction(tr("&Configuration"), this);
connect(
configAction, &QAction::triggered, this, &Controller::openConfigWindow);
auto* infoAction = new QAction(tr("&About"), this);
connect(infoAction, &QAction::triggered, this, &Controller::openInfoWindow);
m_appUpdates = new QAction(tr("Check for updates"), this);
connect(m_appUpdates, &QAction::triggered, this, &Controller::appUpdates);
auto* quitAction = new QAction(tr("&Quit"), this);
connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit);
// recent screenshots
auto* recentAction = new QAction(tr("&Latest Uploads"), this);
connect(recentAction, SIGNAL(triggered()), this, SLOT(showRecentUploads()));
// generate menu
m_trayIconMenu->addAction(captureAction);
m_trayIconMenu->addAction(launcherAction);
m_trayIconMenu->addSeparator();
m_trayIconMenu->addAction(recentAction);
m_trayIconMenu->addSeparator();
m_trayIconMenu->addAction(configAction);
m_trayIconMenu->addSeparator();
m_trayIconMenu->addAction(m_appUpdates);
m_trayIconMenu->addAction(infoAction);
m_trayIconMenu->addSeparator();
m_trayIconMenu->addAction(quitAction);
setCheckForUpdatesEnabled(ConfigHandler().checkForUpdates());
if (nullptr == m_trayIcon) {
m_trayIcon = new QSystemTrayIcon();
Q_ASSERT(m_trayIcon);
}
m_trayIcon->setToolTip(QStringLiteral("Flameshot"));
#if defined(Q_OS_MACOS)
// Because of the following issues on MacOS "Catalina":
// https://bugreports.qt.io/browse/QTBUG-86393
// https://developer.apple.com/forums/thread/126072
auto currentMacOsVersion = QOperatingSystemVersion::current();
if (currentMacOsVersion >= currentMacOsVersion.MacOSBigSur) {
m_trayIcon->setContextMenu(m_trayIconMenu);
}
#else
m_trayIcon->setContextMenu(m_trayIconMenu);
#endif
QIcon trayIcon =
QIcon::fromTheme("flameshot-tray", QIcon(GlobalValues::iconPathPNG()));
m_trayIcon->setIcon(trayIcon);
#if defined(Q_OS_MACOS)
if (currentMacOsVersion < currentMacOsVersion.MacOSBigSur) {
// Because of the following issues on MacOS "Catalina":
// https://bugreports.qt.io/browse/QTBUG-86393
// https://developer.apple.com/forums/thread/126072
auto trayIconActivated = [this](QSystemTrayIcon::ActivationReason r) {
if (m_trayIconMenu->isVisible()) {
m_trayIconMenu->hide();
} else {
m_trayIconMenu->popup(QCursor::pos());
}
};
connect(
m_trayIcon, &QSystemTrayIcon::activated, this, trayIconActivated);
}
#else
auto trayIconActivated = [this](QSystemTrayIcon::ActivationReason r) {
if (r == QSystemTrayIcon::Trigger) {
startVisualCapture();
}
};
connect(m_trayIcon, &QSystemTrayIcon::activated, this, trayIconActivated);
#endif
#ifdef Q_OS_WIN
// Ensure proper removal of tray icon when program quits on Windows.
connect(
qApp, &QCoreApplication::aboutToQuit, m_trayIcon, &QSystemTrayIcon::hide);
#endif
m_trayIcon->show();
if (ConfigHandler().showStartupLaunchMessage()) {
m_trayIcon->showMessage(
"Flameshot",
QObject::tr(
"Hello, I'm here! Click icon in the tray to take a screenshot or "
"click with a right button to see more options."),
trayIcon,
3000);
}
}
void Controller::disableTrayIcon()
{
if (m_trayIcon != nullptr) {
m_trayIcon->hide();
}
ConfigHandler().setDisabledTrayIcon(true);
}
void Controller::sendTrayNotification(const QString& text,
const QString& title,
const int timeout)
{
if (m_trayIcon != nullptr) {
m_trayIcon->showMessage(
title, text, QIcon(GlobalValues::iconPath()), timeout);
}
}
void Controller::showRecentUploads()
{
static UploadHistory* historyWidget = nullptr;
if (historyWidget == nullptr) {
historyWidget = new UploadHistory;
historyWidget->loadHistory();
connect(historyWidget, &QObject::destroyed, this, []() {
historyWidget = nullptr;
});
}
historyWidget->show();
#if defined(Q_OS_MACOS)
historyWidget->activateWindow();
historyWidget->raise();
#endif
}
void Controller::exportCapture(QPixmap capture,
QRect& selection,
const CaptureRequest& req)
{
using CR = CaptureRequest;
int tasks = req.tasks(), mode = req.captureMode();
QString path = req.path();
if (tasks & CR::PRINT_GEOMETRY) {
QByteArray byteArray;
QBuffer buffer(&byteArray);
QTextStream(stdout)
<< selection.width() << "x" << selection.height() << "+"
<< selection.x() << "+" << selection.y() << "\n";
}
if (tasks & CR::PRINT_RAW) {
QByteArray byteArray;
QBuffer buffer(&byteArray);
capture.save(&buffer, "PNG");
QFile file;
file.open(stdout, QIODevice::WriteOnly);
file.write(byteArray);
file.close();
}
if (tasks & CR::SAVE) {
if (req.path().isEmpty()) {
saveToFilesystemGUI(capture);
} else {
saveToFilesystem(capture, path);
}
}
if (tasks & CR::COPY) {
FlameshotDaemon::copyToClipboard(capture);
}
if (tasks & CR::PIN) {
FlameshotDaemon::createPin(capture, selection);
if (mode == CR::SCREEN_MODE || mode == CR::FULLSCREEN_MODE) {
AbstractLogger::info()
<< QObject::tr("Full screen screenshot pinned to screen");
}
}
if (tasks & CR::UPLOAD) {
if (!ConfigHandler().uploadWithoutConfirmation()) {
auto* dialog = new ImgUploadDialog();
if (dialog->exec() == QDialog::Rejected) {
return;
}
}
ImgUploaderBase* widget = ImgUploaderManager().uploader(capture);
widget->show();
widget->activateWindow();
// NOTE: lambda can't capture 'this' because it might be destroyed later
CR::ExportTask tasks = tasks;
QObject::connect(
widget, &ImgUploaderBase::uploadOk, [=](const QUrl& url) {
if (ConfigHandler().copyAndCloseAfterUpload()) {
if (!(tasks & CR::COPY)) {
FlameshotDaemon::copyToClipboard(
url.toString(), tr("URL copied to clipboard."));
widget->close();
} else {
widget->showPostUploadDialog();
}
} else {
widget->showPostUploadDialog();
}
});
}
if (!(tasks & CR::UPLOAD)) {
emit captureTaken(capture, selection);
}
}
void Controller::startFullscreenCapture(const CaptureRequest& req)
{
if (!resolveAnyConfigErrors()) {
return;
}
bool ok = true;
QPixmap p(ScreenGrabber().grabEntireDesktop(ok));
QRect region = req.initialSelection();
if (!region.isNull()) {
p = p.copy(region);
}
if (ok) {
QRect selection; // `flameshot full` does not support --selection
exportCapture(p, selection, req);
} else {
handleCaptureFailed();
}
}
void Controller::handleCaptureTaken(const CaptureRequest& req,
QPixmap p,
QRect selection)
{
exportCapture(p, selection, req);
}
void Controller::handleCaptureFailed()
{
emit captureFailed();
}
void Controller::doLater(int msec, QObject* receiver, lambda func)
{
auto* timer = new QTimer(receiver);
QObject::connect(timer, &QTimer::timeout, receiver, [timer, func]() {
func();
timer->deleteLater();
});
timer->setInterval(msec);
timer->start();
}
// STATIC ATTRIBUTES
Controller::Origin Controller::m_origin = Controller::DAEMON;

View File

@@ -1,116 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
#pragma once
#include "src/core/capturerequest.h"
#include <QMap>
#include <QMenu>
#include <QObject>
#include <QPixmap>
#include <QPointer>
#include <QTimer>
#include <functional>
class CaptureWidget;
class ConfigWindow;
class InfoWindow;
class QSystemTrayIcon;
class CaptureLauncher;
class UploadHistory;
class QNetworkAccessManager;
class QNetworkReply;
#if (defined(Q_OS_MAC) || defined(Q_OS_MAC64) || defined(Q_OS_MACOS) || \
defined(Q_OS_MACX))
class QHotkey;
#endif
using lambda = std::function<void(void)>;
class Controller : public QObject
{
Q_OBJECT
public:
enum Origin
{
CLI,
DAEMON
};
static Controller* getInstance();
void setCheckForUpdatesEnabled(const bool enabled);
static void setOrigin(Origin origin);
static Origin origin();
signals:
// TODO remove all parameters from captureTaken and update dependencies
void captureTaken(QPixmap p, const QRect& selection);
void captureFailed();
public slots:
void requestCapture(const CaptureRequest& request);
void openConfigWindow();
void openInfoWindow();
void appUpdates();
void openLauncherWindow();
// TODO move tray icon handling to FlameshotDaemon
void initTrayIcon();
void enableTrayIcon();
void disableTrayIcon();
void showRecentUploads();
void exportCapture(QPixmap p, QRect& selection, const CaptureRequest& req);
void sendTrayNotification(
const QString& text,
const QString& title = QStringLiteral("Flameshot Info"),
const int timeout = 5000);
private slots:
void startFullscreenCapture(const CaptureRequest& req);
void startVisualCapture(
const CaptureRequest& req = CaptureRequest::GRAPHICAL_MODE);
void startScreenGrab(CaptureRequest req, const int screenNumber = -1);
public slots: // TODO move these up
void handleCaptureTaken(const CaptureRequest& req,
QPixmap p,
QRect selection);
void handleCaptureFailed();
void handleReplyCheckUpdates(QNetworkReply* reply);
private:
Controller();
~Controller();
void getLatestAvailableVersion();
bool resolveAnyConfigErrors();
// replace QTimer::singleShot introduced in Qt 5.4
// the actual target Qt version is 5.3
void doLater(int msec, QObject* receiver, lambda func);
// class members
QAction* m_appUpdates;
QString m_appLatestUrl;
QString m_appLatestVersion;
bool m_showCheckAppUpdateStatus;
static Origin m_origin;
QPointer<CaptureWidget> m_captureWindow;
QPointer<InfoWindow> m_infoWindow;
QPointer<CaptureLauncher> m_launcherWindow;
QPointer<ConfigWindow> m_configWindow;
QPointer<QSystemTrayIcon> m_trayIcon;
QMenu* m_trayIconMenu;
QNetworkAccessManager* m_networkCheckUpdates;
#if (defined(Q_OS_MAC) || defined(Q_OS_MAC64) || defined(Q_OS_MACOS) || \
defined(Q_OS_MACX))
QHotkey* m_HotkeyScreenshotCapture;
QHotkey* m_HotkeyScreenshotHistory;
#endif
};

416
src/core/flameshot.cpp Normal file
View File

@@ -0,0 +1,416 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
#include "flameshot.h"
#include "flameshotdaemon.h"
#if defined(Q_OS_MACOS)
#include "external/QHotkey/QHotkey"
#endif
#include "abstractlogger.h"
#include "screenshotsaver.h"
#include "src/config/configresolver.h"
#include "src/config/configwindow.h"
#include "src/core/qguiappcurrentscreen.h"
#include "src/tools/imgupload/imguploadermanager.h"
#include "src/tools/imgupload/storages/imguploaderbase.h"
#include "src/utils/confighandler.h"
#include "src/utils/screengrabber.h"
#include "src/widgets/capture/capturewidget.h"
#include "src/widgets/capturelauncher.h"
#include "src/widgets/imguploaddialog.h"
#include "src/widgets/infowindow.h"
#include "src/widgets/uploadhistory.h"
#include <QApplication>
#include <QBuffer>
#include <QDebug>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QFile>
#include <QMessageBox>
#include <QThread>
#include <QTimer>
#include <QVersionNumber>
#if defined(Q_OS_MACOS)
#include <QScreen>
#endif
Flameshot::Flameshot()
: m_captureWindow(nullptr)
#if defined(Q_OS_MACOS)
, m_HotkeyScreenshotCapture(nullptr)
, m_HotkeyScreenshotHistory(nullptr)
#endif
{
QString StyleSheet = CaptureButton::globalStyleSheet();
qApp->setStyleSheet(StyleSheet);
#if defined(Q_OS_MACOS)
// Try to take a test screenshot, MacOS will request a "Screen Recording"
// permissions on the first run. Otherwise it will be hidden under the
// CaptureWidget
QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen();
currentScreen->grabWindow(QApplication::desktop()->winId(), 0, 0, 1, 1);
// set global shortcuts for MacOS
m_HotkeyScreenshotCapture = new QHotkey(
QKeySequence(ConfigHandler().shortcut("TAKE_SCREENSHOT")), true, this);
QObject::connect(m_HotkeyScreenshotCapture,
&QHotkey::activated,
qApp,
[this]() { gui(); });
m_HotkeyScreenshotHistory = new QHotkey(
QKeySequence(ConfigHandler().shortcut("SCREENSHOT_HISTORY")), true, this);
QObject::connect(m_HotkeyScreenshotHistory,
&QHotkey::activated,
qApp,
[this]() { history(); });
#endif
}
Flameshot* Flameshot::instance()
{
static Flameshot c;
return &c;
}
CaptureWidget* Flameshot::gui(const CaptureRequest& req)
{
if (!resolveAnyConfigErrors()) {
return nullptr;
}
#if defined(Q_OS_MACOS)
// This is required on MacOS because of Mission Control. If you'll switch to
// another Desktop you cannot take a new screenshot from the tray, you have
// to switch back to the Flameshot Desktop manually. It is not obvious and a
// large number of users are confused and report a bug.
if (m_captureWindow != nullptr) {
m_captureWindow->close();
delete m_captureWindow;
m_captureWindow = nullptr;
}
#endif
if (nullptr == m_captureWindow) {
// TODO is this unnecessary now?
int timeout = 5000; // 5 seconds
const int delay = 100;
QWidget* modalWidget = nullptr;
for (; timeout >= 0; timeout -= delay) {
modalWidget = qApp->activeModalWidget();
if (nullptr == modalWidget) {
break;
}
modalWidget->close();
modalWidget->deleteLater();
QThread::msleep(delay);
}
if (0 == timeout) {
QMessageBox::warning(
nullptr, tr("Error"), tr("Unable to close active modal widgets"));
return nullptr;
}
m_captureWindow = new CaptureWidget(req);
// m_captureWindow = new CaptureWidget(req, false); //
// debug
#ifdef Q_OS_WIN
m_captureWindow->show();
#elif defined(Q_OS_MACOS)
// In "Emulate fullscreen mode"
m_captureWindow->showFullScreen();
m_captureWindow->activateWindow();
m_captureWindow->raise();
#else
m_captureWindow->showFullScreen();
// m_captureWindow->show(); // For CaptureWidget Debugging under Linux
#endif
return m_captureWindow;
} else {
emit captureFailed();
}
}
void Flameshot::screen(CaptureRequest req, const int screenNumber)
{
if (!resolveAnyConfigErrors())
return;
bool ok = true;
QScreen* screen;
if (screenNumber < 0) {
QPoint globalCursorPos = QCursor::pos();
#if QT_VERSION > QT_VERSION_CHECK(5, 10, 0)
screen = qApp->screenAt(globalCursorPos);
#else
screen =
qApp->screens()[qApp->desktop()->screenNumber(globalCursorPos)];
#endif
} else {
screen = qApp->screens()[screenNumber];
}
QPixmap p(ScreenGrabber().grabScreen(screen, ok));
if (ok) {
QRect geometry = ScreenGrabber().screenGeometry(screen);
QRect region = req.initialSelection();
if (region.isNull()) {
region = ScreenGrabber().screenGeometry(screen);
} else {
QRect screenGeom = ScreenGrabber().screenGeometry(screen);
screenGeom.moveTopLeft({ 0, 0 });
region = region.intersected(screenGeom);
p = p.copy(region);
}
if (req.tasks() & CaptureRequest::PIN) {
// change geometry for pin task
req.addPinTask(region);
}
exportCapture(p, geometry, req);
} else {
emit captureFailed();
}
}
void Flameshot::full(const CaptureRequest& req)
{
if (!resolveAnyConfigErrors())
return;
bool ok = true;
QPixmap p(ScreenGrabber().grabEntireDesktop(ok));
QRect region = req.initialSelection();
if (!region.isNull()) {
p = p.copy(region);
}
if (ok) {
QRect selection; // `flameshot full` does not support --selection
exportCapture(p, selection, req);
} else {
emit captureFailed();
}
}
void Flameshot::launcher()
{
if (!resolveAnyConfigErrors())
return;
if (!m_launcherWindow) {
m_launcherWindow = new CaptureLauncher();
}
m_launcherWindow->show();
#if defined(Q_OS_MACOS)
m_launcherWindow->activateWindow();
m_launcherWindow->raise();
#endif
}
void Flameshot::config()
{
if (!resolveAnyConfigErrors())
return;
if (!m_configWindow) {
m_configWindow = new ConfigWindow();
m_configWindow->show();
#if defined(Q_OS_MACOS)
m_configWindow->activateWindow();
m_configWindow->raise();
#endif
}
}
void Flameshot::info()
{
if (!m_infoWindow) {
m_infoWindow = new InfoWindow();
#if defined(Q_OS_MACOS)
m_infoWindow->activateWindow();
m_infoWindow->raise();
#endif
}
}
void Flameshot::history()
{
static UploadHistory* historyWidget = nullptr;
if (historyWidget == nullptr) {
historyWidget = new UploadHistory;
historyWidget->loadHistory();
connect(historyWidget, &QObject::destroyed, this, []() {
historyWidget = nullptr;
});
}
historyWidget->show();
#if defined(Q_OS_MACOS)
historyWidget->activateWindow();
historyWidget->raise();
#endif
}
QVersionNumber Flameshot::getVersion()
{
return QVersionNumber::fromString(
QStringLiteral(APP_VERSION).replace("v", ""));
}
void Flameshot::setOrigin(Origin origin)
{
m_origin = origin;
}
Flameshot::Origin Flameshot::origin()
{
return m_origin;
}
/**
* @brief Prompt the user to resolve config errors if necessary.
* @return Whether errors were resolved.
*/
bool Flameshot::resolveAnyConfigErrors()
{
bool resolved = true;
ConfigHandler config;
if (!config.checkUnrecognizedSettings() || !config.checkSemantics()) {
auto* resolver = new ConfigResolver();
QObject::connect(
resolver, &ConfigResolver::rejected, [resolver, &resolved]() {
resolved = false;
resolver->deleteLater();
if (origin() == CLI) {
exit(1);
}
});
QObject::connect(
resolver, &ConfigResolver::accepted, [resolver, &resolved]() {
resolved = true;
resolver->close();
resolver->deleteLater();
// Ensure that the dialog is closed before starting capture
qApp->processEvents();
});
resolver->exec();
qApp->processEvents();
}
return resolved;
}
void Flameshot::requestCapture(const CaptureRequest& request)
{
if (!resolveAnyConfigErrors()) {
return;
}
switch (request.captureMode()) {
case CaptureRequest::FULLSCREEN_MODE:
QTimer::singleShot(request.delay(),
[this, request] { full(request); });
break;
case CaptureRequest::SCREEN_MODE: {
int&& number = request.data().toInt();
QTimer::singleShot(request.delay(), [this, request, number]() {
screen(request, number);
});
break;
}
case CaptureRequest::GRAPHICAL_MODE: {
QTimer::singleShot(
request.delay(), this, [this, request]() { gui(request); });
break;
}
default:
emit captureFailed();
break;
}
}
void Flameshot::exportCapture(QPixmap capture,
QRect& selection,
const CaptureRequest& req)
{
using CR = CaptureRequest;
int tasks = req.tasks(), mode = req.captureMode();
QString path = req.path();
if (tasks & CR::PRINT_GEOMETRY) {
QByteArray byteArray;
QBuffer buffer(&byteArray);
QTextStream(stdout)
<< selection.width() << "x" << selection.height() << "+"
<< selection.x() << "+" << selection.y() << "\n";
}
if (tasks & CR::PRINT_RAW) {
QByteArray byteArray;
QBuffer buffer(&byteArray);
capture.save(&buffer, "PNG");
QFile file;
file.open(stdout, QIODevice::WriteOnly);
file.write(byteArray);
file.close();
}
if (tasks & CR::SAVE) {
if (req.path().isEmpty()) {
saveToFilesystemGUI(capture);
} else {
saveToFilesystem(capture, path);
}
}
if (tasks & CR::COPY) {
FlameshotDaemon::copyToClipboard(capture);
}
if (tasks & CR::PIN) {
FlameshotDaemon::createPin(capture, selection);
if (mode == CR::SCREEN_MODE || mode == CR::FULLSCREEN_MODE) {
AbstractLogger::info()
<< QObject::tr("Full screen screenshot pinned to screen");
}
}
if (tasks & CR::UPLOAD) {
if (!ConfigHandler().uploadWithoutConfirmation()) {
auto* dialog = new ImgUploadDialog();
if (dialog->exec() == QDialog::Rejected) {
return;
}
}
ImgUploaderBase* widget = ImgUploaderManager().uploader(capture);
widget->show();
widget->activateWindow();
// NOTE: lambda can't capture 'this' because it might be destroyed later
CR::ExportTask tasks = tasks;
QObject::connect(
widget, &ImgUploaderBase::uploadOk, [=](const QUrl& url) {
if (ConfigHandler().copyAndCloseAfterUpload()) {
if (!(tasks & CR::COPY)) {
FlameshotDaemon::copyToClipboard(
url.toString(), tr("URL copied to clipboard."));
widget->close();
} else {
widget->showPostUploadDialog();
}
} else {
widget->showPostUploadDialog();
}
});
}
if (!(tasks & CR::UPLOAD)) {
emit captureTaken(capture);
}
}
// STATIC ATTRIBUTES
Flameshot::Origin Flameshot::m_origin = Flameshot::DAEMON;

76
src/core/flameshot.h Normal file
View File

@@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
#pragma once
#include "src/core/capturerequest.h"
#include <QObject>
#include <QPointer>
#include <QVersionNumber>
class CaptureWidget;
class ConfigWindow;
class InfoWindow;
class CaptureLauncher;
class UploadHistory;
#if (defined(Q_OS_MAC) || defined(Q_OS_MAC64) || defined(Q_OS_MACOS) || \
defined(Q_OS_MACX))
class QHotkey;
#endif
class Flameshot : public QObject
{
Q_OBJECT
public:
enum Origin
{
CLI,
DAEMON
};
static Flameshot* instance();
public slots:
CaptureWidget* gui(
const CaptureRequest& req = CaptureRequest::GRAPHICAL_MODE);
void screen(CaptureRequest req, const int screenNumber = -1);
void full(const CaptureRequest& req);
void launcher();
void config();
void info();
void history();
QVersionNumber getVersion();
public:
static void setOrigin(Origin origin);
static Origin origin();
signals:
void captureTaken(QPixmap p);
void captureFailed();
public slots:
void requestCapture(const CaptureRequest& request);
void exportCapture(QPixmap p, QRect& selection, const CaptureRequest& req);
private:
Flameshot();
bool resolveAnyConfigErrors();
// class members
static Origin m_origin;
QPointer<CaptureWidget> m_captureWindow;
QPointer<InfoWindow> m_infoWindow;
QPointer<CaptureLauncher> m_launcherWindow;
QPointer<ConfigWindow> m_configWindow;
#if (defined(Q_OS_MAC) || defined(Q_OS_MAC64) || defined(Q_OS_MACOS) || \
defined(Q_OS_MACX))
QHotkey* m_HotkeyScreenshotCapture;
QHotkey* m_HotkeyScreenshotHistory;
#endif
};

View File

@@ -2,15 +2,29 @@
#include "abstractlogger.h"
#include "confighandler.h"
#include "controller.h"
#include "flameshot.h"
#include "pinwidget.h"
#include "screenshotsaver.h"
#include "src/utils/globalvalues.h"
#include "src/widgets/capture/capturewidget.h"
#include "src/widgets/trayicon.h"
#include <QApplication>
#include <QClipboard>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDesktopServices>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QPixmap>
#include <QRect>
#include <QTimer>
#include <QUrl>
#ifdef Q_OS_WIN
#include "src/core/globalshortcutfilter.h"
#endif
/**
* @brief A way of accessing the flameshot daemon both from the daemon itself,
@@ -26,21 +40,26 @@
* If the `autoCloseIdleDaemon` option is true, the daemon will close as soon as
* it is not needed to host pinned screenshots and the clipboard. On Windows,
* this option is disabled and the daemon always persists, because the system
* tray is currently the only way to interact with flameshot.
* tray is currently the only way to interact with flameshot there.
*
* Both the daemon and non-daemon flameshot processes use the same public API,
* which is implemented as static methods. In the daemon process, this class is
* also instantiated as a singleton, so it can listen to D-Bus calls via the
* sigslot mechanism. The instantiation is done by calling `start` (this must be
* done only in the daemon process).
* done only in the daemon process). Any instance (as opposed to static) members
* can only be used if the current process is a daemon.
*
* @note The daemon will be automatically launched where necessary, via D-Bus.
* This applies only on Linux.
* This applies only to Linux.
*/
FlameshotDaemon::FlameshotDaemon()
: m_persist(false)
, m_hostingClipboard(false)
, m_clipboardSignalBlocked(false)
, m_trayIcon(nullptr)
, m_networkCheckUpdates(nullptr)
, m_showCheckAppUpdateStatus(false)
, m_appLatestVersion(QStringLiteral(APP_VERSION).replace("v", ""))
{
connect(
QApplication::clipboard(), &QClipboard::dataChanged, this, [this]() {
@@ -51,8 +70,6 @@ FlameshotDaemon::FlameshotDaemon()
m_hostingClipboard = false;
quitIfIdle();
});
// init tray icon
Controller::getInstance()->initTrayIcon();
#ifdef Q_OS_WIN
m_persist = true;
#else
@@ -60,14 +77,27 @@ FlameshotDaemon::FlameshotDaemon()
connect(ConfigHandler::getInstance(),
&ConfigHandler::fileChanged,
this,
[this]() { m_persist = !ConfigHandler().autoCloseIdleDaemon(); });
[this]() {
ConfigHandler config;
if (config.disabledTrayIcon()) {
enableTrayIcon(false);
} else {
enableTrayIcon(true);
}
m_persist = !config.autoCloseIdleDaemon();
});
#endif
if (ConfigHandler().checkForUpdates()) {
getLatestAvailableVersion();
}
}
void FlameshotDaemon::start()
{
if (!m_instance) {
m_instance = new FlameshotDaemon();
// Tray icon needs FlameshotDaemon::instance() to be non-null
m_instance->initTrayIcon();
qApp->setQuitOnLastWindowClosed(false);
}
}
@@ -121,20 +151,6 @@ void FlameshotDaemon::copyToClipboard(QString text, QString notification)
sessionBus.call(m);
}
void FlameshotDaemon::enableTrayIcon(bool enable)
{
#if !defined(Q_OS_WIN)
if (!instance()) {
return;
}
if (enable) {
Controller::getInstance()->enableTrayIcon();
} else {
Controller::getInstance()->disableTrayIcon();
}
#endif
}
/**
* @brief Is this instance of flameshot hosting any windows as a daemon?
*/
@@ -143,6 +159,55 @@ bool FlameshotDaemon::isThisInstanceHostingWidgets()
return instance() && !instance()->m_widgets.isEmpty();
}
void FlameshotDaemon::sendTrayNotification(const QString& text,
const QString& title,
const int timeout)
{
if (m_trayIcon) {
m_trayIcon->showMessage(
title, text, QIcon(GlobalValues::iconPath()), timeout);
}
}
void FlameshotDaemon::showUpdateNotificationIfAvailable(CaptureWidget* widget)
{
if (!m_appLatestUrl.isEmpty() &&
ConfigHandler().ignoreUpdateToVersion().compare(m_appLatestVersion) <
0) {
widget->showAppUpdateNotification(m_appLatestVersion, m_appLatestUrl);
}
}
void FlameshotDaemon::getLatestAvailableVersion()
{
// This features is required for MacOS and Windows user and for Linux users
// who installed Flameshot not from the repository.
m_networkCheckUpdates = new QNetworkAccessManager(this);
QNetworkRequest requestCheckUpdates(QUrl(FLAMESHOT_APP_VERSION_URL));
connect(m_networkCheckUpdates,
&QNetworkAccessManager::finished,
this,
&FlameshotDaemon::handleReplyCheckUpdates);
m_networkCheckUpdates->get(requestCheckUpdates);
// check for updates each 24 hours
QTimer::singleShot(1000 * 60 * 60 * 24, [this]() {
if (ConfigHandler().checkForUpdates()) {
this->getLatestAvailableVersion();
}
});
}
void FlameshotDaemon::checkForUpdates()
{
if (m_appLatestUrl.isEmpty()) {
m_showCheckAppUpdateStatus = true;
getLatestAvailableVersion();
} else {
QDesktopServices::openUrl(QUrl(m_appLatestUrl));
}
}
/**
* @brief Return the daemon instance.
*
@@ -248,6 +313,76 @@ void FlameshotDaemon::attachTextToClipboard(QString text, QString notification)
clipboard->blockSignals(false);
}
void FlameshotDaemon::initTrayIcon()
{
#if defined(Q_OS_LINUX) || defined(Q_OS_UNIX)
if (!ConfigHandler().disabledTrayIcon()) {
enableTrayIcon(true);
}
#elif defined(Q_OS_WIN)
enableTrayIcon(true);
GlobalShortcutFilter* nativeFilter = new GlobalShortcutFilter(this);
qApp->installNativeEventFilter(nativeFilter);
connect(nativeFilter, &GlobalShortcutFilter::printPressed, this, [this]() {
Flameshot::instance()->gui();
});
#endif
}
void FlameshotDaemon::enableTrayIcon(bool enable)
{
if (enable) {
if (m_trayIcon == nullptr) {
m_trayIcon = new TrayIcon();
} else {
m_trayIcon->show();
return;
}
} else if (m_trayIcon) {
m_trayIcon->hide();
}
}
void FlameshotDaemon::handleReplyCheckUpdates(QNetworkReply* reply)
{
if (!ConfigHandler().checkForUpdates()) {
return;
}
if (reply->error() == QNetworkReply::NoError) {
QJsonDocument response = QJsonDocument::fromJson(reply->readAll());
QJsonObject json = response.object();
m_appLatestVersion = json["tag_name"].toString().replace("v", "");
QVersionNumber appLatestVersion =
QVersionNumber::fromString(m_appLatestVersion);
if (Flameshot::instance()->getVersion() < appLatestVersion) {
emit newVersionAvailable(appLatestVersion);
m_appLatestUrl = json["html_url"].toString();
QString newVersion =
tr("New version %1 is available").arg(m_appLatestVersion);
if (m_showCheckAppUpdateStatus) {
sendTrayNotification(newVersion, "Flameshot");
QDesktopServices::openUrl(QUrl(m_appLatestUrl));
}
} else if (m_showCheckAppUpdateStatus) {
sendTrayNotification(tr("You have the latest version"),
"Flameshot");
}
} else {
qWarning() << "Failed to get information about the latest version. "
<< reply->errorString();
if (m_showCheckAppUpdateStatus) {
if (FlameshotDaemon::instance()) {
FlameshotDaemon::instance()->sendTrayNotification(
tr("Failed to get information about the latest version."),
"Flameshot");
}
}
}
m_showCheckAppUpdateStatus = false;
}
QDBusMessage FlameshotDaemon::createMethodCall(QString method)
{
QDBusMessage m =

View File

@@ -8,8 +8,13 @@ class QPixmap;
class QRect;
class QDBusMessage;
class QDBusConnection;
class TrayIcon;
class QNetworkAccessManager;
class QNetworkReply;
class QVersionNumber;
class CaptureWidget;
class FlameshotDaemon : private QObject
class FlameshotDaemon : public QObject
{
Q_OBJECT
public:
@@ -18,9 +23,22 @@ public:
static void createPin(QPixmap capture, QRect geometry);
static void copyToClipboard(QPixmap capture);
static void copyToClipboard(QString text, QString notification = "");
static void enableTrayIcon(bool enable);
static bool isThisInstanceHostingWidgets();
void sendTrayNotification(
const QString& text,
const QString& title = QStringLiteral("Flameshot Info"),
const int timeout = 5000);
void showUpdateNotificationIfAvailable(CaptureWidget* widget);
public slots:
void checkForUpdates();
void getLatestAvailableVersion();
signals:
void newVersionAvailable(QVersionNumber version);
private:
FlameshotDaemon();
void quitIfIdle();
@@ -31,6 +49,12 @@ private:
void attachScreenshotToClipboard(const QByteArray& screenshot);
void attachTextToClipboard(QString text, QString notification);
void initTrayIcon();
void enableTrayIcon(bool enable);
private slots:
void handleReplyCheckUpdates(QNetworkReply* reply);
private:
static QDBusMessage createMethodCall(QString method);
static void checkDBusConnection(const QDBusConnection& connection);
@@ -40,6 +64,13 @@ private:
bool m_hostingClipboard;
bool m_clipboardSignalBlocked;
QList<QWidget*> m_widgets;
TrayIcon* m_trayIcon;
QString m_appLatestUrl;
QString m_appLatestVersion;
bool m_showCheckAppUpdateStatus;
QNetworkAccessManager* m_networkCheckUpdates;
static FlameshotDaemon* m_instance;
friend class FlameshotDBusAdapter;

View File

@@ -2,7 +2,7 @@
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
#include "globalshortcutfilter.h"
#include "src/core/controller.h"
#include "src/core/flameshot.h"
#include <qt_windows.h>
GlobalShortcutFilter::GlobalShortcutFilter(QObject* parent)
@@ -34,12 +34,12 @@ bool GlobalShortcutFilter::nativeEventFilter(const QByteArray& eventType,
// Show screenshots history
if (VK_SNAPSHOT == keycode && MOD_SHIFT == modifiers) {
Controller::getInstance()->showRecentUploads();
Flameshot::instance()->history();
}
// Capture screen
if (VK_SNAPSHOT == keycode && 0 == modifiers) {
Controller::getInstance()->requestCapture(
Flameshot::instance()->requestCapture(
CaptureRequest(CaptureRequest::GRAPHICAL_MODE));
}

View File

@@ -11,7 +11,7 @@
#include "src/cli/commandlineparser.h"
#include "src/config/styleoverride.h"
#include "src/core/capturerequest.h"
#include "src/core/controller.h"
#include "src/core/flameshot.h"
#include "src/core/flameshotdaemon.h"
#include "src/utils/confighandler.h"
#include "src/utils/filenamehandler.h"
@@ -47,16 +47,15 @@ void wayland_hacks()
void requestCaptureAndWait(const CaptureRequest& req)
{
Controller* controller = Controller::getInstance();
controller->requestCapture(req);
QObject::connect(
controller, &Controller::captureTaken, [&](QPixmap, QRect) {
// Only useful on MacOS because each instance hosts its own widgets
if (!FlameshotDaemon::isThisInstanceHostingWidgets()) {
qApp->exit(0);
}
});
QObject::connect(controller, &Controller::captureFailed, []() {
Flameshot* flameshot = Flameshot::instance();
flameshot->requestCapture(req);
QObject::connect(flameshot, &Flameshot::captureTaken, [&](QPixmap) {
// Only useful on MacOS because each instance hosts its own widgets
if (!FlameshotDaemon::isThisInstanceHostingWidgets()) {
qApp->exit(0);
}
});
QObject::connect(flameshot, &Flameshot::captureFailed, []() {
AbstractLogger::info() << "Screenshot aborted.";
qApp->exit(1);
});
@@ -124,7 +123,7 @@ int main(int argc, char* argv[])
qApp->installTranslator(&qtTranslator);
qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
auto* c = Controller::getInstance();
auto c = Flameshot::instance();
FlameshotDaemon::start();
#if !(defined(Q_OS_MACOS) || defined(Q_OS_WIN))
@@ -328,13 +327,13 @@ int main(int argc, char* argv[])
// PROCESS DATA
//--------------
Controller::setOrigin(Controller::CLI);
Flameshot::setOrigin(Flameshot::CLI);
if (parser.isSet(helpOption) || parser.isSet(versionOption)) {
} else if (parser.isSet(launcherArgument)) { // LAUNCHER
delete qApp;
new QApplication(argc, argv);
Controller* controller = Controller::getInstance();
controller->openLauncherWindow();
Flameshot* flameshot = Flameshot::instance();
flameshot->launcher();
qApp->exec();
} else if (parser.isSet(guiArgument)) { // GUI
delete qApp;
@@ -514,7 +513,7 @@ int main(int argc, char* argv[])
new QApplication(argc, argv);
QObject::connect(
qApp, &QApplication::lastWindowClosed, qApp, &QApplication::quit);
Controller::getInstance()->openConfigWindow();
Flameshot::instance()->config();
qApp->exec();
} else {
ConfigHandler config;

View File

@@ -3,7 +3,7 @@
#include "capturecontext.h"
#include "capturerequest.h"
#include "controller.h"
#include "flameshot.h"
// TODO rename
QPixmap CaptureContext::selectedScreenshotArea() const

View File

@@ -3,7 +3,7 @@
#include "screenshotsaver.h"
#include "abstractlogger.h"
#include "src/core/controller.h"
#include "src/core/flameshot.h"
#include "src/core/flameshotdaemon.h"
#include "src/utils/confighandler.h"
#include "src/utils/filenamehandler.h"

View File

@@ -1,5 +1,5 @@
#include "systemnotification.h"
#include "src/core/controller.h"
#include "src/core/flameshot.h"
#include "src/utils/confighandler.h"
#include <QApplication>
#include <QUrl>
@@ -8,6 +8,8 @@
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#else
#include "src/core/flameshotdaemon.h"
#endif
SystemNotification::SystemNotification(QObject* parent)
@@ -44,8 +46,10 @@ void SystemNotification::sendMessage(const QString& text,
this,
[&]() {
// The call is queued to avoid recursive static initialization of
// Controller and ConfigHandler.
Controller::getInstance()->sendTrayNotification(text, title, timeout);
// Flameshot and ConfigHandler.
if (FlameshotDaemon::instance())
FlameshotDaemon::instance()->sendTrayNotification(
text, title, timeout);
},
Qt::QueuedConnection);
#else

View File

@@ -13,6 +13,7 @@ target_sources(
capturelauncher.h
draggablewidgetmaker.h
imagelabel.h
trayicon.h
infowindow.h
loadspinner.h
notificationwidget.h
@@ -30,6 +31,7 @@ target_sources(
PRIVATE capturelauncher.cpp
draggablewidgetmaker.cpp
imagelabel.cpp
trayicon.cpp
infowindow.cpp
loadspinner.cpp
notificationwidget.cpp

View File

@@ -12,7 +12,7 @@
#include "capturewidget.h"
#include "abstractlogger.h"
#include "copytool.h"
#include "src/core/controller.h"
#include "src/core/flameshot.h"
#include "src/core/qguiappcurrentscreen.h"
#include "src/tools/toolfactory.h"
#include "src/utils/colorutils.h"
@@ -261,10 +261,10 @@ CaptureWidget::~CaptureWidget()
if (m_captureDone) {
QRect geometry(m_context.selection);
geometry.setTopLeft(geometry.topLeft() + m_context.widgetOffset);
Controller::getInstance()->exportCapture(
Flameshot::instance()->exportCapture(
pixmap(), geometry, m_context.request);
} else {
Controller::getInstance()->handleCaptureFailed();
emit Flameshot::instance()->captureFailed();
}
}

View File

@@ -3,7 +3,7 @@
#include "capturelauncher.h"
#include "./ui_capturelauncher.h"
#include "src/core/controller.h"
#include "src/core/flameshot.h"
#include "src/utils/globalvalues.h"
#include "src/utils/screengrabber.h"
#include "src/utils/screenshotsaver.h"
@@ -75,19 +75,19 @@ void CaptureLauncher::startCapture()
additionalDelayToHideUI +
ui->delayTime->value() * secondsToMilliseconds);
connectCaptureSlots();
Controller::getInstance()->requestCapture(req);
Flameshot::instance()->requestCapture(req);
}
void CaptureLauncher::connectCaptureSlots() const
{
connect(Controller::getInstance(),
&Controller::captureTaken,
connect(Flameshot::instance(),
&Flameshot::captureTaken,
this,
&CaptureLauncher::captureTaken);
connect(Controller::getInstance(),
&Controller::captureFailed,
&CaptureLauncher::onCaptureTaken);
connect(Flameshot::instance(),
&Flameshot::captureFailed,
this,
&CaptureLauncher::captureFailed);
&CaptureLauncher::onCaptureFailed);
}
void CaptureLauncher::disconnectCaptureSlots() const
@@ -97,17 +97,17 @@ void CaptureLauncher::disconnectCaptureSlots() const
// (random number, usually from 1 up to 20).
// So now it enables signal on "Capture new screenshot" button and disables
// on first success of fail.
disconnect(Controller::getInstance(),
&Controller::captureTaken,
disconnect(Flameshot::instance(),
&Flameshot::captureTaken,
this,
&CaptureLauncher::captureTaken);
disconnect(Controller::getInstance(),
&Controller::captureFailed,
&CaptureLauncher::onCaptureTaken);
disconnect(Flameshot::instance(),
&Flameshot::captureFailed,
this,
&CaptureLauncher::captureFailed);
&CaptureLauncher::onCaptureFailed);
}
void CaptureLauncher::captureTaken(QPixmap screenshot)
void CaptureLauncher::onCaptureTaken(QPixmap screenshot)
{
// MacOS specific, more details in the function disconnectCaptureSlots()
disconnectCaptureSlots();
@@ -124,7 +124,7 @@ void CaptureLauncher::captureTaken(QPixmap screenshot)
ui->launchButton->setEnabled(true);
}
void CaptureLauncher::captureFailed()
void CaptureLauncher::onCaptureFailed()
{
// MacOS specific, more details in the function disconnectCaptureSlots()
disconnectCaptureSlots();
@@ -135,4 +135,4 @@ void CaptureLauncher::captureFailed()
CaptureLauncher::~CaptureLauncher()
{
delete ui;
}
}

View File

@@ -26,6 +26,6 @@ private:
private slots:
void startCapture();
void captureTaken(QPixmap p);
void captureFailed();
};
void onCaptureTaken(QPixmap p);
void onCaptureFailed();
};

183
src/widgets/trayicon.cpp Normal file
View File

@@ -0,0 +1,183 @@
#include "trayicon.h"
#include "src/core/flameshot.h"
#include "src/core/flameshotdaemon.h"
#include "src/utils/globalvalues.h"
#include "src/utils/confighandler.h"
#include <QApplication>
#include <QMenu>
#include <QTimer>
#include <QUrl>
#include <QVersionNumber>
#if defined(Q_OS_MACOS)
#include <QOperatingSystemVersion>
#endif
TrayIcon::TrayIcon(QObject* parent)
: QSystemTrayIcon(parent)
{
initMenu();
setToolTip(QStringLiteral("Flameshot"));
#if defined(Q_OS_MACOS)
// Because of the following issues on MacOS "Catalina":
// https://bugreports.qt.io/browse/QTBUG-86393
// https://developer.apple.com/forums/thread/126072
auto currentMacOsVersion = QOperatingSystemVersion::current();
if (currentMacOsVersion >= currentMacOsVersion.MacOSBigSur) {
setContextMenu(m_menu);
}
#else
setContextMenu(m_menu);
#endif
QIcon icon =
QIcon::fromTheme("flameshot-tray", QIcon(GlobalValues::iconPathPNG()));
setIcon(icon);
#if defined(Q_OS_MACOS)
if (currentMacOsVersion < currentMacOsVersion.MacOSBigSur) {
// Because of the following issues on MacOS "Catalina":
// https://bugreports.qt.io/browse/QTBUG-86393
// https://developer.apple.com/forums/thread/126072
auto trayIconActivated = [this](QSystemTrayIcon::ActivationReason r) {
if (m_menu->isVisible()) {
m_menu->hide();
} else {
m_menu->popup(QCursor::pos());
}
};
connect(this, &QSystemTrayIcon::activated, this, trayIconActivated);
}
#else
connect(this, &TrayIcon::activated, this, [this](ActivationReason r) {
if (r == Trigger) {
startGuiCapture();
}
});
#endif
#ifdef Q_OS_WIN
// Ensure proper removal of tray icon when program quits on Windows.
connect(qApp, &QCoreApplication::aboutToQuit, this, &TrayIcon::hide);
#endif
show(); // TODO needed?
if (ConfigHandler().showStartupLaunchMessage()) {
showMessage(
"Flameshot",
QObject::tr(
"Hello, I'm here! Click icon in the tray to take a screenshot or "
"click with a right button to see more options."),
icon,
3000);
}
connect(ConfigHandler::getInstance(),
&ConfigHandler::fileChanged,
this,
[this]() {});
}
TrayIcon::~TrayIcon()
{
delete m_menu;
}
QAction* TrayIcon::appUpdates()
{
return m_appUpdates;
}
void TrayIcon::initMenu()
{
m_menu = new QMenu();
QAction* captureAction = new QAction(tr("&Take Screenshot"), this);
connect(captureAction, &QAction::triggered, this, [this]() {
#if defined(Q_OS_MACOS)
auto currentMacOsVersion = QOperatingSystemVersion::current();
if (currentMacOsVersion >= currentMacOsVersion.MacOSBigSur) {
startGuiCapture();
} else {
// It seems it is not relevant for MacOS BigSur (Wait 400 ms to hide
// the QMenu)
QTimer::singleShot(400, this, [this]() { startGuiCapture(); });
}
#else
// Wait 400 ms to hide the QMenu
QTimer::singleShot(400, this, [this]() {
startGuiCapture();
});
#endif
});
QAction* launcherAction = new QAction(tr("&Open Launcher"), this);
connect(launcherAction,
&QAction::triggered,
Flameshot::instance(),
&Flameshot::launcher);
QAction* configAction = new QAction(tr("&Configuration"), this);
connect(configAction,
&QAction::triggered,
Flameshot::instance(),
&Flameshot::config);
QAction* infoAction = new QAction(tr("&About"), this);
connect(
infoAction, &QAction::triggered, Flameshot::instance(), &Flameshot::info);
m_appUpdates = new QAction(tr("Check for updates"), this);
connect(m_appUpdates,
&QAction::triggered,
FlameshotDaemon::instance(),
&FlameshotDaemon::checkForUpdates);
connect(FlameshotDaemon::instance(),
&FlameshotDaemon::newVersionAvailable,
this,
[this](QVersionNumber version) {
QString newVersion =
tr("New version %1 is available").arg(version.toString());
m_appUpdates->setText(newVersion);
});
QAction* quitAction = new QAction(tr("&Quit"), this);
connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit);
// recent screenshots
QAction* recentAction = new QAction(tr("&Latest Uploads"), this);
connect(recentAction,
&QAction::triggered,
Flameshot::instance(),
&Flameshot::history);
m_menu->addAction(captureAction);
m_menu->addAction(launcherAction);
m_menu->addSeparator();
m_menu->addAction(recentAction);
m_menu->addSeparator();
m_menu->addAction(configAction);
m_menu->addSeparator();
m_menu->addAction(m_appUpdates);
m_menu->addAction(infoAction);
m_menu->addSeparator();
m_menu->addAction(quitAction);
}
void TrayIcon::enableCheckUpdatesAction(bool enable)
{
if (m_appUpdates != nullptr) {
m_appUpdates->setVisible(enable);
m_appUpdates->setEnabled(enable);
}
if (enable) {
FlameshotDaemon::instance()->getLatestAvailableVersion();
}
}
void TrayIcon::startGuiCapture()
{
auto* widget = Flameshot::instance()->gui();
FlameshotDaemon::instance()->showUpdateNotificationIfAvailable(widget);
}

25
src/widgets/trayicon.h Normal file
View File

@@ -0,0 +1,25 @@
#include <QSystemTrayIcon>
#pragma once
class QAction;
class TrayIcon : public QSystemTrayIcon
{
Q_OBJECT
public:
TrayIcon(QObject* parent = nullptr);
virtual ~TrayIcon();
QAction* appUpdates();
private:
void initTrayIcon();
void initMenu();
void enableCheckUpdatesAction(bool enable);
void startGuiCapture();
QMenu* m_menu;
QAction* m_appUpdates;
};