aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/m_key_server_sync/CMakeLists.txt3
-rw-r--r--src/m_key_server_sync/KeyServerList.cpp64
-rw-r--r--src/m_key_server_sync/KeyServerSettingsPage.cpp11
-rw-r--r--src/m_key_server_sync/KeyServerSettingsPage.h37
-rw-r--r--src/m_key_server_sync/KeyServerSyncModule.cpp333
-rw-r--r--src/m_key_server_sync/SearchKeyDialog.cpp2
-rw-r--r--src/m_key_server_sync/SearchKeyDialog.h4
-rw-r--r--src/m_key_server_sync/module.json7
-rw-r--r--src/m_key_server_sync/ui/main.lua35
-rw-r--r--src/m_pgp_inspect/CMakeLists.txt3
-rw-r--r--src/m_pgp_inspect/PGPInspectDialog.cpp12
-rw-r--r--src/m_pgp_inspect/PGPInspectDialog.h4
-rw-r--r--src/m_pgp_inspect/PGPInspectModule.cpp115
-rw-r--r--src/m_pgp_inspect/module.json5
-rw-r--r--src/m_pgp_inspect/ui/main.lua16
-rw-r--r--src/m_ver_check/CMakeLists.txt3
-rw-r--r--src/m_ver_check/UpdateTab.cpp9
-rw-r--r--src/m_ver_check/UpdateTab.h2
-rw-r--r--src/m_ver_check/VersionCheckingModule.cpp491
-rw-r--r--src/m_ver_check/module.json9
-rw-r--r--src/m_ver_check/ui/main.lua14
21 files changed, 488 insertions, 691 deletions
diff --git a/src/m_key_server_sync/CMakeLists.txt b/src/m_key_server_sync/CMakeLists.txt
index fdaef92..df12fc1 100644
--- a/src/m_key_server_sync/CMakeLists.txt
+++ b/src/m_key_server_sync/CMakeLists.txt
@@ -27,4 +27,5 @@
# capabilities and translation context -- lives in module.json beside this file.
gf_add_module(
NAME key_server_sync
- QT Core Widgets Network)
+ QT Core Widgets Network
+ LUA_SCRIPTS ui/main.lua)
diff --git a/src/m_key_server_sync/KeyServerList.cpp b/src/m_key_server_sync/KeyServerList.cpp
index b4267d7..160d57c 100644
--- a/src/m_key_server_sync/KeyServerList.cpp
+++ b/src/m_key_server_sync/KeyServerList.cpp
@@ -32,28 +32,32 @@
#include <QJsonDocument>
#include <QMutex>
#include <QMutexLocker>
-#include <QSettings>
#include "GFModule.h"
#include "GFSDKUI.h"
namespace {
-constexpr auto kSchemaVersionKey = "key_server_sync/schema_version";
-constexpr auto kServersKey = "key_server_sync/servers";
-constexpr auto kDefaultServerKey = "key_server_sync/default_server";
+// Relative: the Host places them in this module's own settings group, which
+// is where they always were ("key_server_sync/..."), so nothing moves.
+constexpr auto kSchemaVersionKey = "schema_version";
+constexpr auto kServersKey = "servers";
+constexpr auto kDefaultServerKey = "default_server";
constexpr auto kSchemaVersion = 1;
constexpr auto kOpenPGPServer = "https://keys.openpgp.org";
-/// The settings object is shared with the host and with the network task
-/// runner, and QSettings is only reentrant, so every read-modify-write of our
-/// keys has to be serialized.
+/// A read-modify-write of the list is two calls; the mutex keeps two of this
+/// module's own threads from interleaving them. Every call reaches the Host's
+/// settings through the storage capability -- no settings object is shared.
Q_GLOBAL_STATIC(QMutex, settings_mutex)
-auto GlobalSettings() -> QSettings* {
- return qobject_cast<QSettings*>(
- static_cast<QObject*>(GFStorageSettingsRoot(GFModuleSdkContext())));
+auto Get(const char* key) -> QVariant {
+ return gf::sdk::Setting(GFModuleSdkContext(), GF_SETTING_MODULE, key);
+}
+
+void Put(const char* key, const QVariant& value) {
+ gf::sdk::SetSetting(GFModuleSdkContext(), GF_SETTING_MODULE, key, value);
}
/**
@@ -92,10 +96,7 @@ auto ResolveDefaultUrl(const QList<KeyServerEntry>& entries) -> QString {
QString stored;
{
QMutexLocker locker(settings_mutex());
- auto* settings = GlobalSettings();
- if (settings != nullptr) {
- stored = settings->value(kDefaultServerKey).toString();
- }
+ stored = Get(kDefaultServerKey).toString();
}
const auto listed = std::any_of(
@@ -155,18 +156,8 @@ namespace KeyServerList {
auto Load() -> QList<KeyServerEntry> {
QMutexLocker locker(settings_mutex());
- auto* settings = GlobalSettings();
- if (settings == nullptr) {
- LOG_ERROR("global settings unavailable, falling back to seed key servers");
- return SeedEntries();
- }
-
- // The host writes through its own short-lived QSettings objects, and this one
- // outlives them all; without a sync it would keep serving whatever it read at
- // startup.
- settings->sync();
-
- if (!settings->contains(kServersKey)) {
+ const auto stored = Get(kServersKey);
+ if (!stored.isValid()) {
locker.unlock();
auto seed = SeedEntries();
Store(seed, kOpenPGPServer);
@@ -174,7 +165,7 @@ auto Load() -> QList<KeyServerEntry> {
}
const auto document =
- QJsonDocument::fromJson(settings->value(kServersKey).toString().toUtf8());
+ QJsonDocument::fromJson(stored.toString().toUtf8());
QList<KeyServerEntry> entries;
if (document.isArray()) {
@@ -202,12 +193,6 @@ auto Load() -> QList<KeyServerEntry> {
void Store(const QList<KeyServerEntry>& entries, const QString& default_url) {
QMutexLocker locker(settings_mutex());
- auto* settings = GlobalSettings();
- if (settings == nullptr) {
- LOG_ERROR("global settings unavailable, key server list not stored");
- return;
- }
-
QJsonArray array;
for (const auto& entry : entries) {
if (entry.url.isEmpty()) continue;
@@ -225,15 +210,10 @@ void Store(const QList<KeyServerEntry>& entries, const QString& default_url) {
resolved_default = entries.isEmpty() ? QString() : entries.first().url;
}
- settings->setValue(kSchemaVersionKey, kSchemaVersion);
- settings->setValue(
- kServersKey,
- QString::fromUtf8(QJsonDocument(array).toJson(QJsonDocument::Compact)));
- settings->setValue(kDefaultServerKey, resolved_default);
-
- // Our QSettings is a different object from the host's, so without this the
- // host would keep reading the previous values.
- settings->sync();
+ Put(kSchemaVersionKey, kSchemaVersion);
+ Put(kServersKey, QString::fromUtf8(
+ QJsonDocument(array).toJson(QJsonDocument::Compact)));
+ Put(kDefaultServerKey, resolved_default);
}
auto DefaultUrl() -> QString { return ResolveDefaultUrl(Load()); }
diff --git a/src/m_key_server_sync/KeyServerSettingsPage.cpp b/src/m_key_server_sync/KeyServerSettingsPage.cpp
index 6ffe576..48c6494 100644
--- a/src/m_key_server_sync/KeyServerSettingsPage.cpp
+++ b/src/m_key_server_sync/KeyServerSettingsPage.cpp
@@ -87,17 +87,18 @@ KeyServerSettingsPage::KeyServerSettingsPage(QWidget* parent)
&KeyServerSettingsPage::slot_test_selected);
switch_ui_busy(false);
- SetSettings();
+ LoadSettings();
}
-void KeyServerSettingsPage::SetSettings() {
+void KeyServerSettingsPage::LoadSettings() {
entries_ = KeyServerList::Load();
default_url_ = KeyServerList::DefaultUrl();
refresh_table();
}
-void KeyServerSettingsPage::ApplySettings() {
+auto KeyServerSettingsPage::ApplySettings() -> bool {
KeyServerList::Store(entries_, default_url_);
+ return true;
}
void KeyServerSettingsPage::refresh_table() {
@@ -300,7 +301,3 @@ void KeyServerSettingsPage::switch_ui_busy(bool busy) {
ui_->testButton->setDisabled(busy);
}
-auto KeyServerSettingsPageFactory(void* data) -> void* {
- Q_UNUSED(data);
- return new KeyServerSettingsPage();
-}
diff --git a/src/m_key_server_sync/KeyServerSettingsPage.h b/src/m_key_server_sync/KeyServerSettingsPage.h
index 8c387b8..365c94b 100644
--- a/src/m_key_server_sync/KeyServerSettingsPage.h
+++ b/src/m_key_server_sync/KeyServerSettingsPage.h
@@ -28,6 +28,8 @@
#pragma once
+#include <GFModule.h>
+
#include <QWidget>
#include <memory>
@@ -39,30 +41,27 @@ class Ui_KeyServerSettingsPage;
/**
* @brief The Key Servers page of the application's Settings dialog.
*
- * Owned by this module, but hosted by the application: the dialog finds
- * SetSettings() and ApplySettings() by name, the same contract its built-in
- * pages follow, so edits here are staged and only written when the user
- * accepts.
+ * Owned by this module, in a page the application's Settings dialog owns:
+ * LoadSettings() and ApplySettings() are the typed SettingsWidget interface,
+ * so edits here are staged and only written when the user accepts.
*/
-class KeyServerSettingsPage : public QWidget {
+class KeyServerSettingsPage : public QWidget, public gf::ui::SettingsWidget {
Q_OBJECT
public:
explicit KeyServerSettingsPage(QWidget* parent = nullptr);
- public slots:
+ public:
/**
* @brief Load the stored servers, discarding anything staged.
*
- * Invoked by name from the host, both when the page is built and when the
- * user cancels out of a restart confirmation.
+ * Called by the Host both when the page is built and when the user
+ * cancels out of a restart confirmation.
*/
- void SetSettings();
+ void LoadSettings() override;
- /**
- * @brief Persist the staged servers. Invoked by name from the host on OK.
- */
- void ApplySettings();
+ /// Persist the staged servers. Called by the Host on OK.
+ auto ApplySettings() -> bool override;
private slots:
void slot_add();
@@ -84,19 +83,9 @@ class KeyServerSettingsPage : public QWidget {
std::shared_ptr<Ui_KeyServerSettingsPage> ui_;
/// Staged, not stored: the dialog's Cancel has to be able to throw all of
- /// this away, which it does by calling SetSettings() again.
+ /// this away, which it does by calling LoadSettings() again.
QList<KeyServerEntry> entries_;
QString default_url_;
int pending_probes_{0};
};
-/**
- * @brief Build a page for the host's Settings dialog.
- *
- * Matches QObjectFactory. A fresh page every call: the dialog is rebuilt each
- * time it is opened and takes ownership of what it is given.
- *
- * @param data unused
- * @return void* a new KeyServerSettingsPage
- */
-auto KeyServerSettingsPageFactory(void* data) -> void*;
diff --git a/src/m_key_server_sync/KeyServerSyncModule.cpp b/src/m_key_server_sync/KeyServerSyncModule.cpp
index dcc5827..50d3776 100644
--- a/src/m_key_server_sync/KeyServerSyncModule.cpp
+++ b/src/m_key_server_sync/KeyServerSyncModule.cpp
@@ -36,6 +36,7 @@
#include "GFModule.h"
#include "GFModuleIdentity.h"
+#include "GFSDKHostCommands.hpp"
#include "GFSDKUI.h"
#include "KeyServerList.h"
#include "KeyServerSettingsPage.h"
@@ -44,8 +45,31 @@
#include "VKSInterface.h"
namespace {
-constexpr auto kSettingsPageId =
- "com.bktus.gpgfrontend.module.key_server_sync.settings";
+
+using Severity = gf::cmd::host::AppMessage::Severity;
+
+/// A result for the user, shown by the Host from its own window: the module
+/// has no business holding one of the Host's windows to parent a box to.
+void Tell(Severity severity, const QString& title, const QString& text) {
+ Commands().Invoke<gf::cmd::host::AppMessage>({severity, title, text});
+}
+
+/// A fingerprint to open the search dialog with, handed from whoever asked
+/// to the widget factory the Host calls on the GUI thread.
+auto SearchPreset() -> QString& {
+ static QString preset;
+ return preset;
+}
+QMutex g_search_preset_mutex;
+
+void OpenSearch(const QString& fingerprint) {
+ {
+ QMutexLocker locker(&g_search_preset_mutex);
+ SearchPreset() = fingerprint;
+ }
+ Commands().Invoke<gf::cmd::host::ViewOpen>(
+ {gf::cmd::ViewRef{QStringLiteral(GF_MODULE_ID ".search")}});
+}
using KeyCallback = std::function<void(const QString&)>;
using ErrorCallback = std::function<void(const QString&, const QString&)>;
@@ -121,10 +145,10 @@ void FetchKey(const KeyServerList::Route& route, const QString& handle,
*
* @return bool false when the user declined
*/
-auto ConfirmHkpPublish(QWidget* parent, const QString& url) -> bool {
+auto ConfirmHkpPublish(const QString& url) -> bool {
const auto host = QUrl(url).host();
return QMessageBox::warning(
- parent,
+ nullptr,
QCoreApplication::translate("GTrC",
"Publish Without Verification?"),
QCoreApplication::translate(
@@ -144,30 +168,41 @@ auto ConfirmHkpPublish(QWidget* parent, const QString& url) -> bool {
auto OnActivate() -> GFResult {
LOG_INFO("key server sync module registering");
- // Registered untranslated: the module translators are not installed yet, so
- // anything translated here would be stuck at the source text for the rest of
- // the session. The host translates these when it builds the dialog.
- const auto keywords =
- QStringList{GC_TR("keyserver"), GC_TR("key server"), GC_TR("hkp"),
- GC_TR("vks"), GC_TR("publish"), GC_TR("search")}
- .join('\n');
- gf::sdk::RegisterSettingsPage(GFModuleSdkContext(), kSettingsPageId,
- "keys_engines", GC_TR("Key Servers"),
- keywords.toUtf8().constData(),
- KeyServerSettingsPageFactory, nullptr);
+ // Presentation is registered untranslated: the module translators are not
+ // installed yet, so anything translated here would be stuck at the source
+ // text for the rest of the session. The Host translates it when shown.
+ const bool search = gf::ui::RegisterNativeWidget<SearchKeyDialog>(
+ "search", {GC_TR("Key Server"), "", "", "", "", 0, 0},
+ [](const QCborMap& /*args*/) {
+ auto* dialog = new SearchKeyDialog();
+ QString preset;
+ {
+ QMutexLocker locker(&g_search_preset_mutex);
+ std::swap(preset, SearchPreset());
+ }
+ // An empty preset opens a blank search; only a fingerprint that was
+ // actually supplied (the verify-failure flow) seeds the field.
+ if (!preset.isEmpty()) dialog->SetPresetFingerprint(preset);
+ return dialog;
+ });
+ const bool settings = gf::ui::RegisterNativeWidget<KeyServerSettingsPage>(
+ "settings",
+ {GC_TR("Key Servers"),
+ GC_TR("keyserver,key server,hkp,vks,publish,search"), "", "", "", 0, 0},
+ [](const QCborMap& /*args*/) { return new KeyServerSettingsPage(); });
- return GFResult::Ok();
+ return search && settings
+ ? GFResult::Ok()
+ : GFResult::Fail("the key server widgets were not registered");
}
namespace {
-auto UploadKeyToServer(QWidget* parent, int channel, const QString& key_id)
- -> int {
+auto UploadKeyToServer(int channel, const QString& key_id) -> int {
const auto exported =
gf::sdk::ExportKey(GFModuleSdkContext(), channel, key_id, true);
if (exported.isEmpty()) {
- QMessageBox::critical(
- parent, QCoreApplication::translate("GTrC", "Key Upload Failed"),
+ Tell(Severity::kError, QCoreApplication::translate("GTrC", "Key Upload Failed"),
QCoreApplication::translate(
"GTrC",
"Failed to export the public key before uploading.\n"
@@ -182,18 +217,16 @@ auto UploadKeyToServer(QWidget* parent, int channel, const QString& key_id)
const auto server = route.url;
if (!route.vks) {
- if (!ConfirmHkpPublish(parent, server)) return 0;
+ if (!ConfirmHkpPublish(server)) return 0;
auto* pks = new PKSInterface();
QObject::connect(
pks, &PKSInterface::SignalKeyServerKeyUploadResult,
QThread::currentThread(),
- [parent, server, key_id](QNetworkReply::NetworkError error,
+ [server, key_id](QNetworkReply::NetworkError error,
const QString& error_string) {
if (error != QNetworkReply::NoError) {
- QMessageBox::critical(
- parent,
- QCoreApplication::translate("GTrC", "Key Upload Failed"),
+ Tell(Severity::kError, QCoreApplication::translate("GTrC", "Key Upload Failed"),
QCoreApplication::translate(
"GTrC",
"Failed to upload public key to the server.\n"
@@ -204,9 +237,7 @@ auto UploadKeyToServer(QWidget* parent, int channel, const QString& key_id)
}
// No verification mail follows an HKP upload, so do not promise one.
- QMessageBox::information(
- parent,
- QCoreApplication::translate("GTrC",
+ Tell(Severity::kInfo, QCoreApplication::translate("GTrC",
"Public Key Upload Successful"),
QCoreApplication::translate(
"GTrC",
@@ -225,7 +256,7 @@ auto UploadKeyToServer(QWidget* parent, int channel, const QString& key_id)
auto* vks = new VKSInterface(server);
QObject::connect(
vks, &VKSInterface::SignalKeyUploaded, QThread::currentThread(),
- [parent, server](const QString& fpr, const QJsonObject& status,
+ [server](const QString& fpr, const QJsonObject& status,
const QString& token) {
// Handle successful response
QString status_message = QCoreApplication::translate(
@@ -247,9 +278,7 @@ auto UploadKeyToServer(QWidget* parent, int channel, const QString& key_id)
const auto host = QUrl(server).host();
// Notify user of successful upload and status details
- QMessageBox::information(
- parent,
- QCoreApplication::translate("GTrC", "Public Key Upload Successful"),
+ Tell(Severity::kInfo, QCoreApplication::translate("GTrC", "Public Key Upload Successful"),
QCoreApplication::translate(
"GTrC",
"The public key was successfully uploaded to the "
@@ -263,9 +292,8 @@ auto UploadKeyToServer(QWidget* parent, int channel, const QString& key_id)
QObject::connect(
vks, &VKSInterface::SignalErrorOccurred, QThread::currentThread(),
- [parent, key_id](const QString& error, const QString& data) {
- QMessageBox::critical(
- parent, QCoreApplication::translate("GTrC", "Key Upload Failed"),
+ [key_id](const QString& error, const QString& data) {
+ Tell(Severity::kError, QCoreApplication::translate("GTrC", "Key Upload Failed"),
QCoreApplication::translate(
"GTrC",
"Failed to upload public key to the server.\n"
@@ -284,23 +312,21 @@ auto UploadKeyToServer(QWidget* parent, int channel, const QString& key_id)
return 0;
}
-auto UpdateKeyFromKeyServer(QWidget* parent, int channel, const QString& fpr)
- -> int {
+auto UpdateKeyFromKeyServer(int channel, const QString& fpr) -> int {
const auto route = KeyServerList::SyncRoute();
const auto host = QUrl(route.url).host();
FetchKey(
route, fpr, true,
- [parent, channel](const QString& key_data) {
- gf::sdk::ImportKeys(GFModuleSdkContext(), channel, parent,
+ [channel](const QString& key_data) {
+ gf::sdk::ImportKeys(GFModuleSdkContext(), channel, nullptr,
key_data.toUtf8());
},
- [parent, fpr, host](const QString& error, const QString& data) {
+ [fpr, host](const QString& error, const QString& data) {
Q_UNUSED(data);
// Name the server: it is the user's choice now, and a failure they
// cannot attribute to a host is one they cannot fix.
- QMessageBox::critical(
- parent, QCoreApplication::translate("GTrC", "Key Update Failed"),
+ Tell(Severity::kError, QCoreApplication::translate("GTrC", "Key Update Failed"),
QCoreApplication::translate(
"GTrC",
"Failed to retrieve public key from %3.\n"
@@ -313,120 +339,72 @@ auto UpdateKeyFromKeyServer(QWidget* parent, int channel, const QString& fpr)
} // namespace
-auto OnMainwindowMenuMounted(const GFEvent& event) -> GFEventResult {
- LOG_DEBUG("main window menu mounted event: processing");
-
- if (!event.Has("main_window")) {
- LOG_DEBUG("main window menu mounted event: no main_window found");
- return GFEventResult::Bad("no main_window found");
- }
+namespace {
- auto* main_window = GFUIObject<QMainWindow>(event.Str("main_window"));
- if (!main_window) {
- LOG_ERROR(
- "main window menu mounted: main_window handle invalid or not "
- "QMainWindow");
- return GFEventResult::Bad("main_window handle invalid or not QMainWindow");
+/// Both key commands act on one key, named by the Host's context.
+struct KeyArgs {
+ gf::cmd::KeyRef key;
+ static constexpr auto Fields() {
+ return std::make_tuple(gf::cmd::F("key", &KeyArgs::key));
}
+};
- if (!event.Has("import_key_menu")) {
- LOG_DEBUG("main window menu mounted event: no import_key_menu found");
- return GFEventResult::Bad("no import_key_menu found");
- }
+struct PublishKey {
+ static constexpr gf::cmd::Meta kMeta{
+ GF_MODULE_ID ".publish_key", GC_TR("Publish Public Key to Key Server"),
+ GC_TR("Upload the public key to the key server used for syncing"),
+ GC_TR("Key Server Operations"), 0, gf::cmd::kNeedsGuiThread};
+ using Args = KeyArgs;
+ using Result = gf::cmd::Unit;
+};
- auto* import_key_menu = GFUIObject<QMenu>(event.Str("import_key_menu"));
- if (!import_key_menu) {
- LOG_ERROR(
- "main window menu mounted: import_key_menu handle invalid or not "
- "QMenu");
- return GFEventResult::Bad("import_key_menu handle invalid or not QMenu");
- }
+struct RefreshKey {
+ static constexpr gf::cmd::Meta kMeta{
+ GF_MODULE_ID ".refresh_key", GC_TR("Refresh Public Key From Key Server"),
+ GC_TR("Import the latest copy of the public key from the key server"),
+ GC_TR("Key Server Operations"), 0, gf::cmd::kNeedsGuiThread};
+ using Args = KeyArgs;
+ using Result = gf::cmd::Unit;
+};
- LOG_DEBUG("adding key server sync actions to import key menu");
+struct SearchKey {
+ static constexpr gf::cmd::Meta kMeta{
+ GF_MODULE_ID ".search_key", GC_TR("Key Server"),
+ GC_TR("Import public keys from a trusted key server."), "", 0,
+ gf::cmd::kNeedsGuiThread};
+ struct Args {
+ std::optional<QString> fingerprint;
+ static constexpr auto Fields() {
+ return std::make_tuple(gf::cmd::F("fingerprint", &Args::fingerprint));
+ }
+ };
+ using Result = gf::cmd::Unit;
+};
- QMetaObject::invokeMethod(
- QApplication::instance(),
- [&]() -> void {
- QWidget* parent =
- qobject_cast<QWidget*>(static_cast<QObject*>(main_window));
- auto* action = new QAction(
- QCoreApplication::translate("GTrC", "Key Server"), parent);
- action->setToolTip(QCoreApplication::translate(
- "GTrC", "Import public keys from a trusted key server."));
- action->setIcon(QIcon(":/icons/import_key_from_server.png"));
- QObject::connect(action, &QAction::triggered, parent, [=]() {
- auto* dialog = new SearchKeyDialog(parent);
- dialog->show();
- });
- import_key_menu->addAction(action);
- },
- Qt::BlockingQueuedConnection);
- return GFEventResult::Ok();
+auto DoPublishKey(const gf::cmd::CommandContext& /*ctx*/, const KeyArgs& a)
+ -> gf::cmd::Outcome<gf::cmd::Unit> {
+ // Any key with a public part can be published, mirroring the Key
+ // Management "Publish Key to Keyserver" action.
+ UploadKeyToServer(static_cast<int>(a.key.channel), a.key.key_id);
+ return gf::cmd::Outcome<gf::cmd::Unit>::Success({});
}
-auto OnKeyPairOperaMenuCreated(const GFEvent& event) -> GFEventResult {
- auto* tab = GFUIObject<QWidget>(event.Str("tab"));
- if (!tab) {
- LOG_ERROR(
- "key pair opera menu created: tab handle "
- "invalid or not KeyPairOperaTab");
- return GFEventResult::Bad("tab handle invalid or not KeyPairOperaTab");
- }
-
- auto* layout = GFUIObject<QVBoxLayout>(event.Str("opera_layout"));
- if (!layout) {
- LOG_ERROR(
- "key pair opera menu created: opera_menu handle "
- "invalid or not QMenu");
- return GFEventResult::Bad("opera_menu handle invalid or not QMenu");
- }
-
- auto is_private_key = event.Str("is_private_key").toInt() != 0;
- auto has_master_key = event.Str("has_master_key").toInt() != 0;
-
- auto channel = event.Str("channel").toInt();
- auto key_id = event.Str("key_id");
- auto fpr = event.Str("fpr");
-
- FLOG_DEBUG(
- "adding key server sync actions: key id: %1, channel: %2, is "
- "private key: %3, has master key: %4",
- key_id, channel, static_cast<int>(is_private_key),
- static_cast<int>(has_master_key));
-
- QMetaObject::invokeMethod(QApplication::instance(), [=]() -> void {
- auto* menu = new QMenu(tab);
-
- auto* key_server_opera_button = new QPushButton(
- QCoreApplication::translate("GTrC", "Key Server Operations"));
- key_server_opera_button->setStyleSheet("text-align:center;");
- key_server_opera_button->setMenu(menu);
-
- // add upload / update key actions
- auto* upload_key_pair = new QAction(QCoreApplication::translate(
- "GTrC", "Publish Public Key to Key Server"));
- QObject::connect(upload_key_pair, &QAction::triggered, tab,
- [=]() { UploadKeyToServer(tab, channel, key_id); });
- // Any key with a public part can be published, mirroring the Key
- // Management "Publish Key to Keyserver" action.
-
- auto* update_key_pair = new QAction(QCoreApplication::translate(
- "GTrC", "Refresh Public Key From Key Server"));
- QObject::connect(update_key_pair, &QAction::triggered, tab,
- [=]() { UpdateKeyFromKeyServer(tab, channel, fpr); });
-
- // Refresh re-imports the latest public key from the server; it is
- // valid for any key, including your own.
-
- menu->addAction(upload_key_pair);
- menu->addAction(update_key_pair);
-
- layout->addWidget(key_server_opera_button);
- });
+auto DoRefreshKey(const gf::cmd::CommandContext& /*ctx*/, const KeyArgs& a)
+ -> gf::cmd::Outcome<gf::cmd::Unit> {
+ // Refresh re-imports the latest public key from the server; it is valid
+ // for any key, including your own.
+ UpdateKeyFromKeyServer(static_cast<int>(a.key.channel), a.key.fingerprint);
+ return gf::cmd::Outcome<gf::cmd::Unit>::Success({});
+}
- return GFEventResult::Ok();
+auto DoSearchKey(const gf::cmd::CommandContext& /*ctx*/,
+ const SearchKey::Args& a) -> gf::cmd::Outcome<gf::cmd::Unit> {
+ OpenSearch(a.fingerprint.value_or(QString()));
+ return gf::cmd::Outcome<gf::cmd::Unit>::Success({});
}
+} // namespace
+
auto OnRequestGetPublicKeyByFingerprint(const GFEvent& event) -> GFEventResult {
if (event.Str("fingerprint").isEmpty())
return GFEventResult::Bad("fingerprint is empty");
@@ -527,7 +505,11 @@ auto OnRequestUploadPublicKey(const GFEvent& event) -> GFEventResult {
{"key_server", server},
{"protocol", "vks"}});
});
- QObject::connect(vks, &VKSInterface::SignalKeyRetrieved, vks,
+ // Released on the signals an upload actually sends: it never retrieves a
+ // key, so connecting to that one left every upload task behind.
+ QObject::connect(vks, &VKSInterface::SignalKeyUploaded, vks,
+ &VKSInterface::deleteLater);
+ QObject::connect(vks, &VKSInterface::SignalErrorOccurred, vks,
&VKSInterface::deleteLater);
vks->UploadKey(key_text);
return GFEventResult::Deferred();
@@ -535,46 +517,13 @@ auto OnRequestUploadPublicKey(const GFEvent& event) -> GFEventResult {
auto OnRequestSearchPublicKeyByFingerprint(const GFEvent& event)
-> GFEventResult {
- auto fingerprint = event.Str("fingerprint").trimmed();
-
- QWidget* parent = nullptr;
-
- if (event.Has("parent")) {
- parent = GFUIObject<QWidget>(event.Str("parent"));
- }
-
- if (parent == nullptr) {
- parent = QApplication::activeWindow();
- }
-
+ const auto fingerprint = event.Str("fingerprint").trimmed();
FLOG_DEBUG("open key server search dialog with fingerprint: %1", fingerprint);
-
- QMetaObject::invokeMethod(
- QApplication::instance(),
- [parent, fingerprint]() {
- auto* dialog = new SearchKeyDialog(parent);
- // An empty preset opens a blank search dialog; only seed the field
- // when a fingerprint was actually supplied (verify-failure flow).
- if (!fingerprint.isEmpty()) {
- dialog->SetPresetFingerprint(fingerprint);
- }
- dialog->setAttribute(Qt::WA_DeleteOnClose);
- dialog->show();
- dialog->raise();
- dialog->activateWindow();
- },
- Qt::QueuedConnection);
-
+ // The module's own dialog, opened by the Host in a frame of its own.
+ OpenSearch(fingerprint);
return GFEventResult::Ok();
}
-auto OnDeactivate() -> GFResult {
- // The registry holds a function pointer into this shared object; leaving it
- // behind would crash the next time the Settings dialog is built.
- GFUIUnregisterSettingsPage(GFModuleSdkContext(), kSettingsPageId);
- return GFResult::Ok();
-}
-
auto OnUnload() -> void {
// Said "paper key module" until now, copied from a module that no longer
// exists -- so the one line naming who was shutting down named the wrong one.
@@ -582,27 +531,33 @@ auto OnUnload() -> void {
}
// The module's whole framework surface.
-constexpr GFEventBinding kEvents[] = {
- {"KEY_PAIR_OPERA_MENU_CREATED", &OnKeyPairOperaMenuCreated},
- {"MAINWINDOW_MENU_MOUNTED", &OnMainwindowMenuMounted},
+constexpr std::array<GFEventBinding, 4> kEvents = {{
{"REQUEST_GET_PUBLIC_KEY_BY_FINGERPRINT",
&OnRequestGetPublicKeyByFingerprint},
{"REQUEST_GET_PUBLIC_KEY_BY_KEY_ID", &OnRequestGetPublicKeyByKeyId},
{"REQUEST_SEARCH_PUBLIC_KEY_BY_FINGERPRINT",
&OnRequestSearchPublicKeyByFingerprint},
{"REQUEST_UPLOAD_PUBLIC_KEY", &OnRequestUploadPublicKey},
+}};
+
+const std::array<gf::cmd::Binding, 3> kCommands = {
+ gf::cmd::Bind<PublishKey, &DoPublishKey>(),
+ gf::cmd::Bind<RefreshKey, &DoRefreshKey>(),
+ gf::cmd::Bind<SearchKey, &DoSearchKey>(),
};
-constexpr GFModuleHooks kHooks = {
+const GFModuleHooks kHooks = {
sizeof(GFModuleHooks),
GF_MODULE_ID,
GF_MODULE_VERSION,
GF_MODULE_TRANSLATION_CONTEXT,
&OnActivate,
- &OnDeactivate,
+ nullptr, // the Host withdraws the commands, widgets and script itself
&OnUnload,
- kEvents,
- std::size(kEvents),
+ kEvents.data(),
+ kEvents.size(),
+ kCommands.data(),
+ kCommands.size(),
};
extern "C" GF_MODULE_EXPORT auto GFModuleGetApi(uint32_t abi)
diff --git a/src/m_key_server_sync/SearchKeyDialog.cpp b/src/m_key_server_sync/SearchKeyDialog.cpp
index 082963f..579edba 100644
--- a/src/m_key_server_sync/SearchKeyDialog.cpp
+++ b/src/m_key_server_sync/SearchKeyDialog.cpp
@@ -375,5 +375,5 @@ void SearchKeyDialog::slot_lookup_finished_pks(
return;
}
- gf::sdk::ImportKeys(GFModuleSdkContext(), channel, this, key_data);
+ gf::sdk::ImportKeys(GFModuleSdkContext(), channel, nullptr, key_data);
}
diff --git a/src/m_key_server_sync/SearchKeyDialog.h b/src/m_key_server_sync/SearchKeyDialog.h
index 30b8879..c7cd1c7 100644
--- a/src/m_key_server_sync/SearchKeyDialog.h
+++ b/src/m_key_server_sync/SearchKeyDialog.h
@@ -28,13 +28,15 @@
#pragma once
+#include <GFModule.h>
+
#include <QDialog>
#include "PKSInterface.h"
class Ui_SearchKeyDialog;
-class SearchKeyDialog : public QDialog {
+class SearchKeyDialog : public QDialog, public gf::ui::DialogWidget {
Q_OBJECT
public:
explicit SearchKeyDialog(QWidget* parent = nullptr);
diff --git a/src/m_key_server_sync/module.json b/src/m_key_server_sync/module.json
index 0f1b85c..c02aff7 100644
--- a/src/m_key_server_sync/module.json
+++ b/src/m_key_server_sync/module.json
@@ -13,12 +13,15 @@
"ui.custom"
],
"events": [
- "KEY_PAIR_OPERA_MENU_CREATED",
- "MAINWINDOW_MENU_MOUNTED",
"REQUEST_GET_PUBLIC_KEY_BY_FINGERPRINT",
"REQUEST_GET_PUBLIC_KEY_BY_KEY_ID",
"REQUEST_SEARCH_PUBLIC_KEY_BY_FINGERPRINT",
"REQUEST_UPLOAD_PUBLIC_KEY"
],
+ "commands": [
+ "com.bktus.gpgfrontend.module.key_server_sync.publish_key",
+ "com.bktus.gpgfrontend.module.key_server_sync.refresh_key",
+ "com.bktus.gpgfrontend.module.key_server_sync.search_key"
+ ],
"translation_context": "ModuleKeyServerSync"
}
diff --git a/src/m_key_server_sync/ui/main.lua b/src/m_key_server_sync/ui/main.lua
new file mode 100644
index 0000000..2ad2a01
--- /dev/null
+++ b/src/m_key_server_sync/ui/main.lua
@@ -0,0 +1,35 @@
+-- Key server sync: where it is offered.
+--
+-- The commands do the work and carry the titles; the search dialog and the
+-- settings page are the module's own widgets, in containers the Host owns.
+
+local search = commands.get("com.bktus.gpgfrontend.module.key_server_sync.search_key")
+local publish = commands.get("com.bktus.gpgfrontend.module.key_server_sync.publish_key")
+local refresh = commands.get("com.bktus.gpgfrontend.module.key_server_sync.refresh_key")
+
+ui.mount { id = "search", anchor = ui.anchor.dialog {}, widget = native.widget("search") }
+
+ui.mount {
+ id = "settings",
+ anchor = ui.anchor.settings { section = "keys_engines" },
+ widget = native.widget("settings"),
+}
+
+ui.action {
+ id = "search",
+ anchor = ui.anchor("main.menu.import_key"),
+ command = search,
+ icon = ":/icons/import_key_from_server.png",
+}
+
+-- Both act on the key the details dialog is showing. They share a category,
+-- so the Host groups them under one "Key Server Operations" button.
+local function for_this_key(ctx)
+ if not ctx.key then return { visible = false } end
+ return { args = { key = ctx.key } }
+end
+
+ui.action { id = "publish", anchor = ui.anchor("key.details.actions"),
+ command = publish, update = for_this_key }
+ui.action { id = "refresh", anchor = ui.anchor("key.details.actions"),
+ command = refresh, update = for_this_key }
diff --git a/src/m_pgp_inspect/CMakeLists.txt b/src/m_pgp_inspect/CMakeLists.txt
index 705ac23..e052bf4 100644
--- a/src/m_pgp_inspect/CMakeLists.txt
+++ b/src/m_pgp_inspect/CMakeLists.txt
@@ -34,7 +34,8 @@ gf_add_module(
QT Core Widgets
SOURCES PGPInspectModule.cpp
PGPInspectModel.cpp
- PGPInspectDialog.cpp)
+ PGPInspectDialog.cpp
+ LUA_SCRIPTS ui/main.lua)
if(GPGFRONTEND_MODULES_BUILD_TESTS)
add_subdirectory(test)
diff --git a/src/m_pgp_inspect/PGPInspectDialog.cpp b/src/m_pgp_inspect/PGPInspectDialog.cpp
index e3bde03..9dcab4e 100644
--- a/src/m_pgp_inspect/PGPInspectDialog.cpp
+++ b/src/m_pgp_inspect/PGPInspectDialog.cpp
@@ -170,12 +170,10 @@ void PGPInspectDialog::render_document() {
if (block.has_armor && block.armor.crc24 == QLatin1String("mismatch")) {
item->setForeground(
- kLabelColumn, QBrush(QColor(GFUIThemeColor(
- GFModuleSdkContext(), GF_UI_COLOR_DANGER, this))));
+ kLabelColumn, QBrush(QColor(GFUIThemeColorForRole(GFModuleSdkContext(), GF_UI_COLOR_DANGER))));
} else if (!block.error.isEmpty()) {
item->setForeground(
- kLabelColumn, QBrush(QColor(GFUIThemeColor(
- GFModuleSdkContext(), GF_UI_COLOR_WARNING, this))));
+ kLabelColumn, QBrush(QColor(GFUIThemeColorForRole(GFModuleSdkContext(), GF_UI_COLOR_WARNING))));
}
add_packets(item, block.packets);
@@ -205,8 +203,7 @@ void PGPInspectDialog::add_packets(QTreeWidgetItem* parent,
if (packet.Malformed()) {
item->setForeground(
- kLabelColumn, QBrush(QColor(GFUIThemeColor(
- GFModuleSdkContext(), GF_UI_COLOR_WARNING, this))));
+ kLabelColumn, QBrush(QColor(GFUIThemeColorForRole(GFModuleSdkContext(), GF_UI_COLOR_WARNING))));
}
auto fields = packet.fields;
@@ -228,8 +225,7 @@ void PGPInspectDialog::add_fields(QTreeWidgetItem* parent,
row->setText(kValueColumn, field.value);
row->setForeground(
kLabelColumn,
- QBrush(QColor(GFUIThemeColor(GFModuleSdkContext(),
- GF_UI_COLOR_MUTED_TEXT, this))));
+ QBrush(QColor(GFUIThemeColorForRole(GFModuleSdkContext(), GF_UI_COLOR_MUTED_TEXT))));
}
}
diff --git a/src/m_pgp_inspect/PGPInspectDialog.h b/src/m_pgp_inspect/PGPInspectDialog.h
index d84b7c9..93e4224 100644
--- a/src/m_pgp_inspect/PGPInspectDialog.h
+++ b/src/m_pgp_inspect/PGPInspectDialog.h
@@ -29,6 +29,8 @@
#pragma once
#include <QByteArray>
+#include <GFModule.h>
+
#include <QDialog>
#include "PGPInspectModel.h"
@@ -56,7 +58,7 @@ auto PGPInspectBytes(const QByteArray& data) -> PGPInspectDocument;
* Every rule about what the bytes mean lives in PGPInspectModel.h and, below
* that, in the Rust packet walk.
*/
-class PGPInspectDialog : public QDialog {
+class PGPInspectDialog : public QDialog, public gf::ui::DialogWidget {
Q_OBJECT
public:
diff --git a/src/m_pgp_inspect/PGPInspectModule.cpp b/src/m_pgp_inspect/PGPInspectModule.cpp
index 2130cda..5753b9a 100644
--- a/src/m_pgp_inspect/PGPInspectModule.cpp
+++ b/src/m_pgp_inspect/PGPInspectModule.cpp
@@ -27,12 +27,7 @@
*/
#include <GFModule.h>
-
-#include <QAction>
-#include <QApplication>
-#include <QMainWindow>
-#include <QMenu>
-#include <QPointer>
+#include <GFSDKHostCommands.hpp>
#include "GFModuleIdentity.h"
#include "PGPInspectDialog.h"
@@ -52,77 +47,73 @@ auto InspectCurrentTab() -> PGPInspectDocument {
return PGPInspectBytes(bytes);
}
-void RaiseInspectDialog(QWidget *parent) {
- const auto bytes = PGPInspectCurrentTabBytes();
- auto *dialog =
- new PGPInspectDialog(PGPInspectBytes(bytes), bytes.size(), parent);
- dialog->setModal(false);
- dialog->show();
-}
+/**
+ * @brief The module's one action: show the current tab's packet structure.
+ *
+ * Enabled only when the tab holds something to show -- decided when the
+ * menu opens, because what the current tab holds changes constantly and an
+ * entry enabled over plain text promises a window with nothing in it.
+ */
+struct OpenInspector {
+ static constexpr gf::cmd::Meta kMeta{
+ GF_MODULE_ID ".open_inspector",
+ GC_TR("Open OpenPGP Structure Inspector"),
+ GC_TR("Show the packet structure of the current tab"), "", 0,
+ gf::cmd::kNeedsGuiThread};
+ using Args = gf::cmd::Unit;
+ using Result = gf::cmd::Unit;
-} // namespace
+ static auto State(const gf::cmd::CommandContext& /*ctx*/) -> uint32_t {
+ return PGPInspectHasStructure(InspectCurrentTab())
+ ? GF_CMD_STATE_ENABLED | GF_CMD_STATE_VISIBLE
+ : GF_CMD_STATE_VISIBLE;
+ }
+};
-auto OnActivate() -> GFResult {
- // Nothing to register with the host: the module owns no settings page, no
- // tab view and no metatype. Its whole surface is one menu action, added
- // when the menu is mounted.
- return GFResult::Ok();
+auto DoOpenInspector(const gf::cmd::CommandContext& /*ctx*/,
+ const gf::cmd::Unit& /*args*/)
+ -> gf::cmd::Outcome<gf::cmd::Unit> {
+ // The dialog is the module's mount; the Host owns its frame and opens it.
+ Commands().Invoke<gf::cmd::host::ViewOpen>(
+ {gf::cmd::ViewRef{QStringLiteral(GF_MODULE_ID ".inspector")}});
+ return gf::cmd::Outcome<gf::cmd::Unit>::Success({});
}
-auto OnMainWindowMenuMounted(const GFEvent &event) -> GFEventResult {
- QMainWindow *main_window = nullptr;
- if (auto r = event.RequireGui("main_window", main_window); !r.ok) return r;
-
- QMenu *advance_menu = nullptr;
- if (auto r = event.RequireGui("advance_menu", advance_menu); !r.ok) return r;
-
- LOG_DEBUG("adding openpgp structure inspector to the advanced menu");
-
- QMetaObject::invokeMethod(
- QApplication::instance(),
- [=]() -> void {
- auto *action =
- new QAction(QCoreApplication::translate(
- "GTrC", "Open OpenPGP Structure Inspector"),
- nullptr);
- action->setToolTip(QCoreApplication::translate(
- "GTrC", "Show the packet structure of the current tab"));
- QObject::connect(action, &QAction::triggered, main_window,
- [=]() { RaiseInspectDialog(main_window); });
- advance_menu->addAction(action);
-
- // Decided when the menu opens rather than once at mount time: what
- // the current tab holds changes constantly, and an entry that is
- // enabled over plain text promises a window that would have nothing
- // in it.
- QPointer<QAction> guarded(action);
- QObject::connect(advance_menu, &QMenu::aboutToShow, action, [guarded] {
- if (guarded.isNull()) return;
- guarded->setEnabled(PGPInspectHasStructure(InspectCurrentTab()));
- });
- },
- Qt::BlockingQueuedConnection);
+} // namespace
- return GFEventResult::Ok();
+auto OnActivate() -> GFResult {
+ // The inspector, built from the current tab each time it is opened. Where
+ // it is offered is ui/main.lua's business.
+ const bool ok = gf::ui::RegisterNativeWidget<PGPInspectDialog>(
+ "inspector",
+ {GC_TR("OpenPGP Structure"), "", "", "", ":/icons/help.png", 820, 620},
+ [](const QCborMap& /*args*/) {
+ const auto bytes = PGPInspectCurrentTabBytes();
+ return new PGPInspectDialog(PGPInspectBytes(bytes), bytes.size());
+ });
+ return ok ? GFResult::Ok()
+ : GFResult::Fail("the inspector widget was not registered");
}
-// The module's whole framework surface: which events it handles, what runs at
-// each lifecycle point, and one forwarder to the runtime that implements all
-// of it. Everything above is business logic.
-constexpr GFEventBinding kEvents[] = {
- {"MAINWINDOW_MENU_MOUNTED", &OnMainWindowMenuMounted},
+// The module's whole framework surface: what it provides, what runs at each
+// lifecycle point, and one forwarder to the runtime that implements all of
+// it. Everything above is business logic.
+const std::array<gf::cmd::Binding, 1> kCommands = {
+ gf::cmd::Bind<OpenInspector, &DoOpenInspector>(),
};
-constexpr GFModuleHooks kHooks = {
+const GFModuleHooks kHooks = {
sizeof(GFModuleHooks),
GF_MODULE_ID,
GF_MODULE_VERSION,
GF_MODULE_TRANSLATION_CONTEXT,
&OnActivate,
- nullptr, // nothing registered with the host, so nothing to undo
+ nullptr, // the Host withdraws the command, widget and script itself
nullptr,
- kEvents,
- std::size(kEvents),
+ nullptr, // no events
+ 0,
+ kCommands.data(),
+ kCommands.size(),
};
extern "C" GF_MODULE_EXPORT auto GFModuleGetApi(uint32_t abi)
diff --git a/src/m_pgp_inspect/module.json b/src/m_pgp_inspect/module.json
index 8a52688..9cb6754 100644
--- a/src/m_pgp_inspect/module.json
+++ b/src/m_pgp_inspect/module.json
@@ -11,8 +11,9 @@
"ui",
"ui.custom"
],
- "events": [
- "MAINWINDOW_MENU_MOUNTED"
+ "events": [],
+ "commands": [
+ "com.bktus.gpgfrontend.module.pgp_inspect.open_inspector"
],
"translation_context": "ModulePGPInspect"
}
diff --git a/src/m_pgp_inspect/ui/main.lua b/src/m_pgp_inspect/ui/main.lua
new file mode 100644
index 0000000..e85975d
--- /dev/null
+++ b/src/m_pgp_inspect/ui/main.lua
@@ -0,0 +1,16 @@
+-- OpenPGP structure inspector: where it is offered.
+--
+-- What the entry does, what it is called and when it is enabled all belong
+-- to the module's command; this file only places it. The dialog is the
+-- module's own widget, in a frame the Host owns.
+
+local open = commands.get("com.bktus.gpgfrontend.module.pgp_inspect.open_inspector")
+
+ui.mount {
+ id = "inspector",
+ anchor = ui.anchor.dialog {},
+ widget = native.widget("inspector"),
+}
+
+ui.action { id = "menu", anchor = ui.anchor("main.menu.advanced"), command = open }
+ui.action { id = "context", anchor = ui.anchor("editor.context"), command = open }
diff --git a/src/m_ver_check/CMakeLists.txt b/src/m_ver_check/CMakeLists.txt
index b760e6e..fa3369d 100644
--- a/src/m_ver_check/CMakeLists.txt
+++ b/src/m_ver_check/CMakeLists.txt
@@ -28,4 +28,5 @@
gf_add_module(
NAME ver_check
QT Core Network Widgets Xml
- UI_DIR ui)
+ UI_DIR ui
+ LUA_SCRIPTS ui/main.lua)
diff --git a/src/m_ver_check/UpdateTab.cpp b/src/m_ver_check/UpdateTab.cpp
index 02727d0..575ff7b 100644
--- a/src/m_ver_check/UpdateTab.cpp
+++ b/src/m_ver_check/UpdateTab.cpp
@@ -205,15 +205,14 @@ void UpdateTab::slot_show_version_status() {
}
}
-auto UpdateTabFactory(void*) -> void* { return new UpdateTab(); }
-
void UpdateTab::slot_check_version_update() {
check_update_btn_->setEnabled(false);
pb_->show();
- auto api =
- gf::sdk::StateText(GFModuleSdkContext(), "ui",
- "settings.network.update_checking_api", "github");
+ // The one place the choice is stored; the settings page writes it.
+ auto api = gf::sdk::Setting(GFModuleSdkContext(), GF_SETTING_MODULE,
+ "update_checking_api", "github")
+ .toString();
if (api == "bktus") {
auto* task = new BKTUSVersionCheckTask();
diff --git a/src/m_ver_check/UpdateTab.h b/src/m_ver_check/UpdateTab.h
index 9c969de..55ba33d 100644
--- a/src/m_ver_check/UpdateTab.h
+++ b/src/m_ver_check/UpdateTab.h
@@ -86,5 +86,3 @@ class UpdateTab : public QWidget {
*/
void SignalReplyFromUpdateServer(QByteArray data);
};
-
-auto UpdateTabFactory(void* id) -> void*; \ No newline at end of file
diff --git a/src/m_ver_check/VersionCheckingModule.cpp b/src/m_ver_check/VersionCheckingModule.cpp
index 078df10..bb259a2 100644
--- a/src/m_ver_check/VersionCheckingModule.cpp
+++ b/src/m_ver_check/VersionCheckingModule.cpp
@@ -29,10 +29,18 @@
#include "VersionCheckingModule.h"
#include <GFSDKBuildInfo.h>
+#include <GFSDKHostCommands.hpp>
#include <GFSDKLog.h>
#include <GFSDKUI.h>
+#include <QButtonGroup>
+#include <QCheckBox>
+#include <QGroupBox>
+#include <QHBoxLayout>
+#include <QLabel>
#include <QMetaType>
+#include <QRadioButton>
+#include <QVBoxLayout>
#include <QtNetwork>
#include "BKTUSVersionCheckTask.h"
@@ -43,163 +51,171 @@
#include "UpdateTab.h"
#include "Utils.h"
-auto OnActivate() -> GFResult {
- LOG_INFO("version checking module activating");
- return GFResult::Ok();
+namespace {
+
+/// Whether to check at startup. The Host's own setting: the setup wizard
+/// asks it, and this module's settings page shows the same switch rather
+/// than a second one that could disagree.
+constexpr auto kProhibitKey = "network/prohibit_update_check";
+
+/// Which service to ask. The module's own setting.
+constexpr auto kApiKey = "update_checking_api";
+
+auto Api() -> QString {
+ return gf::sdk::Setting(GFModuleSdkContext(), GF_SETTING_MODULE, kApiKey,
+ "github")
+ .toString();
}
-namespace {
+void StoreResult(const SoftwareVersion& sv) {
+ gf::sdk::SetCacheText(GFModuleSdkContext(), GF_STORE_DURABLE,
+ "update_checking_cache",
+ (QJsonDocument(sv.ToJson()).toJson()).constData());
+}
-auto CheckUpdate(const GFEvent& event) -> GFEventResult {
- if (event.Str("api") == "bktus") {
+/// Ask the service; cache what it says for the next start. The task always
+/// reports, success or not, so it is always released.
+void CheckUpdate() {
+ if (Api() == "bktus") {
MLogInfo("checking updating using api of bktus.com");
auto* task = new BKTUSVersionCheckTask();
- QObject::connect(
- task, &BKTUSVersionCheckTask::SignalUpgradeVersion,
- QThread::currentThread(), [event](const SoftwareVersion& sv) {
- gf::sdk::SetCacheText(
- GFModuleSdkContext(), GF_STORE_DURABLE, "update_checking_cache",
- (QJsonDocument(sv.ToJson()).toJson()).constData());
- return GFEventResult::Ok();
- });
+ QObject::connect(task, &BKTUSVersionCheckTask::SignalUpgradeVersion,
+ QCoreApplication::instance(), &StoreResult);
QObject::connect(task, &BKTUSVersionCheckTask::SignalUpgradeVersion, task,
&QObject::deleteLater);
task->Run();
} else {
MLogInfo("checking updating using api of github.com");
auto* task = new GitHubVersionCheckTask();
- QObject::connect(
- task, &GitHubVersionCheckTask::SignalUpgradeVersion,
- QCoreApplication::instance(), [event](const SoftwareVersion& sv) {
- gf::sdk::SetCacheText(
- GFModuleSdkContext(), GF_STORE_DURABLE, "update_checking_cache",
- (QJsonDocument(sv.ToJson()).toJson()).constData());
- return GFEventResult::Ok();
- });
+ QObject::connect(task, &GitHubVersionCheckTask::SignalUpgradeVersion,
+ QCoreApplication::instance(), &StoreResult);
QObject::connect(task, &GitHubVersionCheckTask::SignalUpgradeVersion, task,
&QObject::deleteLater);
task->Run();
}
- // The check runs asynchronously and its callback answers, so nothing is
- // answered here.
- return GFEventResult::Deferred();
}
-auto RaiseUpdateDialog(QWidget* parent) -> QDialog* {
- auto* dialog = new QDialog(parent);
- dialog->setWindowTitle(
- QCoreApplication::translate("GTrC", "Check for Updates"));
- dialog->setModal(true);
- dialog->setAttribute(Qt::WA_DeleteOnClose);
- auto* layout = new QVBoxLayout();
- auto* update_tab = new UpdateTab(dialog);
- layout->addWidget(update_tab);
- dialog->setLayout(layout);
- dialog->resize(500, 600);
- dialog->show();
- return dialog;
+void OpenUpdateDialog() {
+ Commands().Invoke<gf::cmd::host::ViewOpen>(
+ {gf::cmd::ViewRef{QStringLiteral(GF_MODULE_ID ".update")}});
}
-} // namespace
-
-auto OnMainwindowMenuMounted(const GFEvent& event) -> GFEventResult {
- LOG_DEBUG("main window menu mounted event: processing");
-
- if (!event.Has("main_window")) {
- LOG_DEBUG("main window menu mounted event: no main_window found");
- return GFEventResult::Bad("no main_window found");
+/// The update dialog, in a frame the Host owns.
+class UpdateWidget : public QWidget, public gf::ui::DialogWidget {
+ public:
+ UpdateWidget() {
+ auto* layout = new QVBoxLayout(this);
+ layout->addWidget(new UpdateTab(this));
}
+};
- auto* main_window = GFUIObject<QMainWindow>(event.Str("main_window"));
- if (!main_window) {
- LOG_ERROR(
- "main window menu mounted: main_window handle invalid or not "
- "QMainWindow");
- return GFEventResult::Bad("main_window handle invalid or not QMainWindow");
- }
+/// Settings > Updates: whether to check at startup, and where.
+class UpdateSettingsWidget : public QWidget, public gf::ui::SettingsWidget {
+ public:
+ UpdateSettingsWidget() {
+ auto* box = new QGroupBox(
+ QCoreApplication::translate("GTrC", "Update Checking"), this);
+ auto* column = new QVBoxLayout(box);
- auto p_mw = QPointer<QMainWindow>(main_window);
+ check_ = new QCheckBox(
+ QCoreApplication::translate(
+ "GTrC", "Checking for version updates when the application "
+ "starts."),
+ box);
+ column->addWidget(check_);
- if (!event.Has("help_menu")) {
- LOG_DEBUG("main window menu mounted event: no help_menu found");
- return GFEventResult::Bad("no help_menu found");
+ auto* row = new QHBoxLayout();
+ row->addWidget(new QLabel(
+ QCoreApplication::translate("GTrC", "Update Checking API:"), box));
+ github_ = new QRadioButton(QCoreApplication::translate("GTrC", "GitHub"),
+ box);
+ bktus_ = new QRadioButton(QCoreApplication::translate("GTrC", "BKTUS.com"),
+ box);
+ auto* group = new QButtonGroup(this);
+ group->addButton(github_);
+ group->addButton(bktus_);
+ row->addWidget(github_);
+ row->addWidget(bktus_);
+ row->addStretch();
+ column->addLayout(row);
+
+ auto* layout = new QVBoxLayout(this);
+ layout->addWidget(box);
+ layout->addStretch();
}
- auto* help_menu = GFUIObject<QMenu>(event.Str("help_menu"));
- if (!help_menu) {
- LOG_ERROR(
- "main window menu mounted: help_menu handle invalid or not "
- "QMenu");
- return GFEventResult::Bad("help_menu handle invalid or not QMenu");
+ void LoadSettings() override {
+ check_->setChecked(!gf::sdk::Setting(GFModuleSdkContext(), GF_SETTING_HOST,
+ kProhibitKey, true)
+ .toBool());
+ const bool bktus = Api() == "bktus";
+ bktus_->setChecked(bktus);
+ github_->setChecked(!bktus);
}
- auto p_help_menu = QPointer<QMenu>(help_menu);
+ auto ApplySettings() -> bool override {
+ const bool host_ok =
+ gf::sdk::SetSetting(GFModuleSdkContext(), GF_SETTING_HOST,
+ kProhibitKey, !check_->isChecked());
+ const bool module_ok = gf::sdk::SetSetting(
+ GFModuleSdkContext(), GF_SETTING_MODULE, kApiKey,
+ bktus_->isChecked() ? QStringLiteral("bktus")
+ : QStringLiteral("github"));
+ return host_ok && module_ok;
+ }
- LOG_DEBUG("adding check update action to help menu");
+ private:
+ QCheckBox* check_;
+ QRadioButton* github_;
+ QRadioButton* bktus_;
+};
- QMetaObject::invokeMethod(
- QApplication::instance(),
- [&]() -> void {
- if (!p_mw || !p_help_menu) {
- LOG_ERROR(
- "main window menu mounted: main_window or help_menu was deleted "
- "before invoking the check update action");
- return;
- }
+/// Help > Check for Updates.
+struct CheckForUpdates {
+ static constexpr gf::cmd::Meta kMeta{
+ GF_MODULE_ID ".check_for_updates", GC_TR("Check for Updates"),
+ GC_TR("See whether a newer GpgFrontend is available"), "", 0,
+ gf::cmd::kNeedsGuiThread};
+ using Args = gf::cmd::Unit;
+ using Result = gf::cmd::Unit;
+};
- QWidget* parent =
- qobject_cast<QWidget*>(static_cast<QObject*>(main_window));
- auto* action = new QAction(
- QCoreApplication::translate("GTrC", "Check for Updates"), nullptr);
- action->setToolTip(QCoreApplication::translate(
- "GTrC", "Check for updates from the Internet."));
- action->setIcon(QIcon(":/icons/update.png"));
- QObject::connect(action, &QAction::triggered, parent,
- [=]() -> void { RaiseUpdateDialog(parent); });
- help_menu->addAction(action);
- },
- Qt::BlockingQueuedConnection);
- return GFEventResult::Ok();
+auto DoCheckForUpdates(const gf::cmd::CommandContext& /*ctx*/,
+ const gf::cmd::Unit& /*args*/)
+ -> gf::cmd::Outcome<gf::cmd::Unit> {
+ OpenUpdateDialog();
+ return gf::cmd::Outcome<gf::cmd::Unit>::Success({});
}
-auto OnApplicationLoaded(const GFEvent& event) -> GFEventResult {
- LOG_DEBUG("application starting completed event: processing");
+} // namespace
- auto* parent = GFUIObject<QWidget>(
- event.Has("main_window") ? event.Str("main_window") : "");
- if (!parent) {
- LOG_ERROR("application loaded: main_window handle invalid or not QWidget");
- return GFEventResult::Bad("main_window handle invalid or not QWidget");
- }
+auto OnActivate() -> GFResult {
+ LOG_INFO("version checking module activating");
+ const bool dialog = gf::ui::RegisterNativeWidget<UpdateWidget>(
+ "update", {GC_TR("Check for Updates"), "", "", "", "", 500, 600},
+ [](const QCborMap& /*args*/) { return new UpdateWidget(); });
+ const bool settings = gf::ui::RegisterNativeWidget<UpdateSettingsWidget>(
+ "settings",
+ {GC_TR("Updates"), GC_TR("update,version,check,github,bktus"), "", "",
+ "", 0, 0},
+ [](const QCborMap& /*args*/) { return new UpdateSettingsWidget(); });
+ return dialog && settings
+ ? GFResult::Ok()
+ : GFResult::Fail("the update widgets were not registered");
+}
- // check version information
- auto settings = qobject_cast<QSettings*>(
- static_cast<QObject*>(GFStorageSettingsRoot(GFModuleSdkContext())));
- if (!settings) {
- LOG_ERROR("application loaded: global settings handle invalid");
- return GFEventResult::Bad("global settings handle invalid");
- }
+auto OnApplicationLoaded(const GFEvent& /*event*/) -> GFEventResult {
+ LOG_DEBUG("application starting completed event: processing");
- // ensure that it will not perform at the first startup before wizard is done
- if (!settings->contains("network/prohibit_update_checking")) {
- LOG_DEBUG(
- "application loaded: prohibit_update_checking setting "
- "not found");
+ // Absent until the setup wizard has run: no check before the user has been
+ // asked whether to check at all.
+ const auto prohibited = gf::sdk::Setting(GFModuleSdkContext(),
+ GF_SETTING_HOST, kProhibitKey);
+ if (!prohibited.isValid()) {
+ LOG_DEBUG("application loaded: the setup wizard has not asked yet");
return GFEventResult::Ok();
}
-
- auto update_checking_api =
- settings->value("network/update_checking_api", "github").toString();
- FLOG_DEBUG("application loaded: update checking api: %1",
- update_checking_api);
-
- // we only check for update if the user did set the option to allow it
- auto prohibit_update_checking =
- settings->value("network/prohibit_update_checking", true).toBool();
- FLOG_DEBUG("application loaded: prohibit update checking: %1",
- prohibit_update_checking);
-
- if (prohibit_update_checking) {
+ if (prohibited.toBool()) {
LOG_DEBUG("application loaded: update checking is prohibited");
return GFEventResult::Ok();
}
@@ -209,10 +225,9 @@ auto OnApplicationLoaded(const GFEvent& event) -> GFEventResult {
auto json = QJsonDocument::fromJson(cache.toUtf8());
if (json.isEmpty() || !json.isObject()) {
- LOG_DEBUG(
- "application loaded: no valid cached version info found, "
- "checking update");
- return CheckUpdate(event);
+ LOG_DEBUG("application loaded: nothing cached, checking now");
+ CheckUpdate();
+ return GFEventResult::Ok();
}
SoftwareVersion sv;
@@ -220,7 +235,8 @@ auto OnApplicationLoaded(const GFEvent& event) -> GFEventResult {
FLOG_DEBUG("got software version meta data: %1", json.toJson());
if (sv.timestamp.addDays(1) < QDateTime::currentDateTime()) {
- return CheckUpdate(event);
+ CheckUpdate();
+ return GFEventResult::Ok();
}
FillGrtWithVersionInfo(sv);
@@ -229,235 +245,36 @@ auto OnApplicationLoaded(const GFEvent& event) -> GFEventResult {
!sv.current_commit_hash_publish_in_remote) {
LOG_INFO(
"software version is outdated or not fully released, notifying user");
-
- QMetaObject::invokeMethod(
- QApplication::instance(),
- [=]() {
- auto* dialog = RaiseUpdateDialog(parent);
- Q_UNUSED(dialog);
- },
- Qt::QueuedConnection);
+ OpenUpdateDialog();
}
- return GFEventResult::Ok();
-}
-
-auto OnNetworkSettingsTabUiCreated(const GFEvent& event) -> GFEventResult {
- LOG_DEBUG("network settings tab ui created event: processing");
-
- auto* tab = GFUIObject<QWidget>(event.Has("network_settings_tab")
- ? event.Str("network_settings_tab")
- : "");
- if (!tab) {
- LOG_ERROR(
- "network settings tab ui created: network_settings_tab handle "
- "invalid or not QWidget");
- return GFEventResult::Bad(
- "network_settings_tab handle invalid or not QWidget");
- }
-
- auto* capability_group_box = GFUIObject<QGroupBox>(
- event.Has("capability_group_box") ? event.Str("capability_group_box")
- : "");
-
- if (!capability_group_box) {
- LOG_ERROR(
- "network settings tab ui created: capability_group_box handle "
- "invalid or not QGroupBox");
- return GFEventResult::Bad(
- "capability_group_box handle invalid or not QGroupBox");
- }
-
- QMetaObject::invokeMethod(
- QApplication::instance(),
- [=]() {
- auto* update_checking_check_box = new QCheckBox(
- QCoreApplication::translate("GTrC",
- "Checking for version updates when the "
- "application starts."),
- capability_group_box);
- update_checking_check_box->setObjectName("update_checking_check_box");
-
- capability_group_box->layout()->addWidget(update_checking_check_box);
-
- auto* github_radio_button =
- new QRadioButton(QCoreApplication::translate("GTrC", "GitHub"),
- capability_group_box);
- auto* bktus_radio_button =
- new QRadioButton(QCoreApplication::translate("GTrC", "BKTUS.com"),
- capability_group_box);
-
- auto layout = new QHBoxLayout();
- layout->addWidget(new QLabel(
- QCoreApplication::translate("GTrC", "Update Checking API:"),
- capability_group_box));
- layout->addWidget(github_radio_button);
- layout->addWidget(bktus_radio_button);
-
- capability_group_box->layout()->addItem(layout);
-
- auto* update_api_group = new QButtonGroup(tab);
- update_api_group->setObjectName("update_api_group");
- update_api_group->addButton(github_radio_button);
- update_api_group->addButton(bktus_radio_button);
- },
- Qt::QueuedConnection);
-
- return GFEventResult::Ok();
-}
-
-auto OnNetworkSettingsTabApplySettings(const GFEvent& event) -> GFEventResult {
- LOG_DEBUG("network settings tab apply settings event: processing");
- auto* settings = qobject_cast<QSettings*>(
- static_cast<QObject*>(GFStorageSettingsRoot(GFModuleSdkContext())));
- if (!settings) {
- LOG_ERROR(
- "network settings tab apply settings: global settings handle "
- "invalid");
- return GFEventResult::Bad("global settings handle invalid");
- }
- auto* tab = GFUIObject<QWidget>(event.Has("network_settings_tab")
- ? event.Str("network_settings_tab")
- : "");
- if (!tab) {
- LOG_ERROR(
- "network settings tab apply settings: network_settings_tab "
- "handle invalid or not QWidget");
- return GFEventResult::Bad(
- "network_settings_tab handle invalid or not QWidget");
- }
-
- // we need to apply the settings in the main thread to avoid some
- // potential racing conditions
- auto p_tab = QPointer<QWidget>(tab);
-
- QMetaObject::invokeMethod(
- QApplication::instance(),
- [=]() {
- if (!p_tab) {
- LOG_ERROR(
- "network settings tab apply settings: network_settings_tab "
- "already deleted when applying settings");
- return;
- }
- auto* update_checking_check_box =
- p_tab->findChild<QCheckBox*>("update_checking_check_box");
- if (update_checking_check_box) {
- settings->setValue(
- "network/version_checking/"
- "check_for_updates_on_startup",
- update_checking_check_box->isChecked());
- }
-
- auto* update_api_group =
- tab->findChild<QButtonGroup*>("update_api_group");
- if (update_api_group) {
- QString api = "github";
- if (update_api_group->buttons().at(1)->isChecked()) {
- api = "bktus";
- }
- settings->setValue("network/version_checking/update_checking_api",
- api);
- }
-
- FLOG_DEBUG(
- "network settings tab apply settings: version checking "
- "settings applied, "
- "check for updates on startup: %1, update checking api: %2",
- update_checking_check_box
- ? QString::number(update_checking_check_box->isChecked())
- : "null",
- update_api_group
- ? (update_api_group->buttons().at(1)->isChecked() ? "bktus"
- : "github")
- : "null");
- },
- Qt::QueuedConnection);
-
- return GFEventResult::Ok();
-}
-
-auto OnNetworkSettingsTabLoadSettings(const GFEvent& event) -> GFEventResult {
- LOG_DEBUG("network settings tab load settings event: processing");
-
- auto* settings = qobject_cast<QSettings*>(
- static_cast<QObject*>(GFStorageSettingsRoot(GFModuleSdkContext())));
-
- if (!settings) {
- LOG_ERROR(
- "network settings tab load settings: global settings handle "
- "invalid");
- return GFEventResult::Bad("global settings handle invalid");
- }
-
- auto* tab = GFUIObject<QWidget>(event.Has("network_settings_tab")
- ? event.Str("network_settings_tab")
- : "");
- if (!tab) {
- LOG_ERROR(
- "network settings tab load settings: network_settings_tab "
- "handle invalid or not QWidget");
- return GFEventResult::Bad(
- "network_settings_tab handle invalid or not QWidget");
- }
-
- QMetaObject::invokeMethod(
- QApplication::instance(),
- [=]() {
- auto* update_checking_check_box =
- tab->findChild<QCheckBox*>("update_checking_check_box");
- if (update_checking_check_box) {
- auto check_for_updates_on_startup =
- settings
- ->value(
- "network/version_checking/"
- "check_for_updates_on_startup",
- false)
- .toBool();
- update_checking_check_box->setChecked(check_for_updates_on_startup);
- }
-
- auto* update_api_group =
- tab->findChild<QButtonGroup*>("update_api_group");
- if (update_api_group) {
- auto update_checking_api =
- settings
- ->value("network/version_checking/update_checking_api",
- "github")
- .toString();
- if (update_checking_api == "github") {
- update_api_group->buttons().at(0)->setChecked(true);
- } else {
- update_api_group->buttons().at(1)->setChecked(true);
- }
- }
- },
- Qt::BlockingQueuedConnection);
-
+ // An observation, answered at once: the check runs on its own.
return GFEventResult::Ok();
}
auto OnUnload() -> void { LOG_INFO("version checking module unregistering"); }
// The module's whole framework surface.
-constexpr GFEventBinding kEvents[] = {
+constexpr std::array<GFEventBinding, 1> kEvents = {{
{"APPLICATION_LOADED", &OnApplicationLoaded},
- {"MAINWINDOW_MENU_MOUNTED", &OnMainwindowMenuMounted},
- {"NETWORK_SETTINGS_TAB_APPLY_SETTINGS", &OnNetworkSettingsTabApplySettings},
- {"NETWORK_SETTINGS_TAB_LOAD_SETTINGS", &OnNetworkSettingsTabLoadSettings},
- {"NETWORK_SETTINGS_TAB_UI_CREATED", &OnNetworkSettingsTabUiCreated},
+}};
+
+const std::array<gf::cmd::Binding, 1> kCommands = {
+ gf::cmd::Bind<CheckForUpdates, &DoCheckForUpdates>(),
};
-constexpr GFModuleHooks kHooks = {
+const GFModuleHooks kHooks = {
sizeof(GFModuleHooks),
GF_MODULE_ID,
GF_MODULE_VERSION,
GF_MODULE_TRANSLATION_CONTEXT,
&OnActivate,
- nullptr, // the settings tab is built through events, so nothing to undo
+ nullptr, // the Host withdraws the command, widgets and script itself
&OnUnload,
- kEvents,
- std::size(kEvents),
+ kEvents.data(),
+ kEvents.size(),
+ kCommands.data(),
+ kCommands.size(),
};
extern "C" GF_MODULE_EXPORT auto GFModuleGetApi(uint32_t abi)
diff --git a/src/m_ver_check/module.json b/src/m_ver_check/module.json
index 77f3980..7c37190 100644
--- a/src/m_ver_check/module.json
+++ b/src/m_ver_check/module.json
@@ -12,11 +12,10 @@
"ui.custom"
],
"events": [
- "APPLICATION_LOADED",
- "MAINWINDOW_MENU_MOUNTED",
- "NETWORK_SETTINGS_TAB_APPLY_SETTINGS",
- "NETWORK_SETTINGS_TAB_LOAD_SETTINGS",
- "NETWORK_SETTINGS_TAB_UI_CREATED"
+ "APPLICATION_LOADED"
+ ],
+ "commands": [
+ "com.bktus.gpgfrontend.module.version_checking.check_for_updates"
],
"translation_context": "ModuleVersionChecking"
}
diff --git a/src/m_ver_check/ui/main.lua b/src/m_ver_check/ui/main.lua
new file mode 100644
index 0000000..ba9ee48
--- /dev/null
+++ b/src/m_ver_check/ui/main.lua
@@ -0,0 +1,14 @@
+-- Version checking: where it is offered. The dialog and the settings page
+-- are the module's own widgets, in containers the Host owns.
+
+local check = commands.get("com.bktus.gpgfrontend.module.version_checking.check_for_updates")
+
+ui.mount { id = "update", anchor = ui.anchor.dialog {}, widget = native.widget("update") }
+
+ui.mount {
+ id = "settings",
+ anchor = ui.anchor.settings { section = "application" },
+ widget = native.widget("settings"),
+}
+
+ui.action { id = "help", anchor = ui.anchor("main.menu.help"), command = check }