Commit dec0e6ce authored by Méven Car's avatar Méven Car

Refactor Spectacle to run in single instance mode with KDBusService::Unique

Summary:
Since Spectacle got DBus activation support in 09cd1188, it has been run in multiple instance mode.
But it has been shaddowed by using qbus ee862d16 in the desktop file to mimic singleton behavior.

This refactors Spectacle to run in true single instance mode using KDBusService::Unique.
This is to allow Spectacle to have its executable in its Exec desktop Entry, to allow KWin to match the executable to the service file, itself to allow screenshots under Wayland without intermediate click.

The listed bugs are fixed incidently except for 414739 where a simple fix was added in screenShotUpdated.

BUG: 420477
BUG: 414739

CCBUG: 412186

Test Plan:
Start spectacle, alternatively, from shortcut, command line.
Take screenshots, from alternative ways.

There is always at most one Spectacle window unless launched with `--new-instance`.

Reviewers: davidre, ngraham, bport, davidedmundson, apol

Reviewed By: davidre

Subscribers: broulik, anthonyfieroni, #spectacle

Tags: #spectacle

Differential Revision: https://phabricator.kde.org/D29487
parent 246ac1f8
......@@ -140,13 +140,13 @@ Keywords[uk]=знімок;захоплення;друк;знімок вікна;
Keywords[x-test]=xxsnapshotxx;xxcapturexx;xxprintxx;xxscreenshotxx;xxsnippingxx;xxsnipxx;
Keywords[zh_CN]=snapshot;capture;print;screenshot;snipping;snip;快照;截图;抓取;屏幕截图;截屏;抓屏;截取;
Keywords[zh_TW]=snapshot;capture;print;screenshot;snipping;snip;截圖;擷取;截圖程式
Exec=@QtBinariesDir@/qdbus org.kde.Spectacle / StartAgent
Exec=${CMAKE_INSTALL_PREFIX}/bin/spectacle
Icon=spectacle
Type=Application
Terminal=false
StartupNotify=false
Actions=FullScreenScreenShot;CurrentMonitorScreenShot;ActiveWindowScreenShot;RectangularRegionScreenShot;
X-DBUS-StartupType=Multi
X-DBUS-StartupType=Unique
X-DBUS-ServiceName=org.kde.Spectacle
X-KDE-Shortcuts=Print
......
......@@ -36,12 +36,13 @@ int main(int argc, char **argv)
{
// set up the application
QApplication lApp(argc, argv);
QApplication app(argc, argv);
lApp.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
lApp.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
KLocalizedString::setApplicationDomain("spectacle");
QCoreApplication::setOrganizationDomain(QStringLiteral("org.kde"));
KAboutData aboutData(QStringLiteral("spectacle"),
i18n("Spectacle"),
......@@ -53,115 +54,55 @@ int main(int argc, char **argv)
aboutData.addAuthor(QStringLiteral("David Redondo"), QString(), QStringLiteral("kde@david-redondo.de"));
aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
KAboutData::setApplicationData(aboutData);
lApp.setWindowIcon(QIcon::fromTheme(QStringLiteral("spectacle")));
app.setWindowIcon(QIcon::fromTheme(QStringLiteral("spectacle")));
// set up the command line options parser
SpectacleCore lCore;
QCommandLineParser lCmdLineParser;
aboutData.setupCommandLine(&lCmdLineParser);
lCore.populateCommandLineParser(&lCmdLineParser);
lCmdLineParser.addOptions({
{{QStringLiteral("f"), QStringLiteral("fullscreen")}, i18n("Capture the entire desktop (default)")},
{{QStringLiteral("m"), QStringLiteral("current")}, i18n("Capture the current monitor")},
{{QStringLiteral("a"), QStringLiteral("activewindow")}, i18n("Capture the active window")},
{{QStringLiteral("u"), QStringLiteral("windowundercursor")}, i18n("Capture the window currently under the cursor, including parents of pop-up menus")},
{{QStringLiteral("t"), QStringLiteral("transientonly")}, i18n("Capture the window currently under the cursor, excluding parents of pop-up menus")},
{{QStringLiteral("r"), QStringLiteral("region")}, i18n("Capture a rectangular region of the screen")},
{{QStringLiteral("g"), QStringLiteral("gui")}, i18n("Start in GUI mode (default)")},
{{QStringLiteral("b"), QStringLiteral("background")}, i18n("Take a screenshot and exit without showing the GUI")},
{{QStringLiteral("s"), QStringLiteral("dbus")}, i18n("Start in DBus-Activation mode")},
{{QStringLiteral("n"), QStringLiteral("nonotify")}, i18n("In background mode, do not pop up a notification when the screenshot is taken")},
{{QStringLiteral("o"), QStringLiteral("output")}, i18n("In background mode, save image to specified file"), QStringLiteral("fileName")},
{{QStringLiteral("d"), QStringLiteral("delay")}, i18n("In background mode, delay before taking the shot (in milliseconds)"), QStringLiteral("delayMsec")},
{{QStringLiteral("c"), QStringLiteral("clipboard")}, i18n("In background mode, copy screenshot to clipboard")},
{{QStringLiteral("w"), QStringLiteral("onclick")}, i18n("Wait for a click before taking screenshot. Invalidates delay")}
});
lCmdLineParser.process(lApp);
// first parsing for help-about
lCmdLineParser.process(app.arguments());
aboutData.processCommandLine(&lCmdLineParser);
// extract the capture mode
Spectacle::CaptureMode lCaptureMode = Spectacle::CaptureMode::AllScreens;
if (lCmdLineParser.isSet(QStringLiteral("current"))) {
lCaptureMode = Spectacle::CaptureMode::CurrentScreen;
} else if (lCmdLineParser.isSet(QStringLiteral("activewindow"))) {
lCaptureMode = Spectacle::CaptureMode::ActiveWindow;
} else if (lCmdLineParser.isSet(QStringLiteral("region"))) {
lCaptureMode = Spectacle::CaptureMode::RectangularRegion;
} else if (lCmdLineParser.isSet(QStringLiteral("windowundercursor"))) {
lCaptureMode = Spectacle::CaptureMode::TransientWithParent;
} else if (lCmdLineParser.isSet(QStringLiteral("transientonly"))) {
lCaptureMode = Spectacle::CaptureMode::WindowUnderCursor;
}
// and new-instance
if (lCmdLineParser.isSet(QStringLiteral("new-instance"))){
lCore.init();
// are we running in background or dbus mode?
QObject::connect(&lCore, &SpectacleCore::allDone, &app, &QCoreApplication::quit, Qt::QueuedConnection);
SpectacleCore::StartMode lStartMode = SpectacleCore::StartMode::Gui;
bool lNotify = true;
bool lCopyToClipboard = false;
qint64 lDelayMsec = 0;
QString lFileName = QString();
// fire it up
lCore.onActivateRequested(app.arguments(), QStringLiteral());
if (lCmdLineParser.isSet(QStringLiteral("background"))) {
lStartMode = SpectacleCore::StartMode::Background;
} else if (lCmdLineParser.isSet(QStringLiteral("dbus"))) {
lStartMode = SpectacleCore::StartMode::DBus;
return app.exec();
}
switch(lStartMode) {
case SpectacleCore::StartMode::Background:
if (lCmdLineParser.isSet(QStringLiteral("nonotify"))) {
lNotify = false;
}
if (lCmdLineParser.isSet(QStringLiteral("output"))) {
lFileName = lCmdLineParser.value(QStringLiteral("output"));
}
if (lCmdLineParser.isSet(QStringLiteral("delay"))) {
bool lParseOk = false;
qint64 lDelayValue = lCmdLineParser.value(QStringLiteral("delay")).toLongLong(&lParseOk);
if (lParseOk) {
lDelayMsec = lDelayValue;
}
}
if (lCmdLineParser.isSet(QStringLiteral("onclick"))) {
lDelayMsec = -1;
}
if (lCmdLineParser.isSet(QStringLiteral("clipboard"))) {
lCopyToClipboard = true;
}
lApp.setQuitOnLastWindowClosed(false);
break;
case SpectacleCore::StartMode::DBus:
lApp.setQuitOnLastWindowClosed(false);
break;
case SpectacleCore::StartMode::Gui:
break;
}
// Ensure that we only launch a new instance if we need to
// If there is already an instance running, we will quit here
// and activateRequested signal is triggered
// For some reason this does not work properly if behind an if
KDBusService service(KDBusService::Unique, &lCore);
// release the kraken
// Delay initialisation after we now we are in the single instance or new-instance was passed, to avoid doing it each time spectacle executable is called
lCore.init();
SpectacleCore lCore(lStartMode, lCaptureMode, lFileName, lDelayMsec, lNotify, lCopyToClipboard);
QObject::connect(&lCore, &SpectacleCore::allDone, qApp, &QApplication::quit);
// set up the KDBusService activateRequested slot
QObject::connect(&service, &KDBusService::activateRequested, &lCore, &SpectacleCore::onActivateRequested);
QObject::connect(&lCore, &SpectacleCore::allDone, &app, &QCoreApplication::quit, Qt::QueuedConnection);
QObject::connect(qApp, &QApplication::aboutToQuit, Settings::self(), &Settings::save);
// create the dbus connections
new KDBusService(KDBusService::Multiple, &lCore);
// create the dbus connections
SpectacleDBusAdapter *lDBusAdapter = new SpectacleDBusAdapter(&lCore);
QObject::connect(&lCore, &SpectacleCore::grabFailed, lDBusAdapter, &SpectacleDBusAdapter::ScreenshotFailed);
QObject::connect(ExportManager::instance(), &ExportManager::imageSaved, &lCore, [&](const QUrl &savedAt) {
emit lDBusAdapter->ScreenshotTaken(savedAt.toLocalFile());
lDBusAdapter->ScreenshotTaken(savedAt.toLocalFile());
});
QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), &lCore);
QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Spectacle"));
// fire it up
lCore.onActivateRequested(app.arguments(), QStringLiteral());
return lApp.exec();
return app.exec();
}
This diff is collapsed.
......@@ -22,6 +22,7 @@
#pragma once
#include <QObject>
#include <QCommandLineParser>
#include "ExportManager.h"
#include "Gui/KSMainWindow.h"
......@@ -51,18 +52,15 @@ class SpectacleCore: public QObject
Background = 2
};
explicit SpectacleCore(StartMode theStartMode,
Spectacle::CaptureMode theCaptureMode,
QString &theSaveFileName,
qint64 theDelayMsec,
bool theNotifyOnGrab,
bool theCopyToClipboard,
QObject *parent = nullptr);
explicit SpectacleCore(QObject *parent = nullptr);
virtual ~SpectacleCore() = default;
void init();
QString filename() const;
void setFilename(const QString &filename);
void populateCommandLineParser(QCommandLineParser *lCmdLineParser);
Q_SIGNALS:
void errorMessage(const QString &errString);
......@@ -76,15 +74,15 @@ class SpectacleCore: public QObject
void showErrorMessage(const QString &theErrString);
void screenshotUpdated(const QPixmap &thePixmap);
void screenshotFailed();
void dbusStartAgent();
void doStartDragAndDrop();
void doNotify(const QUrl &theSavedAt);
void doCopyPath(const QUrl &savedAt);
private:
void onActivateRequested(QStringList arguments, const QString& /*workingDirectory */);
private:
void initGui(bool theIncludePointer, bool theIncludeDecorations);
void initGui(int theDelay, bool theIncludePointer, bool theIncludeDecorations);
Platform::GrabMode toPlatformGrabMode(Spectacle::CaptureMode theCaptureMode);
void setUpShortcuts();
......
......@@ -33,32 +33,27 @@ inline SpectacleCore *SpectacleDBusAdapter::parent() const
return static_cast<SpectacleCore *>(QObject::parent());
}
Q_NOREPLY void SpectacleDBusAdapter::StartAgent()
{
parent()->dbusStartAgent();
}
Q_NOREPLY void SpectacleDBusAdapter::FullScreen(bool includeMousePointer)
void SpectacleDBusAdapter::FullScreen(bool includeMousePointer)
{
parent()->takeNewScreenshot(Spectacle::CaptureMode::AllScreens, 0, includeMousePointer, true);
}
Q_NOREPLY void SpectacleDBusAdapter::CurrentScreen(bool includeMousePointer)
void SpectacleDBusAdapter::CurrentScreen(bool includeMousePointer)
{
parent()->takeNewScreenshot(Spectacle::CaptureMode::CurrentScreen, 0, includeMousePointer, true);
}
Q_NOREPLY void SpectacleDBusAdapter::ActiveWindow(bool includeWindowDecorations, bool includeMousePointer)
void SpectacleDBusAdapter::ActiveWindow(bool includeWindowDecorations, bool includeMousePointer)
{
parent()->takeNewScreenshot(Spectacle::CaptureMode::ActiveWindow, 0, includeMousePointer, includeWindowDecorations);
}
Q_NOREPLY void SpectacleDBusAdapter::WindowUnderCursor(bool includeWindowDecorations, bool includeMousePointer)
void SpectacleDBusAdapter::WindowUnderCursor(bool includeWindowDecorations, bool includeMousePointer)
{
parent()->takeNewScreenshot(Spectacle::CaptureMode::WindowUnderCursor, 0, includeMousePointer, includeWindowDecorations);
}
Q_NOREPLY void SpectacleDBusAdapter::RectangularRegion(bool includeMousePointer)
void SpectacleDBusAdapter::RectangularRegion(bool includeMousePointer)
{
parent()->takeNewScreenshot(Spectacle::CaptureMode::RectangularRegion, 0, includeMousePointer, false);
}
......@@ -28,36 +28,6 @@ class SpectacleDBusAdapter: public QDBusAbstractAdaptor
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.Spectacle")
Q_CLASSINFO("D-Bus Introspection", ""
" <interface name=\"org.kde.Spectacle\">\n"
" <method name=\"StartAgent\">\n"
" </method>\n"
" <method name=\"FullScreen\">\n"
" <arg direction=\"in\" type=\"b\" name=\"includeMousePointer\"/>\n"
" </method>\n"
" <method name=\"CurrentScreen\">\n"
" <arg direction=\"in\" type=\"b\" name=\"includeMousePointer\"/>\n"
" </method>\n"
" <method name=\"ActiveWindow\">\n"
" <arg direction=\"in\" type=\"b\" name=\"includeWindowDecorations\"/>\n"
" <arg direction=\"in\" type=\"b\" name=\"includeMousePointer\"/>\n"
" </method>\n"
" <method name=\"WindowUnderCursor\">\n"
" <arg direction=\"in\" type=\"b\" name=\"includeWindowDecorations\"/>\n"
" <arg direction=\"in\" type=\"b\" name=\"includeMousePointer\"/>\n"
" </method>\n"
" <method name=\"RectangularRegion\">\n"
" <arg direction=\"in\" type=\"b\" name=\"includeMousePointer\"/>\n"
" </method>\n"
" <signal name=\"ScreenshotTaken\">\n"
" <arg direction=\"out\" type=\"s\" name=\"fileName\"/>\n"
" </signal>\n"
" <signal name=\"ScreenshotFailed\">\n"
" </signal>\n"
" </interface>\n"
""
)
public:
SpectacleDBusAdapter(SpectacleCore *parent);
......@@ -67,7 +37,6 @@ class SpectacleDBusAdapter: public QDBusAbstractAdaptor
public Q_SLOTS:
Q_NOREPLY void StartAgent();
Q_NOREPLY void FullScreen(bool includeMousePointer);
Q_NOREPLY void CurrentScreen(bool includeMousePointer);
Q_NOREPLY void ActiveWindow(bool includeWindowDecorations, bool includeMousePointer);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment