235 lines
7.9 KiB
C++
235 lines
7.9 KiB
C++
/*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
* SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
|
*/
|
|
#include "cli.h"
|
|
#include "../logging_p.h"
|
|
#include "../model/qr.h"
|
|
#include "flows_p.h"
|
|
#include "state_p.h"
|
|
|
|
#include <KLocalizedString>
|
|
#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
|
|
{
|
|
void CommandLineOptions::addOptions(QCommandLineParser &parser)
|
|
{
|
|
Proxy::addOptions(parser);
|
|
}
|
|
|
|
CommandLineOptions::CommandLineOptions(QCommandLineParser &parser, bool parseOk, QObject *parent) :
|
|
QObject(parent), m_parseOk(parseOk), m_errorText(parseOk ? QString() : parser.errorText()), m_parser(parser)
|
|
{
|
|
}
|
|
|
|
QString CommandLineOptions::errorText(void) const
|
|
{
|
|
return m_errorText;
|
|
}
|
|
|
|
bool CommandLineOptions::optionsOk(void) const
|
|
{
|
|
return m_parseOk;
|
|
}
|
|
|
|
bool CommandLineOptions::newAccountRequested(void) const
|
|
{
|
|
return optionsOk() && !m_parser.positionalArguments().isEmpty();
|
|
}
|
|
|
|
void CommandLineOptions::handleNewAccount(model::AccountInput *recipient)
|
|
{
|
|
if (!newAccountRequested()) {
|
|
qCDebug(logger) << "Ignoring request to handle new account:"
|
|
<< "Invalid commandline options or no URI was received on the commandline";
|
|
return;
|
|
}
|
|
|
|
auto job = new CommandLineAccountJob(recipient);
|
|
const auto argv = m_parser.positionalArguments();
|
|
QObject::connect(job, &CommandLineAccountJob::newAccountProcessed, this, &CommandLineOptions::newAccountProcessed);
|
|
QObject::connect(job, &CommandLineAccountJob::newAccountInvalid, this, &CommandLineOptions::newAccountInvalid);
|
|
job->run(argv[0]);
|
|
}
|
|
|
|
CommandLineAccountJob::CommandLineAccountJob(model::AccountInput *recipient) :
|
|
QObject(), m_alive(true), m_recipient(recipient)
|
|
{
|
|
QObject::connect(recipient, &QObject::destroyed, this, &CommandLineAccountJob::expired);
|
|
}
|
|
|
|
void CommandLineAccountJob::expired(void)
|
|
{
|
|
m_alive = false;
|
|
}
|
|
|
|
void CommandLineAccountJob::run(const QString &uri)
|
|
{
|
|
QtConcurrent::run(&CommandLineAccountJob::processNewAccount, this, uri);
|
|
}
|
|
|
|
void CommandLineAccountJob::processNewAccount(CommandLineAccountJob *target, const QString &uri)
|
|
{
|
|
const auto result = model::QrParameters::parse(uri);
|
|
bool invoked = false;
|
|
if (result) {
|
|
qCInfo(logger) << "Successfully parsed the URI passed on the commandline";
|
|
invoked = QMetaObject::invokeMethod(target, [target, result](void) -> void {
|
|
if (target->m_alive) {
|
|
result->populate(target->m_recipient);
|
|
qCDebug(logger) << "Reporting success parsing URI from commandline";
|
|
Q_EMIT target->newAccountProcessed();
|
|
} else {
|
|
qCDebug(logger) << "Not reporting success parsing URI from commandline: recipient has expired";
|
|
}
|
|
QTimer::singleShot(0, target, &QObject::deleteLater);
|
|
});
|
|
} else {
|
|
qCInfo(logger) << "Failed to parse the URI passed on the commandline";
|
|
invoked = QMetaObject::invokeMethod(target, [target](void) -> void {
|
|
if (target->m_alive) {
|
|
qCDebug(logger) << "Reporting failure to parse URI from commandline";
|
|
Q_EMIT target->newAccountInvalid();
|
|
} else {
|
|
qCDebug(logger) << "Not reporting failure to parse URI from commandline: recipient has expired";
|
|
}
|
|
QTimer::singleShot(0, target, &QObject::deleteLater);
|
|
});
|
|
}
|
|
|
|
if (!invoked) {
|
|
Q_ASSERT_X(false, Q_FUNC_INFO, "should be able to invoke meta method");
|
|
qCDebug(logger) << "Failed to signal result of processing the URI passed on the commandline";
|
|
}
|
|
}
|
|
|
|
Proxy::Proxy(QGuiApplication *app, QObject *parent) :
|
|
QObject(parent), m_keysmith(nullptr), m_app(app)
|
|
{
|
|
Q_ASSERT_X(m_app, Q_FUNC_INFO, "Should have a valid QGuiApplication instance");
|
|
}
|
|
|
|
void Proxy::addOptions(QCommandLineParser &parser)
|
|
{
|
|
parser.addPositionalArgument(
|
|
QStringLiteral("<uri>"),
|
|
i18nc("@info (<uri> placeholder)", "Optional account to add, formatted as otpauth:// URI (e.g. from a QR code)")
|
|
);
|
|
}
|
|
|
|
bool Proxy::parseCommandLine(QCommandLineParser &parser, const QStringList &argv)
|
|
{
|
|
// options that will be handled via UI interaction
|
|
app::Proxy::addOptions(parser);
|
|
return parser.parse(argv);
|
|
}
|
|
|
|
void Proxy::disable(void)
|
|
{
|
|
m_keysmith = nullptr;
|
|
}
|
|
|
|
bool Proxy::enable(Keysmith *keysmith)
|
|
{
|
|
Q_ASSERT_X(keysmith, Q_FUNC_INFO, "should be given a valid Keysmith instance");
|
|
if (m_keysmith) {
|
|
qCDebug(logger)
|
|
<< "Not (re)initialising proxy with new Keysmith instance:" << keysmith
|
|
<< "Already initialised with:" << m_keysmith;
|
|
return false;
|
|
}
|
|
|
|
m_keysmith = keysmith;
|
|
QObject::connect(m_keysmith, &QObject::destroyed, this, &Proxy::disable);
|
|
return true;
|
|
}
|
|
|
|
bool Proxy::proxy(const QCommandLineParser &parser, bool parsedOk)
|
|
{
|
|
if (!parsedOk) {
|
|
qCDebug(logger) << "Not proxying to Keysmith instance: invalid command line";
|
|
return false;
|
|
}
|
|
if (!m_keysmith) {
|
|
qCDebug(logger) << "Not proxying command line arguments: not initialised with a Keysmith instance";
|
|
return false;
|
|
}
|
|
|
|
auto state = flowStateOf(m_keysmith);
|
|
if (state->flowRunning()) {
|
|
qCDebug(logger) << "Not proxying command line arguments: a 'competing' flow is already running";
|
|
return false;
|
|
}
|
|
|
|
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
|
|
}
|