feat!: respond to D-Bus activation

Minimal support for requesting window activation in response to
activation requests from D-Bus.

This provides minimal support for xdg D-Bus activation in Keysmith:
only the activation signal itself is handled; the DBus API for opening
URLs is not implemented.

Issues: #18
master
Johan Ouwerkerk 2021-02-07 18:56:43 +01:00 committed by Bhushan Shah
parent 287cbcd480
commit fb953ed677
7 changed files with 158 additions and 2 deletions

View File

@ -55,6 +55,7 @@ endif()
if (BUILD_DBUS_INTERFACE OR (NOT ANDROID AND NOT DEFINED BUILD_DBUS_INTERFACE))
find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED)
find_package(KF5WindowSystem ${KF5_MIN_VERSION} REQUIRED)
set(ENABLE_DBUS_INTERFACE ON)
endif()

View File

@ -12,4 +12,12 @@ set(keysmith_SRCS
)
add_library(keysmith_lib STATIC ${keysmith_SRCS})
target_link_libraries(keysmith_lib Qt5::Core Qt5::Gui Qt5::Concurrent Qt5::Qml KF5::I18n model_lib account_lib)
target_link_libraries(keysmith_lib
Qt5::Core Qt5::Gui Qt5::Qml Qt5::Concurrent
KF5::I18n
model_lib account_lib
)
if (ENABLE_DBUS_INTERFACE)
target_link_libraries(keysmith_lib KF5::DBusAddons KF5::WindowSystem)
endif()

View File

@ -12,6 +12,14 @@
#include <QCommandLineOption>
#include <QtConcurrent>
#ifdef ENABLE_DBUS_INTERFACE
#include <QWindow>
#include <Qt>
#include <KDBusService>
#include <kstartupinfo.h>
#include <kwindowsystem.h>
#endif
KEYSMITH_LOGGER(logger, ".app.cli")
namespace app
@ -165,8 +173,62 @@ namespace app
return false;
}
(new InitialFlow(m_keysmith))->run(parser);
if (state->initialFlowDone()) {
(new ExternalCommandLineFlow(m_keysmith))->run(parser);
} else {
(new InitialFlow(m_keysmith))->run(parser);
}
return true;
}
#ifdef ENABLE_DBUS_INTERFACE
static QWindow * getMainWindow(QGuiApplication *app)
{
if (!app) {
qCDebug(logger) << "Cannot find a valid main window without a QGuiApplication";
return nullptr;
}
const auto windows = app->topLevelWindows();
for (auto *window: windows) {
if (window && window->type() == Qt::Window) {
return window;
}
}
qCDebug(logger) << "Unable to find main window for QGuiApplication:" << app;
return nullptr;
}
void Proxy::handleDBusActivation(const QStringList &arguments, const QString &workingDirectory)
{
Q_UNUSED(workingDirectory);
qCInfo(logger) << "Handling Keysmith activation request";
auto *s = sender();
Q_ASSERT_X(s, Q_FUNC_INFO, "should be triggered with a valid sender()");
auto *svc = qobject_cast<KDBusService*>(s);
Q_ASSERT_X(svc, Q_FUNC_INFO, "should be triggered by a KDBusService instance");
QCommandLineParser cliParser;
bool parseOk = parseCommandLine(cliParser, arguments);
if (!proxy(cliParser, parseOk)) {
qCDebug(logger) << "Rejected command line arguments";
svc->setExitValue(1);
}
const auto mainWindow = getMainWindow(m_app);
if (!mainWindow) {
qCWarning(logger) << "Unable to activate Keysmith main window: unable to find it";
svc->setExitValue(1);
return;
}
qCDebug(logger) << "Activating Keysmith main window";
KStartupInfo::setNewStartupId(mainWindow, KStartupInfo::startupId());
KWindowSystem::activateWindow(mainWindow->winId());
}
#endif
}

View File

@ -8,6 +8,8 @@
#include "keysmith.h"
#include "../model/input.h"
#include "../keysmith-features.h"
#include <QCommandLineParser>
#include <QGuiApplication>
#include <QObject>
@ -70,6 +72,10 @@ namespace app
explicit Proxy(QGuiApplication *app, QObject *parent = nullptr);
bool enable(Keysmith *keysmith);
bool proxy(const QCommandLineParser &parser, bool parsedOk);
#ifdef ENABLE_DBUS_INTERFACE
public Q_SLOTS:
void handleDBusActivation(const QStringList &arguments, const QString &workingDirectory);
#endif
private Q_SLOTS:
void disable(void);
private:

View File

@ -204,4 +204,65 @@ namespace app
flowStateOf(m_app)->setFlowRunning(false);
QTimer::singleShot(0, this, &QObject::deleteLater);
}
ExternalCommandLineFlow::ExternalCommandLineFlow(Keysmith *app) :
QObject(app), m_app(app), m_input(new model::AccountInput(this))
{
Q_ASSERT_X(app, Q_FUNC_INFO, "should have a Keysmith instance");
}
void ExternalCommandLineFlow::run(const QCommandLineParser &parser)
{
const auto argv = parser.positionalArguments();
if (argv.isEmpty()) {
qCDebug(logger) << "No URIs to handle, nothing to do for external commandline:" << this;
QTimer::singleShot(0, this, &ExternalCommandLineFlow::deleteLater);
return;
}
flowStateOf(m_app)->setFlowRunning(true);
overviewStateOf(m_app)->setActionsEnabled(false);
qCDebug(logger) << "Will parse given URI(s) from external commandline:" << this;
auto job = new CommandLineAccountJob(m_input);
QObject::connect(job, &CommandLineAccountJob::newAccountProcessed,
this, &ExternalCommandLineFlow::onNewAccountProcessed);
QObject::connect(job, &CommandLineAccountJob::newAccountInvalid,
this, &ExternalCommandLineFlow::onNewAccountInvalid);
job->run(argv[0]);
}
void ExternalCommandLineFlow::onNewAccountProcessed(void)
{
auto vm = new AddAccountViewModel(m_input, accountListOf(m_app), false, true);
QObject::connect(vm, &AddAccountViewModel::accepted, this, &ExternalCommandLineFlow::onAccepted);
QObject::connect(vm, &AddAccountViewModel::cancelled, this, &ExternalCommandLineFlow::back);
navigationFor(m_app)->push(Navigation::Page::AddAccount, vm);
}
void ExternalCommandLineFlow::onNewAccountInvalid(void)
{
auto vm = new ErrorViewModel(
i18nc("@title:window", "Invalid account"),
i18nc("@info:label", "The account you are trying to add is invalid. Continue without adding the account."),
false
);
QObject::connect(vm, &ErrorViewModel::dismissed, this, &ExternalCommandLineFlow::back);
navigationFor(m_app)->navigate(Navigation::Page::Error, vm);
}
void ExternalCommandLineFlow::onAccepted(void)
{
accountListOf(m_app)->addAccount(m_input);
QTimer::singleShot(0, this, &ExternalCommandLineFlow::back);
}
void ExternalCommandLineFlow::back(void)
{
auto vm = new AccountsOverviewViewModel(m_app);
navigationFor(m_app)->navigate(Navigation::Page::AccountsOverview, vm);
overviewStateOf(m_app)->setActionsEnabled(true);
flowStateOf(m_app)->setFlowRunning(false);
QTimer::singleShot(0, this, &QObject::deleteLater);
}
}

View File

@ -51,6 +51,23 @@ namespace app
Keysmith * const m_app;
model::AccountInput * const m_input;
};
class ExternalCommandLineFlow: public QObject
{
Q_OBJECT
public:
explicit ExternalCommandLineFlow(Keysmith *app);
public:
void run(const QCommandLineParser &parser);
private Q_SLOTS:
void onNewAccountInvalid(void);
void onNewAccountProcessed(void);
void back(void);
void onAccepted(void);
private:
Keysmith * const m_app;
model::AccountInput * const m_input;
};
}
#endif

View File

@ -79,6 +79,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
#ifdef ENABLE_DBUS_INTERFACE
KDBusService service(KDBusService::Unique);
QObject::connect(&service, &KDBusService::activateRequested, &proxy, &app::Proxy::handleDBusActivation);
#endif
QQmlApplicationEngine engine;