diff options
| author | saturneric <[email protected]> | 2026-09-19 11:57:16 +0200 |
|---|---|---|
| committer | saturneric <[email protected]> | 2026-09-19 11:57:16 +0200 |
| commit | cf2eebe9eea1cad1d12dd789fea13d511a63b4b0 (patch) | |
| tree | e25c9cf4f21a4c0d11d7b4be3888bd60b6fed52b | |
| parent | feat(modules): package modules as signed gfmodule files (diff) | |
| download | modules-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
| -rw-r--r-- | include/GFModule.h | 65 | ||||
| -rw-r--r-- | include/GFModuleBootstrap.h | 92 | ||||
| -rw-r--r-- | include/GFModuleCommonUtils.hpp | 522 | ||||
| -rw-r--r-- | include/GFModuleConvert.h | 216 | ||||
| -rw-r--r-- | include/GFModuleDeclare.h | 48 | ||||
| -rw-r--r-- | include/GFModuleDefine.h | 13 | ||||
| -rw-r--r-- | include/GFModuleEvent.h | 236 | ||||
| -rw-r--r-- | include/GFModuleI18n.h | 101 | ||||
| -rw-r--r-- | include/GFModuleLog.h | 77 | ||||
| -rw-r--r-- | include/GFModuleMemory.h | 213 |
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); +} |
