aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2026-09-19 11:57:16 +0200
committersaturneric <[email protected]>2026-09-19 11:57:16 +0200
commitcf2eebe9eea1cad1d12dd789fea13d511a63b4b0 (patch)
treee25c9cf4f21a4c0d11d7b4be3888bd60b6fed52b /include
parentfeat(modules): package modules as signed gfmodule files (diff)
downloadmodules-cf2eebe9eea1cad1d12dd789fea13d511a63b4b0.tar.gz
modules-cf2eebe9eea1cad1d12dd789fea13d511a63b4b0.zip
refactor(module): centralize module headers and identity
- split common utilities into focused logging, event, i18n, memory, and conversion headers - add GFModule.h as the unified module include - derive bootstrap identity and translation context from generated metadata - replace parameterized bootstrap macros with GF_MODULE_BOOTSTRAP() - retain deprecated compatibility headers and allocation helper names - remove the obsolete GFModuleDeclare.h interface - correct non-debug logging macros to use their intended levels
Diffstat (limited to 'include')
-rw-r--r--include/GFModule.h65
-rw-r--r--include/GFModuleBootstrap.h92
-rw-r--r--include/GFModuleCommonUtils.hpp522
-rw-r--r--include/GFModuleConvert.h216
-rw-r--r--include/GFModuleDeclare.h48
-rw-r--r--include/GFModuleDefine.h13
-rw-r--r--include/GFModuleEvent.h236
-rw-r--r--include/GFModuleI18n.h101
-rw-r--r--include/GFModuleLog.h77
-rw-r--r--include/GFModuleMemory.h213
10 files changed, 947 insertions, 636 deletions
diff --git a/include/GFModule.h b/include/GFModule.h
new file mode 100644
index 0000000..740ed81
--- /dev/null
+++ b/include/GFModule.h
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+/**
+ * @file GFModule.h
+ * @brief The one header a module includes.
+ *
+ * Everything a module needs, grouped by what it is for. Include this; the
+ * individual headers are there for when you want to see what is in each area,
+ * not because you have to pick.
+ *
+ * | Header | What lives there |
+ * | ----------------- | ------------------------------------------------ |
+ * | GFModuleLog.h | `LOG_*`, `FLOG_*`, `MLog*` |
+ * | GFModuleEvent.h | `LISTEN`, `CB_*`, handlers, event conversion |
+ * | GFModuleI18n.h | translation context and `.qm` reader |
+ * | GFModuleMemory.h | who frees what: `DUP`/`UDUP`, SDK allocation |
+ * | GFModuleConvert.h | QString/QMap <-> C, `FormatString` |
+ *
+ * Deliberately NOT here: `GFModuleBootstrap.h`, and through it the generated
+ * `GFModuleIdentity.h`. Exactly one translation unit per module bootstraps, so
+ * only that one includes it. Everything else -- including the sources a
+ * module's own gtest target compiles without the SDK -- then builds without
+ * needing anything generated.
+ *
+ * A module declares itself in `module.json` beside its `CMakeLists.txt`, and
+ * `gf_add_module()` turns that one file into the generated identity, the
+ * translation wiring and the signed package. Nothing about a module's identity
+ * is written twice.
+ */
+
+#include "GFModuleConvert.h"
+#include "GFModuleEvent.h"
+#include "GFModuleExport.h"
+#include "GFModuleI18n.h"
+#include "GFModuleLog.h"
+#include "GFModuleMemory.h"
+#include "GFSDKBuildInfo.h"
diff --git a/include/GFModuleBootstrap.h b/include/GFModuleBootstrap.h
index 7e9b650..1e0eb8c 100644
--- a/include/GFModuleBootstrap.h
+++ b/include/GFModuleBootstrap.h
@@ -31,6 +31,7 @@
#include <GFSDKModuleApi.h>
#include "GFModuleExport.h"
+#include "GFModuleIdentity.h"
#include "GFSDKBuildInfo.h"
/**
@@ -47,10 +48,12 @@
* null), and what lets a test supply its own table instead of compiling a
* parallel set of stub symbols that drift from the real SDK.
*
- * USAGE, once per module:
+ * USAGE, once per module, with no arguments at all:
*
- * GF_MODULE_BOOTSTRAP("com.example.module", "1.0.0",
- * MyActivate, MyExecute, MyDeactivate, MyUnregister)
+ * GF_MODULE_BOOTSTRAP()
+ *
+ * Identity comes from the generated GFModuleIdentity.h, which is built from
+ * the module's own module.json.
*
* The host api pointer is stashed in GFHost() for the module's own code to
* reach without threading it through every function. It is valid from the
@@ -67,80 +70,42 @@ inline const GFHostApi*& GFHostApiSlot() {
inline auto GFHost() -> const GFHostApi* { return GFHostApiSlot(); }
/**
- * @brief Define the module's bootstrap symbol and lifecycle table.
+ * @brief Define this module's bootstrap symbol, lifecycle table and identity.
*
- * @param id module identifier, e.g. "com.example.module"
- * @param ver module version, e.g. "1.0.0"
- * @param fn_act int(const GFHostApi*, void*) -- setup; host api is borrowed
- * @param fn_exe int(GFModuleEvent*) -- handle one event
- * @param fn_dea int(void) -- cancel work, drop registrations
- * @param fn_unr void(void) -- final teardown
- */
-#define GF_MODULE_BOOTSTRAP(id, ver, fn_act, fn_exe, fn_dea, fn_unr) \
- static int GFBootstrapActivate(const GFHostApi* host, void* reserved) { \
- GFHostApiSlot() = host; \
- return (fn_act)(host, reserved); \
- } \
- extern "C" GF_MODULE_EXPORT const GFModuleApi* GFModuleGetApi( \
- uint32_t host_abi) { \
- /* Decline a host outside the range this module was built for, rather */\
- /* than loading and failing on the first mismatched call. */ \
- if (host_abi < GF_SDK_ABI_MIN_SUPPORTED || \
- host_abi > GF_SDK_ABI_VERSION) { \
- return nullptr; \
- } \
- /* Static: the host borrows this table and never frees it. */ \
- static const GFModuleApi kApi = { \
- sizeof(GFModuleApi), GF_SDK_ABI_VERSION, (id), (ver), \
- &GFBootstrapActivate, (fn_exe), (fn_dea), (fn_unr), \
- }; \
- return &kApi; \
- }
-
-
-/**
- * @brief Bootstrap a module that already has the classic lifecycle functions.
+ * Takes no arguments. Everything it needs comes from `GFModuleIdentity.h`,
+ * which `gf_add_module()` generates from the module's `module.json` -- so the
+ * identifier and version the host cross-checks against the signed manifest
+ * cannot disagree with it, because there is only one copy.
*
- * Adapts GFRegisterModule / GFActiveModule / GFExecuteModule /
- * GFDeactivateModule / GFUnregisterModule onto the table, so porting a module
- * is a one-line change at the top of the file rather than a rewrite of its
- * entry points. It also keeps GFGetModuleID() and friends defined, because
- * module code calls GFGetModuleID() constantly (LISTEN, CB, the translator
- * reader) and those calls should not have to change.
+ * It replaces `GF_MODULE_BOOTSTRAP_V2(id, name, ver, desc, author)`, whose
+ * five arguments were the C++ half of the same five values the CMake call
+ * repeated. Three of them -- name, description, author -- fed only
+ * `GFGetModuleMetaData()`, which had no callers at all: the host reads that
+ * metadata from the verified package manifest instead. So they are simply
+ * gone, rather than moved.
+ *
+ * The module still writes its lifecycle functions under their classic names --
+ * GFRegisterModule, GFActiveModule, GFExecuteModule, GFDeactivateModule,
+ * GFUnregisterModule -- and this adapts them onto the table.
*
* Note the ORDER inside activate: the host's table has no separate register
* step, so whatever the module did in GFRegisterModule -- typically
* registering its translator -- has to run here, before GFActiveModule starts
* subscribing to events.
*/
-#define GF_MODULE_BOOTSTRAP_V2(id, name, ver, desc, author) \
+#define GF_MODULE_BOOTSTRAP() \
/* Identity strings are BORROWED statics, not fresh allocations. */\
/* They used to be DUP(...)ed on every call because the SDK entry points */\
/* they were passed to freed their arguments. Now that arguments are */\
/* borrowed, allocating here would simply leak -- and GFGetModuleID() is */\
/* called on the order of seventy times across the modules. */\
- auto GFGetModuleGFSDKVersion() -> const char* { \
- return GF_SDK_VERSION_STR; \
- } \
- auto GFGetModuleGFSDKABIVersion() -> int { return GF_SDK_ABI_VERSION; } \
- auto GFGetModuleQtEnvVersion() -> const char* { return QT_VERSION_STR; } \
- auto GFGetModuleID() -> const char* { return (id); } \
- auto GFGetModuleVersion() -> const char* { return (ver); } \
- auto GFGetModuleMetaData() -> GFModuleMetaData* { \
- return QMapToGFModuleMetaDataList( \
- {{"Name", (name)}, {"Description", (desc)}, {"Author", (author)}}); \
- } \
+ auto GFGetModuleID() -> const char* { return GF_MODULE_ID; } \
using MEvent = QMap<QString, QString>; \
using EventHandler = std::function<int(const MEvent&)>; \
namespace { \
- /* NOTE: `Module##nameEventHandlers` pastes onto the IDENTIFIER */\
- /* `nameEventHandlers`, so it does not substitute the `name` parameter */\
- /* at all -- it always yields ModulenameEventHandlers. That is inherited */\
- /* from GF_MODULE_API_DEFINE_V2 and kept deliberately: `name` is a string*/\
- /* literal here, and actually pasting it would not compile. */\
- static QMap<QString, EventHandler> Module##nameEventHandlers; \
+ static QMap<QString, EventHandler> gModuleEventHandlers; \
static QMap<QString, EventHandler>& _gr_module_event_handlers = \
- Module##nameEventHandlers; \
+ gModuleEventHandlers; \
} \
DEFINE_EXECUTE_API_USING_STANDARD_EVEN_HANDLE_MODEL \
static int GFBootstrapActivate(const GFHostApi* host, void*) { \
@@ -152,15 +117,18 @@ inline auto GFHost() -> const GFHostApi* { return GFHostApiSlot(); }
static void GFBootstrapUnregister() { (void)GFUnregisterModule(); } \
extern "C" GF_MODULE_EXPORT const GFModuleApi* GFModuleGetApi( \
uint32_t host_abi) { \
+ /* Decline a host outside the range this module was built for, rather */\
+ /* than loading and failing on the first mismatched call. */ \
if (host_abi < GF_SDK_ABI_MIN_SUPPORTED || \
host_abi > GF_SDK_ABI_VERSION) { \
return nullptr; \
} \
+ /* Static: the host borrows this table and never frees it. */ \
static const GFModuleApi kApi = { \
sizeof(GFModuleApi), \
GF_SDK_ABI_VERSION, \
- (id), \
- (ver), \
+ GF_MODULE_ID, \
+ GF_MODULE_VERSION, \
&GFBootstrapActivate, \
&GFExecuteModule, \
&GFDeactivateModule, \
diff --git a/include/GFModuleCommonUtils.hpp b/include/GFModuleCommonUtils.hpp
index 8a4c1a7..ca224fa 100644
--- a/include/GFModuleCommonUtils.hpp
+++ b/include/GFModuleCommonUtils.hpp
@@ -28,522 +28,10 @@
#pragma once
-#include <GFSDKBasic.h>
-#include <GFSDKLog.h>
-#include <GFSDKModule.h>
-#include <GFSDKUI.h>
-#include <GFSDKUIModel.h>
-
-#include <QByteArray>
-#include <QMap>
-#include <QSharedPointer>
-#include <QString>
-#include <QVariant>
-#include <cstring>
-
-// OWNERSHIP, in one place.
-//
-// SDK ARGUMENTS ARE BORROWED. Pass a pointer straight through --
-// `s.toUtf8().constData()` for a QString -- and keep owning it. Do NOT
-// pre-allocate one to hand over: nothing on the other side will free it.
-// That is what DUP()/QDUP() used to be for, and why they are gone.
+// DEPRECATED. This header's contents were split by concern into GFModule.h and
+// the five headers it pulls in. It remains for one cycle so that a module can
+// be ported without being rewritten in the same commit.
//
-// The one exception, and it is a struct rather than an argument: the few
-// aggregates a module builds and hands over whole (GFModuleEvent,
-// GFModuleEventParam, GFModuleMetaData, GFCommandExecuteContext) still
-// transfer, so their char* members are still allocated with GFModuleStrDup.
-//
-// SDK RETURN VALUES ARE OWNED. Reclaim them, which is what the UDUP family
-// is for -- that half of the old convention was always right.
-#define DUP(v) GFModuleStrDup(v)
-#define SECDUP(v) GFModuleSecStrDup(v)
-#define UDUP(v) UnStrDup(v)
-#define USECDUP(v) UnSecStrDup(v)
-#define UDUPN(v, n) UnBytesDup(v, n)
-
-// No DUP: SDK arguments are borrowed now, so pre-allocating here would leak.
-#define LISTEN(event) GFModuleListenEvent(GFGetModuleID(), (event))
-
-#define DEFINE_TRANSLATIONS_STRUCTURE(name) \
- class GTrC { \
- Q_DECLARE_TR_FUNCTIONS(GTrC) \
- }; \
- auto TranslatorDataReader(const char* p_l, char** p_d)->int { \
- auto locale = QString::fromUtf8(p_l == nullptr ? "" : p_l); \
- QFile f(QString(":/i18n/%2.%1.qm").arg(locale).arg(#name)); \
- if (f.exists() && f.open(QIODevice::ReadOnly)) { \
- auto b = f.readAll(); \
- *p_d = AllocBufferAndCopy(b); \
- return b.size(); \
- } \
- FLOG_WARN("%3 loading, locale: %1, not found", locale, f.fileName(), \
- QString::fromUtf8(GFGetModuleID())); \
- *p_d = nullptr; \
- return 0; \
- }
-
-#define REGISTER_TRANS_READER() \
- GFAppRegisterTranslatorReader(GFGetModuleID(), TranslatorDataReader)
-
-#define GC_TR(text) QT_TRANSLATE_NOOP("GTrC", text)
-
-#define CONCATENATE_DETAIL(x, y) x##y
-#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y)
-#define GTRC_TR(name, src) CONCATENATE(GTrC_, name)::tr(src)
-
-#define STRINGIFY(x) #x
-#define TOSTRING(x) STRINGIFY(x)
-#define GTRC_AS_STRING(name) TOSTRING(GTrC_##name)
-
-#define EXECUTE_MODULE() \
- auto GFExecuteModule(GFModuleEvent* p_event)->int { \
- auto event = ConvertEventToMap(p_event);
-
-#define END_EXECUTE_MODULE() }
-
-#undef CB_SUCC
-#define CB_SUCC(event) \
- { \
- CB(event, GFGetModuleID(), {{"ret", "0"}}); \
- return 0; \
- }
-
-#undef CB_ERR
-#define CB_ERR(event, ret, err) \
- { \
- CB(event, GFGetModuleID(), \
- {{"ret", QString::number(ret)}, {"err", QString(err)}}); \
- return ret; \
- }
-
-#undef CB_ERR_NO_RET
-#define CB_ERR_NO_RET(event, ret, err) \
- { \
- CB(event, GFGetModuleID(), \
- {{"ret", QString::number(ret)}, {"err", QString(err)}}); \
- }
-
-#define DEFINE_EXECUTE_API_USING_STANDARD_EVEN_HANDLE_MODEL \
- EXECUTE_MODULE() { \
- auto event_id = event["event_id"]; \
- auto it = _gr_module_event_handlers.find(event_id); \
- if (it != _gr_module_event_handlers.end()) { \
- return it.value()(event); \
- } \
- CB_ERR(event, -1, QString("unsupported event id: %1").arg(event_id)); \
- } \
- END_EXECUTE_MODULE()
-
-#define REGISTER_EVENT_HANDLER(event_id, handler) \
- namespace { \
- auto _gv_register_event_handler_by_id_##event_id = []() -> const bool { \
- _gr_module_event_handlers[#event_id] = handler; \
- return true; \
- }(); \
- }
-
-inline void MLogDebug(const QString& s) { GFModuleLogDebug(s.toUtf8()); }
-inline void MLogInfo(const QString& s) { GFModuleLogInfo(s.toUtf8()); }
-inline void MLogWarn(const QString& s) { GFModuleLogWarn(s.toUtf8()); }
-inline void MLogError(const QString& s) { GFModuleLogError(s.toUtf8()); }
-
-#define LOG_DEBUG(format) MLogDebug(FormatString(QString(format)))
-#define LOG_INFO(format) MLogDebug(FormatString(QString(format)))
-#define LOG_WARN(format) MLogDebug(FormatString(QString(format)))
-#define LOG_ERROR(format) MLogDebug(FormatString(QString(format)))
-
-#define FLOG_DEBUG(format, ...) \
- MLogDebug(FormatString(QString(format), __VA_ARGS__))
-#define FLOG_INFO(format, ...) \
- MLogInfo(FormatString(QString(format), __VA_ARGS__))
-#define FLOG_WARN(format, ...) \
- MLogWarn(FormatString(QString(format), __VA_ARGS__))
-#define FLOG_ERROR(format, ...) \
- MLogError(FormatString(QString(format), __VA_ARGS__))
-
-inline auto QStrDup(const QString& str) -> char* { return DUP(str.toUtf8()); }
-
-inline auto QSecStrDup(const QString& str) -> char* {
- return SECDUP(str.toUtf8());
-}
-
-/// Takes ownership of a sized SDK buffer and returns its octets verbatim.
-///
-/// The QString forms below decode UTF-8 and stop at the first NUL, which is
-/// right for identifiers and error strings and wrong for message data: a MIME
-/// entity may be 8bit or binary, and a signature covers exact octets. Anything
-/// that crosses the crypto boundary must use this and the matching @c *N SDK
-/// entry points instead.
-inline auto UnBytesDup(const char* s, size_t size) -> QByteArray {
- if (s == nullptr) return {};
- QByteArray bytes(s, static_cast<qsizetype>(size));
- GFFreeMemory(static_cast<void*>(const_cast<char*>(s)));
- return bytes;
-}
-
-inline auto UnStrDup(const char* s) -> QString {
- auto q_s = QString::fromUtf8(s == nullptr ? "" : s);
- if (s != nullptr) GFFreeMemory(static_cast<void*>(const_cast<char*>(s)));
- return q_s;
-}
-
-inline auto UnSecStrDup(const char* s) -> QString {
- auto q_s = QString::fromUtf8(s == nullptr ? "" : s);
- if (s != nullptr) GFSecFreeMemory(static_cast<void*>(const_cast<char*>(s)));
- return q_s;
-}
-
-inline auto FormatStringHelper(const QString& format, const std::string& arg)
- -> QString {
- return format.arg(QString::fromStdString(arg));
-}
-
-template <typename T>
-auto FormatStringHelper(const QString& format, T arg) -> QString {
- return format.arg(arg);
-}
-
-template <typename... Args>
-auto FormatStringHelper(const QString& format, const std::string& arg,
- Args... args) -> QString {
- return FormatStringHelper(format.arg(QString::fromStdString(arg)), args...);
-}
-
-template <typename T, typename... Args>
-auto FormatStringHelper(const QString& format, T arg, Args... args) -> QString {
- return FormatStringHelper(format.arg(arg), args...);
-}
-
-inline auto FormatStringHelper(const QString& format) -> QString {
- return format;
-}
-
-template <typename T>
-inline auto FormatStringHelper(const T& format) ->
- typename std::enable_if<std::is_same<T, std::string>::value,
- QString>::type {
- return FormatStringHelper(QString::fromStdString(format));
-}
-
-template <typename... Args>
-auto FormatString(const QString& format, Args... args) -> QString {
- return FormatStringHelper(format, args...);
-}
-
-inline auto QMapToMetaDataArray(const QMap<QString, QString>& map)
- -> MetaData** {
- auto** meta_data_array =
- static_cast<MetaData**>(GFAllocateMemory(sizeof(MetaData*) * map.size()));
-
- int index = 0;
- for (auto it = map.begin(); it != map.end(); ++it) {
- meta_data_array[index] =
- static_cast<MetaData*>(GFAllocateMemory(sizeof(MetaData)));
-
- QByteArray const key = it.key().toUtf8();
- QByteArray const value = it.value().toUtf8();
-
- meta_data_array[index]->key = GFModuleStrDup(key);
-
- meta_data_array[index]->value = GFModuleStrDup(value);
-
- ++index;
- }
-
- return meta_data_array;
-}
-
-inline auto QMapToGFModuleMetaDataList(const QMap<QString, QString>& map)
- -> GFModuleMetaData* {
- GFModuleMetaData* head = nullptr;
- GFModuleMetaData* tail = nullptr;
-
- for (auto it = map.begin(); it != map.end(); ++it) {
- auto* new_node = static_cast<GFModuleMetaData*>(
- GFAllocateMemory(sizeof(GFModuleMetaData)));
-
- QByteArray const key = it.key().toUtf8();
- QByteArray const value = it.value().toUtf8();
-
- new_node->key = DUP(key);
- new_node->value = SECDUP(value);
-
- new_node->next = nullptr;
-
- if (tail != nullptr) {
- tail->next = new_node;
- } else {
- head = new_node;
- }
- tail = new_node;
- }
-
- return head;
-}
-
-inline auto ConvertEventParamsToMap(GFModuleEventParam* params)
- -> QMap<QString, QString> {
- QMap<QString, QString> param_map;
- GFModuleEventParam* current = params;
- GFModuleEventParam* last;
-
- while (current != nullptr) {
- const auto name = UDUP(current->name);
- const auto value = USECDUP(current->value);
-
- if (!name.isEmpty()) param_map[name] = value;
-
- last = current;
- current = current->next;
- GFFreeMemory(last);
- }
-
- return param_map;
-}
-
-inline auto ConvertMapToParams(const QMap<QString, QString>& param_map)
- -> GFModuleEventParam* {
- GFModuleEventParam* head = nullptr;
- GFModuleEventParam* prev = nullptr;
-#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
- for (const auto& [key, value] : param_map.asKeyValueRange()) {
- auto* param = static_cast<GFModuleEventParam*>(
- GFAllocateMemory(sizeof(GFModuleEventParam)));
-
- param->name = DUP(key.toUtf8());
- param->value = SECDUP(value.toUtf8());
- param->next = nullptr;
-
- if (prev == nullptr) {
- head = param;
- } else {
- prev->next = param;
- }
- prev = param;
- }
-#else
- for (auto it = param_map.keyValueBegin(); it != param_map.keyValueEnd();
- ++it) {
- auto* param = static_cast<GFModuleEventParam*>(
- GFAllocateMemory(sizeof(GFModuleEventParam)));
-
- param->name = DUP(it->first.toUtf8());
- param->value = SECDUP(it->second.toUtf8());
- param->next = nullptr;
-
- if (prev == nullptr) {
- head = param;
- } else {
- prev->next = param;
- }
- prev = param;
- }
-#endif
-
- return head;
-}
-
-inline auto ConvertEventToMap(GFModuleEvent* event) -> QMap<QString, QString> {
- QMap<QString, QString> event_map;
-
- event_map["event_id"] = UDUP(event->id);
- event_map["trigger_id"] = UDUP(event->trigger_id);
- event_map.insert(ConvertEventParamsToMap(event->params));
-
- GFFreeMemory(event);
-
- return event_map;
-}
-
-inline auto ConvertMapToEvent(QMap<QString, QString> event_map)
- -> GFModuleEvent* {
- auto* event =
- static_cast<GFModuleEvent*>(GFAllocateMemory(sizeof(GFModuleEvent)));
-
- event->id = DUP(event_map["event_id"].toUtf8());
- event->trigger_id = DUP(event_map["trigger_id"].toUtf8());
- event->params = nullptr;
-
- return event;
-}
-
-inline void CB(const QMap<QString, QString>& event, const char* module,
- const QMap<QString, QString>& params) {
- GFModuleTriggerModuleEventCallback(ConvertMapToEvent(event), module,
- ConvertMapToParams(params));
-}
-
-/// Copies @p b into an SDK buffer, NUL-terminated one past the end.
-///
-/// The terminator is not optional politeness: the buffer is handed to the
-/// host, and a caller that treats it as a C string would otherwise read past
-/// the allocation. @p b .size() stays authoritative for the byte count.
-inline auto AllocBufferAndCopy(const QByteArray& b) -> char* {
- auto* p = static_cast<char*>(GFAllocateMemory(sizeof(char) * (b.size() + 1)));
- if (p == nullptr) return nullptr;
- memcpy(p, b.constData(), b.size());
- p[b.size()] = '\0';
- return p;
-}
-
-/// NOTE ON NAMING: the four helpers below say "Secure" but allocate from the
-/// ORDINARY arena (GFAllocateMemory), not the wiping one (GFSecAllocateMemory).
-/// They are named for the SDK allocator generally, not for the secure tier.
-/// For anything that must actually be erased, use the secure tier explicitly
-/// or a GFBuf handle -- do not rely on these names.
-template <typename T, typename... Args>
-auto SecureCreateSharedObject(Args&&... args) -> std::shared_ptr<T> {
- void* mem = GFAllocateMemory(sizeof(T));
- if (!mem) throw std::bad_alloc();
-
- try {
- T* obj = new (mem) T(std::forward<Args>(args)...);
- return std::shared_ptr<T>(obj, [](T* ptr) {
- ptr->~T();
- GFFreeMemory(ptr);
- });
- } catch (...) {
- GFFreeMemory(mem);
- throw;
- }
-}
-
-template <typename T>
-class PointerConverter {
- public:
- explicit PointerConverter(void* ptr) : ptr_(ptr) {}
-
- auto AsType() const -> T* { return static_cast<T*>(ptr_); }
-
- private:
- void* ptr_;
-};
-
-/**
- * @brief
- *
- * @tparam T
- * @return T*
- */
-template <typename T>
-auto SecureMallocAsType(std::size_t size) -> T* {
- return PointerConverter<T>(GFAllocateMemory(size)).AsType();
-}
-
-/**
- * @brief
- *
- * @return void*
- */
-template <typename T>
-auto SecureReallocAsType(T* ptr, std::size_t size) -> T* {
- return PointerConverter<T>(GFReallocateMemory(ptr, size)).AsType();
-}
-
-template <typename T, typename... Args>
-auto SecureCreateQSharedObject(Args&&... args) -> QSharedPointer<T> {
- void* mem = GFAllocateMemory(sizeof(T));
- if (!mem) throw std::bad_alloc();
-
- try {
- T* obj = new (mem) T(std::forward<Args>(args)...);
- return QSharedPointer<T>(obj, [](T* ptr) {
- ptr->~T();
- GFFreeMemory(ptr);
- });
- } catch (...) {
- GFFreeMemory(mem);
- throw;
- }
-}
-
-inline auto CharArrayToQStringList(char** pl_components, int size)
- -> QStringList {
- QStringList list;
- for (int i = 0; i < size; ++i) {
- list.append(UDUP(pl_components[i]));
- }
- GFFreeMemory(static_cast<void*>(pl_components));
- return list;
-}
-
-inline auto QStringListToCharArray(const QStringList& list) -> char** {
- char** char_array =
- static_cast<char**>(GFAllocateMemory(list.size() * sizeof(char*)));
-
- int index = 0;
- for (const QString& item : list) {
- QByteArray value = item.toUtf8();
- char_array[index] = static_cast<char*>(GFAllocateMemory(value.size() + 1));
- std::strcpy(char_array[index], value.constData());
- index++;
- }
-
- return char_array;
-}
-
-template <typename T>
-inline auto ArrayToQList(T** pl_components, int size) -> QList<T> {
- if (pl_components == nullptr || size <= 0) {
- return QList<T>();
- }
-
- QList<T> list;
- for (int i = 0; i < size; ++i) {
- list.append(*pl_components[i]);
- GFFreeMemory(pl_components[i]);
- }
- GFFreeMemory(pl_components);
- return list;
-}
-
-template <typename T>
-inline auto QListToArray(const QList<T>& list) -> T** {
- T** array = static_cast<T**>(GFAllocateMemory(list.size() * sizeof(T*)));
- int index = 0;
- for (const T& item : list) {
- auto mem = static_cast<T*>(GFAllocateMemory(sizeof(T)));
- array[index] = new (mem) T(item);
- index++;
- }
-
- return array;
-}
-
-inline auto ConvertQVariantToVoidPtr(const QVariant& variant) -> void* {
- void* mem = GFAllocateMemory(sizeof(QVariant));
- auto* variant_ptr = new (mem) QVariant(variant);
- return static_cast<void*>(variant_ptr);
-}
-
-inline auto ConvertVoidPtrToQVariant(void* ptr) -> QVariant {
- if (ptr == nullptr) return {};
-
- auto* variant_ptr = static_cast<QVariant*>(ptr);
- QVariant variant = *variant_ptr;
-
- GFFreeMemory(variant_ptr);
- return variant;
-}
-
-template <typename T>
-auto GFUIGetGUIObjectAs(const QString& handle) -> T* {
- auto* obj = static_cast<QObject*>(
- GFUIGetGUIObject(handle.toUtf8().constData()));
- if (!obj) {
- return nullptr;
- }
- return qobject_cast<T*>(obj);
-}
-
-#define Q_VARIANT_Q_OBJECT_FACTORY_DECLARE(name) \
- auto name(void* data_raw_ptr)->void*;
-
-#define Q_VARIANT_Q_OBJECT_FACTORY_DEFINE(name, func) \
- auto name(void* data_raw_ptr)->void* { \
- auto data = ConvertVoidPtrToQVariant(data_raw_ptr); \
- return func(data); \
- }
+// Include "GFModule.h" instead.
-#define GUI_OBJECT(factory, data) \
- GFUICreateGUIObject(factory, ConvertQVariantToVoidPtr(data))
+#include "GFModule.h"
diff --git a/include/GFModuleConvert.h b/include/GFModuleConvert.h
new file mode 100644
index 0000000..8b521c7
--- /dev/null
+++ b/include/GFModuleConvert.h
@@ -0,0 +1,216 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <GFSDKBasic.h>
+#include <GFSDKModule.h>
+#include <GFSDKUI.h>
+#include <GFSDKUIModel.h>
+
+#include <QMap>
+#include <QString>
+#include <QStringList>
+#include <QVariant>
+#include <cstring>
+#include <string>
+#include <type_traits>
+
+#include "GFModuleMemory.h"
+
+/**
+ * @file GFModuleConvert.h
+ * @brief Moving values across the C boundary, and formatting strings.
+ *
+ * Everything here converts between what a module works in (QString, QMap,
+ * QList) and what the SDK speaks (char*, null-terminated arrays, linked
+ * lists). The ownership rules that apply are in GFModuleMemory.h and are not
+ * repeated: as a rule of thumb, a `Convert...ToMap` consumes what it is given
+ * and a `ConvertMapTo...` produces something the callee will own.
+ */
+
+// ---------------------------------------------------------------- formatting
+
+inline auto FormatStringHelper(const QString& format, const std::string& arg)
+ -> QString {
+ return format.arg(QString::fromStdString(arg));
+}
+
+template <typename T>
+auto FormatStringHelper(const QString& format, T arg) -> QString {
+ return format.arg(arg);
+}
+
+template <typename... Args>
+auto FormatStringHelper(const QString& format, const std::string& arg,
+ Args... args) -> QString {
+ return FormatStringHelper(format.arg(QString::fromStdString(arg)), args...);
+}
+
+template <typename T, typename... Args>
+auto FormatStringHelper(const QString& format, T arg, Args... args) -> QString {
+ return FormatStringHelper(format.arg(arg), args...);
+}
+
+inline auto FormatStringHelper(const QString& format) -> QString {
+ return format;
+}
+
+template <typename T>
+inline auto FormatStringHelper(const T& format) ->
+ typename std::enable_if<std::is_same<T, std::string>::value,
+ QString>::type {
+ return FormatStringHelper(QString::fromStdString(format));
+}
+
+/// Qt-style `%1`/`%2` substitution. NOTE: `%1`, not printf's `%s`.
+template <typename... Args>
+auto FormatString(const QString& format, Args... args) -> QString {
+ return FormatStringHelper(format, args...);
+}
+
+// ------------------------------------------------------------------ metadata
+
+inline auto QMapToMetaDataArray(const QMap<QString, QString>& map)
+ -> MetaData** {
+ auto** meta_data_array =
+ static_cast<MetaData**>(GFAllocateMemory(sizeof(MetaData*) * map.size()));
+
+ int index = 0;
+ for (auto it = map.begin(); it != map.end(); ++it) {
+ meta_data_array[index] =
+ static_cast<MetaData*>(GFAllocateMemory(sizeof(MetaData)));
+
+ QByteArray const key = it.key().toUtf8();
+ QByteArray const value = it.value().toUtf8();
+
+ meta_data_array[index]->key = GFModuleStrDup(key);
+ meta_data_array[index]->value = GFModuleStrDup(value);
+
+ ++index;
+ }
+
+ return meta_data_array;
+}
+
+// -------------------------------------------------------------- arrays, lists
+
+inline auto CharArrayToQStringList(char** pl_components, int size)
+ -> QStringList {
+ QStringList list;
+ for (int i = 0; i < size; ++i) {
+ list.append(UDUP(pl_components[i]));
+ }
+ GFFreeMemory(static_cast<void*>(pl_components));
+ return list;
+}
+
+inline auto QStringListToCharArray(const QStringList& list) -> char** {
+ char** char_array =
+ static_cast<char**>(GFAllocateMemory(list.size() * sizeof(char*)));
+
+ int index = 0;
+ for (const QString& item : list) {
+ QByteArray value = item.toUtf8();
+ char_array[index] = static_cast<char*>(GFAllocateMemory(value.size() + 1));
+ std::strcpy(char_array[index], value.constData());
+ index++;
+ }
+
+ return char_array;
+}
+
+template <typename T>
+inline auto ArrayToQList(T** pl_components, int size) -> QList<T> {
+ if (pl_components == nullptr || size <= 0) {
+ return QList<T>();
+ }
+
+ QList<T> list;
+ for (int i = 0; i < size; ++i) {
+ list.append(*pl_components[i]);
+ GFFreeMemory(pl_components[i]);
+ }
+ GFFreeMemory(pl_components);
+ return list;
+}
+
+template <typename T>
+inline auto QListToArray(const QList<T>& list) -> T** {
+ T** array = static_cast<T**>(GFAllocateMemory(list.size() * sizeof(T*)));
+ int index = 0;
+ for (const T& item : list) {
+ auto mem = static_cast<T*>(GFAllocateMemory(sizeof(T)));
+ array[index] = new (mem) T(item);
+ index++;
+ }
+
+ return array;
+}
+
+// ------------------------------------------------------------------- QVariant
+
+inline auto ConvertQVariantToVoidPtr(const QVariant& variant) -> void* {
+ void* mem = GFAllocateMemory(sizeof(QVariant));
+ auto* variant_ptr = new (mem) QVariant(variant);
+ return static_cast<void*>(variant_ptr);
+}
+
+inline auto ConvertVoidPtrToQVariant(void* ptr) -> QVariant {
+ if (ptr == nullptr) return {};
+
+ auto* variant_ptr = static_cast<QVariant*>(ptr);
+ QVariant variant = *variant_ptr;
+
+ GFFreeMemory(variant_ptr);
+ return variant;
+}
+
+// --------------------------------------------------------------- gui objects
+
+template <typename T>
+auto GFUIGetGUIObjectAs(const QString& handle) -> T* {
+ auto* obj =
+ static_cast<QObject*>(GFUIGetGUIObject(handle.toUtf8().constData()));
+ if (!obj) {
+ return nullptr;
+ }
+ return qobject_cast<T*>(obj);
+}
+
+#define Q_VARIANT_Q_OBJECT_FACTORY_DECLARE(name) \
+ auto name(void* data_raw_ptr)->void*;
+
+#define Q_VARIANT_Q_OBJECT_FACTORY_DEFINE(name, func) \
+ auto name(void* data_raw_ptr)->void* { \
+ auto data = ConvertVoidPtrToQVariant(data_raw_ptr); \
+ return func(data); \
+ }
+
+#define GUI_OBJECT(factory, data) \
+ GFUICreateGUIObject(factory, ConvertQVariantToVoidPtr(data))
diff --git a/include/GFModuleDeclare.h b/include/GFModuleDeclare.h
deleted file mode 100644
index 5fa2c0e..0000000
--- a/include/GFModuleDeclare.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Copyright (C) 2021-2024 Saturneric <[email protected]>
- *
- * This file is part of GpgFrontend.
- *
- * GpgFrontend is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GpgFrontend is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
- *
- * The initial version of the source code is inherited from
- * the gpg4usb project, which is under GPL-3.0-or-later.
- *
- * All the source code of GpgFrontend was modified and released by
- * Saturneric <[email protected]> starting on May 12, 2021.
- *
- * SPDX-License-Identifier: GPL-3.0-or-later
- *
- */
-
-#pragma once
-
-#include <GFSDKModule.h>
-
-#include "GFModuleExport.h"
-
-#define GF_MODULE_API_DECLARE \
- extern "C" { \
- auto GF_MODULE_EXPORT GFGetModuleGFSDKVersion() -> const char *; \
- auto GF_MODULE_EXPORT GFGetModuleGFSDKABIVersion() -> int; \
- auto GF_MODULE_EXPORT GFGetModuleQtEnvVersion() -> const char *; \
- auto GF_MODULE_EXPORT GFGetModuleID() -> const char *; \
- auto GF_MODULE_EXPORT GFGetModuleVersion() -> const char *; \
- auto GF_MODULE_EXPORT GFGetModuleMetaData() -> GFModuleMetaData *; \
- auto GF_MODULE_EXPORT GFRegisterModule() -> int; \
- auto GF_MODULE_EXPORT GFActiveModule() -> int; \
- auto GF_MODULE_EXPORT GFExecuteModule(GFModuleEvent *) -> int; \
- auto GF_MODULE_EXPORT GFDeactivateModule() -> int; \
- auto GF_MODULE_EXPORT GFUnregisterModule() -> int; \
- };
diff --git a/include/GFModuleDefine.h b/include/GFModuleDefine.h
index 1a77bd6..0392960 100644
--- a/include/GFModuleDefine.h
+++ b/include/GFModuleDefine.h
@@ -28,13 +28,8 @@
#pragma once
-// The GF_MODULE_API_DEFINE / _V2 macros that used to live here defined the ten
-// separately-exported module symbols. They are gone: a module now describes
-// itself through the single bootstrap table in GFModuleBootstrap.h, which is
-// what lets the host and the module negotiate an ABI and lets the host
-// withhold a capability. This header remains for the shared includes.
-
-#include "GFModuleCommonUtils.hpp"
-#include "GFSDKBuildInfo.h"
-
+// DEPRECATED. Its two includes are what GFModule.h does properly.
+//
+// Include "GFModule.h" instead.
+#include "GFModule.h"
diff --git a/include/GFModuleEvent.h b/include/GFModuleEvent.h
new file mode 100644
index 0000000..1a9ec1d
--- /dev/null
+++ b/include/GFModuleEvent.h
@@ -0,0 +1,236 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <GFSDKBasic.h>
+#include <GFSDKModule.h>
+
+#include <QMap>
+#include <QString>
+#include <functional>
+
+#include "GFModuleConvert.h"
+#include "GFModuleMemory.h"
+
+/**
+ * @file GFModuleEvent.h
+ * @brief Subscribing to events, handling them, and answering them.
+ *
+ * A module says what it wants to hear with LISTEN(), declares a handler per
+ * event with REGISTER_EVENT_HANDLER(), and answers with CB_SUCC() or CB_ERR().
+ * The dispatch table in between is generated by
+ * DEFINE_EXECUTE_API_USING_STANDARD_EVEN_HANDLE_MODEL, which the bootstrap
+ * macro expands for you -- a module does not normally write it out.
+ *
+ * Every one of these expands GFGetModuleID(), which is why that accessor is
+ * the one piece of generated identity that still exists.
+ */
+
+/**
+ * @brief This module's identifier.
+ *
+ * Declared here because this header's macros are what call it. It is *defined*
+ * by GF_MODULE_BOOTSTRAP() in exactly one translation unit per module, from
+ * the identifier in module.json -- so a second copy cannot exist to disagree
+ * with the signed manifest the host checks it against.
+ */
+auto GFGetModuleID() -> const char*;
+
+/**
+ * @brief The lifecycle functions every module defines.
+ *
+ * Written out rather than produced by a macro. `GF_MODULE_API_DECLARE` used to
+ * expand to eleven of these, six of which described entry points the host
+ * stopped resolving when the bootstrap table replaced them -- so the
+ * declarations outlived the functions and nothing noticed, because a
+ * declaration nobody defines costs nothing until somebody calls it.
+ *
+ * GF_MODULE_BOOTSTRAP() adapts these onto the table the host actually uses.
+ */
+extern "C" {
+auto GFRegisterModule() -> int;
+auto GFActiveModule() -> int;
+auto GFExecuteModule(GFModuleEvent*) -> int;
+auto GFDeactivateModule() -> int;
+auto GFUnregisterModule() -> int;
+}
+
+/// Subscribe to an event by id.
+///
+/// No DUP: SDK arguments are borrowed, so pre-allocating here would leak.
+#define LISTEN(event) GFModuleListenEvent(GFGetModuleID(), (event))
+
+// ------------------------------------------------------------- conversions
+
+inline auto ConvertEventParamsToMap(GFModuleEventParam* params)
+ -> QMap<QString, QString> {
+ QMap<QString, QString> param_map;
+ GFModuleEventParam* current = params;
+ GFModuleEventParam* last;
+
+ while (current != nullptr) {
+ const auto name = UDUP(current->name);
+ const auto value = USECDUP(current->value);
+
+ if (!name.isEmpty()) param_map[name] = value;
+
+ last = current;
+ current = current->next;
+ GFFreeMemory(last);
+ }
+
+ return param_map;
+}
+
+inline auto ConvertMapToParams(const QMap<QString, QString>& param_map)
+ -> GFModuleEventParam* {
+ GFModuleEventParam* head = nullptr;
+ GFModuleEventParam* prev = nullptr;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
+ for (const auto& [key, value] : param_map.asKeyValueRange()) {
+ auto* param = static_cast<GFModuleEventParam*>(
+ GFAllocateMemory(sizeof(GFModuleEventParam)));
+
+ param->name = DUP(key.toUtf8());
+ param->value = SECDUP(value.toUtf8());
+ param->next = nullptr;
+
+ if (prev == nullptr) {
+ head = param;
+ } else {
+ prev->next = param;
+ }
+ prev = param;
+ }
+#else
+ for (auto it = param_map.keyValueBegin(); it != param_map.keyValueEnd();
+ ++it) {
+ auto* param = static_cast<GFModuleEventParam*>(
+ GFAllocateMemory(sizeof(GFModuleEventParam)));
+
+ param->name = DUP(it->first.toUtf8());
+ param->value = SECDUP(it->second.toUtf8());
+ param->next = nullptr;
+
+ if (prev == nullptr) {
+ head = param;
+ } else {
+ prev->next = param;
+ }
+ prev = param;
+ }
+#endif
+
+ return head;
+}
+
+inline auto ConvertEventToMap(GFModuleEvent* event) -> QMap<QString, QString> {
+ QMap<QString, QString> event_map;
+
+ event_map["event_id"] = UDUP(event->id);
+ event_map["trigger_id"] = UDUP(event->trigger_id);
+ event_map.insert(ConvertEventParamsToMap(event->params));
+
+ GFFreeMemory(event);
+
+ return event_map;
+}
+
+inline auto ConvertMapToEvent(QMap<QString, QString> event_map)
+ -> GFModuleEvent* {
+ auto* event =
+ static_cast<GFModuleEvent*>(GFAllocateMemory(sizeof(GFModuleEvent)));
+
+ event->id = DUP(event_map["event_id"].toUtf8());
+ event->trigger_id = DUP(event_map["trigger_id"].toUtf8());
+ event->params = nullptr;
+
+ return event;
+}
+
+// ---------------------------------------------------------------- answering
+
+inline void CB(const QMap<QString, QString>& event, const char* module,
+ const QMap<QString, QString>& params) {
+ GFModuleTriggerModuleEventCallback(ConvertMapToEvent(event), module,
+ ConvertMapToParams(params));
+}
+
+#undef CB_SUCC
+/// Answer an event with success, and return from the handler.
+#define CB_SUCC(event) \
+ { \
+ CB(event, GFGetModuleID(), {{"ret", "0"}}); \
+ return 0; \
+ }
+
+#undef CB_ERR
+/// Answer an event with a failure, and return that code from the handler.
+#define CB_ERR(event, ret, err) \
+ { \
+ CB(event, GFGetModuleID(), \
+ {{"ret", QString::number(ret)}, {"err", QString(err)}}); \
+ return ret; \
+ }
+
+#undef CB_ERR_NO_RET
+/// The same, without returning -- for a handler with cleanup still to do.
+#define CB_ERR_NO_RET(event, ret, err) \
+ { \
+ CB(event, GFGetModuleID(), \
+ {{"ret", QString::number(ret)}, {"err", QString(err)}}); \
+ }
+
+// ----------------------------------------------------------------- dispatch
+
+#define EXECUTE_MODULE() \
+ auto GFExecuteModule(GFModuleEvent* p_event)->int { \
+ auto event = ConvertEventToMap(p_event);
+
+#define END_EXECUTE_MODULE() }
+
+#define DEFINE_EXECUTE_API_USING_STANDARD_EVEN_HANDLE_MODEL \
+ EXECUTE_MODULE() { \
+ auto event_id = event["event_id"]; \
+ auto it = _gr_module_event_handlers.find(event_id); \
+ if (it != _gr_module_event_handlers.end()) { \
+ return it.value()(event); \
+ } \
+ CB_ERR(event, -1, QString("unsupported event id: %1").arg(event_id)); \
+ } \
+ END_EXECUTE_MODULE()
+
+/// Register a handler for one event id, at static-initialisation time.
+#define REGISTER_EVENT_HANDLER(event_id, handler) \
+ namespace { \
+ auto _gv_register_event_handler_by_id_##event_id = []() -> const bool { \
+ _gr_module_event_handlers[#event_id] = handler; \
+ return true; \
+ }(); \
+ }
diff --git a/include/GFModuleI18n.h b/include/GFModuleI18n.h
new file mode 100644
index 0000000..4bb52f6
--- /dev/null
+++ b/include/GFModuleI18n.h
@@ -0,0 +1,101 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <GFSDKBasic.h>
+
+#include <QCoreApplication>
+#include <QFile>
+#include <QIODevice>
+#include <QString>
+
+#include "GFModuleLog.h"
+#include "GFModuleMemory.h"
+
+/**
+ * @file GFModuleI18n.h
+ * @brief Translating a module's own strings.
+ *
+ * A module compiles its `.qm` files into itself as Qt resources, and hands
+ * them to the host on request. Two lines set that up:
+ *
+ * DEFINE_TRANSLATIONS_STRUCTURE() // once, at file scope
+ * REGISTER_TRANS_READER() // once, during activation
+ *
+ * The context name is no longer passed in. It comes from
+ * `GF_MODULE_TRANSLATION_CONTEXT`, generated from `module.json` and supplied by
+ * `GFModuleIdentity.h` -- which this header does not include, because a macro
+ * is expanded where it is used: the one translation unit that bootstraps has
+ * that definition, and no other source needs it. It comes from one place because it
+ * used to be written out three times -- here, in `gpgfrontend_collect_ts_files`
+ * and in the `.ts` filenames -- and nothing checked that the three agreed. A
+ * context that disagrees with its `.ts` files does not fail to build; it
+ * silently produces a module that is never translated.
+ */
+
+#define GF_CONCATENATE_DETAIL(x, y) x##y
+#define GF_CONCATENATE(x, y) GF_CONCATENATE_DETAIL(x, y)
+#define GF_STRINGIFY(x) #x
+#define GF_TOSTRING(x) GF_STRINGIFY(x)
+
+/// Mark a string for translation in the module's own context.
+#define GC_TR(text) QT_TRANSLATE_NOOP("GTrC", text)
+
+#define GTRC_TR(name, src) GF_CONCATENATE(GTrC_, name)::tr(src)
+#define GTRC_AS_STRING(name) GF_TOSTRING(GTrC_##name)
+
+/**
+ * @brief Define the module's translation context and its `.qm` reader.
+ *
+ * Expands at file scope, once per module. The reader is what
+ * REGISTER_TRANS_READER() hands to the host.
+ */
+#define DEFINE_TRANSLATIONS_STRUCTURE() \
+ class GTrC { \
+ Q_DECLARE_TR_FUNCTIONS(GTrC) \
+ }; \
+ auto TranslatorDataReader(const char* p_l, char** p_d)->int { \
+ auto locale = QString::fromUtf8(p_l == nullptr ? "" : p_l); \
+ QFile f(QString(":/i18n/%2.%1.qm") \
+ .arg(locale) \
+ .arg(GF_MODULE_TRANSLATION_CONTEXT)); \
+ if (f.exists() && f.open(QIODevice::ReadOnly)) { \
+ auto b = f.readAll(); \
+ *p_d = AllocBufferAndCopy(b); \
+ return b.size(); \
+ } \
+ FLOG_WARN("%3 loading, locale: %1, not found", locale, f.fileName(), \
+ QString::fromUtf8(GFGetModuleID())); \
+ *p_d = nullptr; \
+ return 0; \
+ }
+
+/// Tell the host where to get this module's translations.
+#define REGISTER_TRANS_READER() \
+ GFAppRegisterTranslatorReader(GFGetModuleID(), TranslatorDataReader)
diff --git a/include/GFModuleLog.h b/include/GFModuleLog.h
new file mode 100644
index 0000000..0024ca9
--- /dev/null
+++ b/include/GFModuleLog.h
@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <GFSDKLog.h>
+
+#include <QString>
+
+#include "GFModuleConvert.h"
+
+/**
+ * @file GFModuleLog.h
+ * @brief Logging from inside a module.
+ *
+ * Two families, differing only in whether they take arguments:
+ *
+ * LOG_INFO("started");
+ * FLOG_INFO("started with %1 accounts", count);
+ *
+ * The placeholders are Qt's `%1`, `%2`, ... and **not** printf's `%s`/`%d`.
+ * Passing a printf format produces a message with the format string in it
+ * rather than the value, which is the most common mistake made here.
+ *
+ * ## The levels are real levels
+ *
+ * They were not. `LOG_INFO`, `LOG_WARN` and `LOG_ERROR` all expanded to
+ * `MLogDebug`, so 58 call sites across the modules -- 46 of them `LOG_ERROR`
+ * -- were emitted at debug level and never appeared at any higher one. Every
+ * module author who logged an error and saw nothing was looking at this. The
+ * `FLOG_*` forms were always correct, which is exactly why it went unnoticed:
+ * the two families sat beside each other and only one of them worked.
+ */
+
+inline void MLogDebug(const QString& s) { GFModuleLogDebug(s.toUtf8()); }
+inline void MLogInfo(const QString& s) { GFModuleLogInfo(s.toUtf8()); }
+inline void MLogWarn(const QString& s) { GFModuleLogWarn(s.toUtf8()); }
+inline void MLogError(const QString& s) { GFModuleLogError(s.toUtf8()); }
+
+#define LOG_DEBUG(format) MLogDebug(FormatString(QString(format)))
+#define LOG_INFO(format) MLogInfo(FormatString(QString(format)))
+#define LOG_WARN(format) MLogWarn(FormatString(QString(format)))
+#define LOG_ERROR(format) MLogError(FormatString(QString(format)))
+
+#define FLOG_DEBUG(format, ...) \
+ MLogDebug(FormatString(QString(format), __VA_ARGS__))
+#define FLOG_INFO(format, ...) \
+ MLogInfo(FormatString(QString(format), __VA_ARGS__))
+#define FLOG_WARN(format, ...) \
+ MLogWarn(FormatString(QString(format), __VA_ARGS__))
+#define FLOG_ERROR(format, ...) \
+ MLogError(FormatString(QString(format), __VA_ARGS__))
diff --git a/include/GFModuleMemory.h b/include/GFModuleMemory.h
new file mode 100644
index 0000000..07e1584
--- /dev/null
+++ b/include/GFModuleMemory.h
@@ -0,0 +1,213 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <GFSDKBasic.h>
+
+#include <QByteArray>
+#include <QSharedPointer>
+#include <QString>
+#include <cstring>
+#include <memory>
+#include <new>
+
+/**
+ * @file GFModuleMemory.h
+ * @brief Who frees what, and the helpers that make it obvious.
+ *
+ * ## The rule, in one place
+ *
+ * **SDK arguments are borrowed.** Pass a pointer straight through --
+ * `s.toUtf8().constData()` for a QString -- and keep owning it. Do *not*
+ * pre-allocate one to hand over: nothing on the other side will free it. That
+ * is what the old `DUP()`/`QDUP()` at call sites was for, and why doing it now
+ * simply leaks.
+ *
+ * The one exception is a struct rather than an argument: the few aggregates a
+ * module builds and hands over whole -- `GFModuleEvent`, `GFModuleEventParam`,
+ * `GFModuleMetaData`, `GFCommandExecuteContext` -- still transfer, so their
+ * `char*` members are still allocated with GFModuleStrDup.
+ *
+ * **SDK return values are owned.** Reclaim them, which is what the `U...`
+ * family below is for.
+ */
+
+/// Allocate a copy of @p v that a transferring struct will own.
+#define DUP(v) GFModuleStrDup(v)
+
+/// The same, from the wiping allocator, for a value that is a secret.
+#define SECDUP(v) GFModuleSecStrDup(v)
+
+/// Take ownership of an SDK string and return it as a QString.
+#define UDUP(v) UnStrDup(v)
+
+/// The same, releasing it back to the wiping allocator.
+#define USECDUP(v) UnSecStrDup(v)
+
+/// Take ownership of a sized SDK buffer and return its exact octets.
+#define UDUPN(v, n) UnBytesDup(v, n)
+
+/// Takes ownership of a sized SDK buffer and returns its octets verbatim.
+///
+/// The QString forms below decode UTF-8 and stop at the first NUL, which is
+/// right for identifiers and error strings and wrong for message data: a MIME
+/// entity may be 8bit or binary, and a signature covers exact octets. Anything
+/// that crosses the crypto boundary must use this and the matching @c *N SDK
+/// entry points instead.
+inline auto UnBytesDup(const char* s, size_t size) -> QByteArray {
+ if (s == nullptr) return {};
+ QByteArray bytes(s, static_cast<qsizetype>(size));
+ GFFreeMemory(static_cast<void*>(const_cast<char*>(s)));
+ return bytes;
+}
+
+inline auto UnStrDup(const char* s) -> QString {
+ auto q_s = QString::fromUtf8(s == nullptr ? "" : s);
+ if (s != nullptr) GFFreeMemory(static_cast<void*>(const_cast<char*>(s)));
+ return q_s;
+}
+
+inline auto UnSecStrDup(const char* s) -> QString {
+ auto q_s = QString::fromUtf8(s == nullptr ? "" : s);
+ if (s != nullptr) GFSecFreeMemory(static_cast<void*>(const_cast<char*>(s)));
+ return q_s;
+}
+
+inline auto QStrDup(const QString& str) -> char* { return DUP(str.toUtf8()); }
+
+inline auto QSecStrDup(const QString& str) -> char* {
+ return SECDUP(str.toUtf8());
+}
+
+/// Copies @p b into an SDK buffer, NUL-terminated one past the end.
+///
+/// The terminator is not optional politeness: the buffer is handed to the
+/// host, and a caller that treats it as a C string would otherwise read past
+/// the allocation. @p b .size() stays authoritative for the byte count.
+inline auto AllocBufferAndCopy(const QByteArray& b) -> char* {
+ auto* p = static_cast<char*>(GFAllocateMemory(sizeof(char) * (b.size() + 1)));
+ if (p == nullptr) return nullptr;
+ memcpy(p, b.constData(), b.size());
+ p[b.size()] = '\0';
+ return p;
+}
+
+template <typename T>
+class PointerConverter {
+ public:
+ explicit PointerConverter(void* ptr) : ptr_(ptr) {}
+
+ auto AsType() const -> T* { return static_cast<T*>(ptr_); }
+
+ private:
+ void* ptr_;
+};
+
+/**
+ * @brief Construct a T in SDK-allocated memory, freed through the SDK.
+ *
+ * NAMING. These four used to be called `Secure...`, which said the wrong
+ * thing: they allocate from the ORDINARY SDK arena (GFAllocateMemory), not the
+ * wiping one (GFSecAllocateMemory). The old names had a comment saying so,
+ * which is the weakest possible place to put it -- a name is read every time
+ * and a comment once. For anything that must actually be erased, use the
+ * secure entry points explicitly or a GFBuf handle.
+ */
+template <typename T, typename... Args>
+auto SdkCreateSharedObject(Args&&... args) -> std::shared_ptr<T> {
+ void* mem = GFAllocateMemory(sizeof(T));
+ if (!mem) throw std::bad_alloc();
+
+ try {
+ T* obj = new (mem) T(std::forward<Args>(args)...);
+ return std::shared_ptr<T>(obj, [](T* ptr) {
+ ptr->~T();
+ GFFreeMemory(ptr);
+ });
+ } catch (...) {
+ GFFreeMemory(mem);
+ throw;
+ }
+}
+
+template <typename T, typename... Args>
+auto SdkCreateQSharedObject(Args&&... args) -> QSharedPointer<T> {
+ void* mem = GFAllocateMemory(sizeof(T));
+ if (!mem) throw std::bad_alloc();
+
+ try {
+ T* obj = new (mem) T(std::forward<Args>(args)...);
+ return QSharedPointer<T>(obj, [](T* ptr) {
+ ptr->~T();
+ GFFreeMemory(ptr);
+ });
+ } catch (...) {
+ GFFreeMemory(mem);
+ throw;
+ }
+}
+
+/// Allocate @p size bytes from the ordinary SDK arena, typed.
+template <typename T>
+auto SdkMallocAsType(std::size_t size) -> T* {
+ return PointerConverter<T>(GFAllocateMemory(size)).AsType();
+}
+
+/// Resize an SDK allocation, typed.
+template <typename T>
+auto SdkReallocAsType(T* ptr, std::size_t size) -> T* {
+ return PointerConverter<T>(GFReallocateMemory(ptr, size)).AsType();
+}
+
+// Deprecated spellings, kept for one cycle so a module can be ported without
+// being rewritten. They allocate from the ordinary arena, exactly as the new
+// names say and the old ones did not.
+template <typename T, typename... Args>
+[[deprecated("renamed to SdkCreateSharedObject: it is not a secure allocation")]]
+auto SecureCreateSharedObject(Args&&... args) -> std::shared_ptr<T> {
+ return SdkCreateSharedObject<T>(std::forward<Args>(args)...);
+}
+
+template <typename T, typename... Args>
+[[deprecated("renamed to SdkCreateQSharedObject: it is not a secure allocation")]]
+auto SecureCreateQSharedObject(Args&&... args) -> QSharedPointer<T> {
+ return SdkCreateQSharedObject<T>(std::forward<Args>(args)...);
+}
+
+template <typename T>
+[[deprecated("renamed to SdkMallocAsType: it is not a secure allocation")]]
+auto SecureMallocAsType(std::size_t size) -> T* {
+ return SdkMallocAsType<T>(size);
+}
+
+template <typename T>
+[[deprecated("renamed to SdkReallocAsType: it is not a secure allocation")]]
+auto SecureReallocAsType(T* ptr, std::size_t size) -> T* {
+ return SdkReallocAsType<T>(ptr, size);
+}