// action_registry.cpp — see action_registry.h. Needs no REAPER API pointers: // rec->Register is a member call on the dispatch struct REAPER hands the entry point. #include "shell/actions/action_registry.h" #include #include #include #include "core/version/app_version.h" // channelCommandId / channelActionName namespace reasampler { namespace { using version::channelActionName; using version::channelCommandId; // std::deque never invalidates references on push_back, so a c_str() handed to // REAPER stays valid until process exit. Memoized by suffix so register and // mirror-unregister get the SAME id pointer. std::deque g_strStore; // std::deque so element addresses never move after push_back — REAPER keeps each // &accel until the mirror-unregister. struct TableEntry { ActionTableRow row; const char* id = nullptr; // interned channel-qualified command id int cmd = 0; // minted command id (0 = mint failed / inert row) gaccel_register_t accel{}; // Actions-list entry; address handed to REAPER }; std::deque g_table; } // namespace const char* channelIdFor(const char* suffix) { const std::string composed = channelCommandId(suffix); for (const std::string& s : g_strStore) if (s == composed) return s.c_str(); g_strStore.push_back(composed); return g_strStore.back().c_str(); } int registerAction(reaper_plugin_info_t* rec, const char* suffix, gaccel_register_t& accel, const char* phrase) { const char* id = channelIdFor(suffix); const int cmd = rec->Register("command_id", (void*)id); if (cmd) { g_strStore.push_back(channelActionName(phrase)); accel.accel.cmd = cmd; accel.desc = g_strStore.back().c_str(); rec->Register("gaccel", (void*)&accel); } return cmd; } void registerActionTable(reaper_plugin_info_t* rec, const ActionTableRow* rows, std::size_t count) { g_table.clear(); // idempotent-by-construction: a re-register never doubles-up rows for (std::size_t i = 0; i < count; ++i) { g_table.push_back(TableEntry{rows[i]}); TableEntry& e = g_table.back(); e.id = channelIdFor(e.row.suffix); e.cmd = registerAction(rec, e.row.suffix, e.accel, e.row.phrase); } } bool actionTableHandleCommand(int command) { if (command == 0) return false; for (const TableEntry& e : g_table) if (e.cmd != 0 && command == e.cmd) { e.row.run(e.row.arg); return true; } return false; } int actionTableCommandId(const char* suffix) { for (const TableEntry& e : g_table) if (std::strcmp(e.row.suffix, suffix) == 0) return e.cmd; return 0; } void unregisterActionTable(reaper_plugin_info_t* rec) { // Reverse table order, mirroring the register loop. Each '-command_id' // re-presents the SAME interned pointer channelIdFor handed out at register. for (auto it = g_table.rbegin(); it != g_table.rend(); ++it) { rec->Register("-gaccel", (void*)&it->accel); rec->Register("-command_id", (void*)it->id); } } } // namespace reasampler