aboutsummaryrefslogtreecommitdiffstats
path: root/include/GFModuleEvent.h
diff options
context:
space:
mode:
Diffstat (limited to 'include/GFModuleEvent.h')
-rw-r--r--include/GFModuleEvent.h236
1 files changed, 236 insertions, 0 deletions
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; \
+ }(); \
+ }