diff options
Diffstat (limited to 'include/GFModuleEvent.h')
| -rw-r--r-- | include/GFModuleEvent.h | 236 |
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; \ + }(); \ + } |
