diff --git a/src/config/generalconf.cpp b/src/config/generalconf.cpp index a9e5b14b..759d0c5a 100644 --- a/src/config/generalconf.cpp +++ b/src/config/generalconf.cpp @@ -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 #include @@ -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) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 482d71ca..c933bdd6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/capturerequest.cpp b/src/core/capturerequest.cpp index 0d3aa315..eec0ceb0 100644 --- a/src/core/capturerequest.cpp +++ b/src/core/capturerequest.cpp @@ -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" diff --git a/src/core/controller.cpp b/src/core/controller.cpp deleted file mode 100644 index 6a5a9829..00000000 --- a/src/core/controller.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_WIN -#include "src/core/globalshortcutfilter.h" -#endif - -#if defined(Q_OS_MACOS) -#include -#include -#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; diff --git a/src/core/controller.h b/src/core/controller.h deleted file mode 100644 index edfc3ac9..00000000 --- a/src/core/controller.h +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include - -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; - -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 m_captureWindow; - QPointer m_infoWindow; - QPointer m_launcherWindow; - QPointer m_configWindow; - QPointer 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 -}; diff --git a/src/core/flameshot.cpp b/src/core/flameshot.cpp new file mode 100644 index 00000000..ae68cb5a --- /dev/null +++ b/src/core/flameshot.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(Q_OS_MACOS) +#include +#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; diff --git a/src/core/flameshot.h b/src/core/flameshot.h new file mode 100644 index 00000000..c738a737 --- /dev/null +++ b/src/core/flameshot.h @@ -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 +#include +#include + +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 m_captureWindow; + QPointer m_infoWindow; + QPointer m_launcherWindow; + QPointer 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 +}; diff --git a/src/core/flameshotdaemon.cpp b/src/core/flameshotdaemon.cpp index 1445d94e..72254903 100644 --- a/src/core/flameshotdaemon.cpp +++ b/src/core/flameshotdaemon.cpp @@ -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 #include #include #include +#include +#include +#include +#include +#include #include #include +#include +#include + +#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 = diff --git a/src/core/flameshotdaemon.h b/src/core/flameshotdaemon.h index 25206066..2bfdf9c7 100644 --- a/src/core/flameshotdaemon.h +++ b/src/core/flameshotdaemon.h @@ -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 m_widgets; + TrayIcon* m_trayIcon; + + QString m_appLatestUrl; + QString m_appLatestVersion; + bool m_showCheckAppUpdateStatus; + QNetworkAccessManager* m_networkCheckUpdates; + static FlameshotDaemon* m_instance; friend class FlameshotDBusAdapter; diff --git a/src/core/globalshortcutfilter.cpp b/src/core/globalshortcutfilter.cpp index aac69ec2..c98b18f4 100644 --- a/src/core/globalshortcutfilter.cpp +++ b/src/core/globalshortcutfilter.cpp @@ -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 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)); } diff --git a/src/main.cpp b/src/main.cpp index 1d84be11..500cf38c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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; diff --git a/src/tools/capturecontext.cpp b/src/tools/capturecontext.cpp index 843a1dc0..b14f0ea6 100644 --- a/src/tools/capturecontext.cpp +++ b/src/tools/capturecontext.cpp @@ -3,7 +3,7 @@ #include "capturecontext.h" #include "capturerequest.h" -#include "controller.h" +#include "flameshot.h" // TODO rename QPixmap CaptureContext::selectedScreenshotArea() const diff --git a/src/utils/screenshotsaver.cpp b/src/utils/screenshotsaver.cpp index 53094072..32f83778 100644 --- a/src/utils/screenshotsaver.cpp +++ b/src/utils/screenshotsaver.cpp @@ -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" diff --git a/src/utils/systemnotification.cpp b/src/utils/systemnotification.cpp index c4be02b3..16c19efa 100644 --- a/src/utils/systemnotification.cpp +++ b/src/utils/systemnotification.cpp @@ -1,5 +1,5 @@ #include "systemnotification.h" -#include "src/core/controller.h" +#include "src/core/flameshot.h" #include "src/utils/confighandler.h" #include #include @@ -8,6 +8,8 @@ #include #include #include +#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 diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 4fb3afd6..95902559 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -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 diff --git a/src/widgets/capture/capturewidget.cpp b/src/widgets/capture/capturewidget.cpp index 4c7ad238..9d3dee97 100644 --- a/src/widgets/capture/capturewidget.cpp +++ b/src/widgets/capture/capturewidget.cpp @@ -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(); } } diff --git a/src/widgets/capturelauncher.cpp b/src/widgets/capturelauncher.cpp index f352a4fc..2993cc34 100644 --- a/src/widgets/capturelauncher.cpp +++ b/src/widgets/capturelauncher.cpp @@ -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; -} \ No newline at end of file +} diff --git a/src/widgets/capturelauncher.h b/src/widgets/capturelauncher.h index 315eef7a..00467573 100644 --- a/src/widgets/capturelauncher.h +++ b/src/widgets/capturelauncher.h @@ -26,6 +26,6 @@ private: private slots: void startCapture(); - void captureTaken(QPixmap p); - void captureFailed(); -}; \ No newline at end of file + void onCaptureTaken(QPixmap p); + void onCaptureFailed(); +}; diff --git a/src/widgets/trayicon.cpp b/src/widgets/trayicon.cpp new file mode 100644 index 00000000..9d1137a7 --- /dev/null +++ b/src/widgets/trayicon.cpp @@ -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 +#include +#include +#include +#include + +#if defined(Q_OS_MACOS) +#include +#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); +} diff --git a/src/widgets/trayicon.h b/src/widgets/trayicon.h new file mode 100644 index 00000000..386ece77 --- /dev/null +++ b/src/widgets/trayicon.h @@ -0,0 +1,25 @@ +#include + +#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; +};