Reorganize extension-reaper-macos into per-module subfolders, add hello module

Splits socket/tracking into their own subdirectories and pulls the hello
action registration out into its own module, matching the one-module-per-
folder pattern; build.sh now outputs into build/macos instead of the repo root.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Paul Lipscomb
2026-07-22 18:49:25 -04:00
parent 7d4f8a4cd2
commit 2173e366ea
10 changed files with 231 additions and 87 deletions
+1 -1
View File
@@ -1 +1 @@
*.dylib
build/
+10 -4
View File
@@ -3,7 +3,11 @@
set -e
cd "$(dirname "$0")"
OUT="reaper_extension-reaper-macos.dylib"
NAME="reaper_extension-reaper-macos.dylib"
OUT_DIR="build/macos"
OUT="$OUT_DIR/$NAME"
mkdir -p "$OUT_DIR"
clang++ \
-std=c++17 \
@@ -12,13 +16,15 @@ clang++ \
-arch arm64 \
-Ivendor/reaper-sdk/sdk \
-Ivendor/reaper-sdk/WDL \
-Isrc \
-o "$OUT" \
src/main.cpp \
src/tracking.cpp \
src/socket.cpp
src/hello/hello.cpp \
src/tracking/tracking.cpp \
src/socket/socket.cpp
echo "Built $OUT"
DEST="$HOME/Library/Application Support/REAPER/UserPlugins/$OUT"
DEST="$HOME/Library/Application Support/REAPER/UserPlugins/$NAME"
cp "$OUT" "$DEST"
echo "Copied to $DEST (restart REAPER to reload)"
+63
View File
@@ -0,0 +1,63 @@
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_ShowConsoleMsg
#include "reaper_plugin.h"
#include "reaper_plugin_functions.h"
#include <cstdio>
static bool isOurAction;
static bool handled;
static int helloActionId = 0;
static const char *kHelloActionIdStr = "EXTENSION_REAPER_MACOS_HELLO";
static const char *kHelloActionName = "Hello Action List Display Name";
static bool HelloAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd);
void RegisterHello(reaper_plugin_info_t *rec)
{
custom_action_register_t actionDescription = {
0,
kHelloActionIdStr,
kHelloActionName,
NULL,
};
helloActionId = rec->Register("custom_action", &actionDescription);
char buf[128];
snprintf(buf, sizeof(buf), "[extension-reaper-macos] helloActionId = %d\n", helloActionId);
ShowConsoleMsg(buf);
rec->Register("hookcommand2", (void *)HelloAction);
}
static void PerformHelloAction()
{
ShowConsoleMsg("[extension-reaper-macos] Hello action triggered\n");
}
static void RunHelloActionLogic(int command)
{
char buf[128];
snprintf(buf, sizeof(buf), "[extension-reaper-macos] HelloAction called: command=%d helloActionId=%d\n", command, helloActionId);
ShowConsoleMsg(buf);
isOurAction = (command == helloActionId);
if (isOurAction == true)
{
PerformHelloAction();
handled = true;
return;
}
ShowConsoleMsg("[extension-reaper-macos] ignored — not ours\n");
handled = false;
}
static bool HelloAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd)
{
RunHelloActionLogic(command);
return handled;
}
@@ -0,0 +1,70 @@
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_ShowConsoleMsg
#include "reaper_plugin.h"
#include "reaper_plugin_functions.h"
#include "hello/hello.h"
#include <cstdio>
static bool isOurAction;
static bool handled;
static int helloActionId = 0;
static const char *kHelloActionIdStr = "EXTENSION_REAPER_MACOS_HELLO";
static const char *kHelloActionName = "Hello Action List Display Name";
static bool HelloAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd);
static void HandleHelloAction(int command);
static void RunHelloAction();
//step 1 register
void RegisterHello(reaper_plugin_info_t *rec)
{
custom_action_register_t actionDescription = {
0,
kHelloActionIdStr,
kHelloActionName,
NULL,
};
helloActionId = rec->Register("custom_action", &actionDescription);
char buf[128];
snprintf(buf, sizeof(buf), "[extension-reaper-macos] helloActionId = %d\n", helloActionId);
ShowConsoleMsg(buf);
rec->Register("hookcommand2", (void *)HelloAction);
}
//step 2 callback from REAPER
static bool HelloAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd)
{
HandleHelloAction(command);
return handled;
}
//step 3 check whether the ID REAPER gave us is our registered action, and dispatch to step 4 if so
static void HandleHelloAction(int command)
{
char buf[128];
snprintf(buf, sizeof(buf), "[extension-reaper-macos] HelloAction called: command=%d helloActionId=%d\n", command, helloActionId);
ShowConsoleMsg(buf);
isOurAction = (command == helloActionId);
if (isOurAction == true)
{
RunHelloAction();
handled = true;
return;
}
ShowConsoleMsg("[extension-reaper-macos] ignored — not ours\n");
handled = false;
}
//step 4 perform action
static void RunHelloAction()
{
ShowConsoleMsg("[extension-reaper-macos] Hello action triggered\n");
}
+14
View File
@@ -0,0 +1,14 @@
// The "Hello" test action — the first thing built in this extension, to
// prove custom_action registration + hookcommand2 callbacks work end to
// end. Kept around as a working reference, not load-bearing for the real
// feature.
#ifndef EXTENSION_REAPER_MACOS_HELLO_H
#define EXTENSION_REAPER_MACOS_HELLO_H
#include "reaper_plugin.h"
// Call once from the entrypoint, after REAPERAPI_LoadAPI has succeeded.
void RegisterHello(reaper_plugin_info_t *rec);
#endif
+70 -79
View File
@@ -1,27 +1,21 @@
// extension-reaper-macos — bare-minimum REAPER extension.
// extension-reaper-macos — REAPER extension entry point.
//
// Loads, registers one test action ("extension-reaper-macos: Hello"), and
// prints a confirmation to the REAPER console.
// This file's only job: establish REAPER_PLUGIN_ENTRYPOINT (the one
// function name REAPER looks for), do the required load/unload/version
// checks, then delegate everything else to its own module — hello/,
// tracking/, socket/.
//
// Top-level story:
// - We tell the compiler which REAPER functions we need a box for.
// - We tell the compiler which REAPER functions we need a box for. This
// file has to cover everything used anywhere in the project, since it's
// the one with REAPERAPI_IMPLEMENT — the one that owns the real storage.
// - REAPER loads our .dylib and calls our one required function, once.
// - We check we're actually loading, not unloading, and the version matches.
// - We fill our function box(es) with their real address.
// - We describe a new action and hand it to REAPER — it creates it, gives
// us back a number.
// - We subscribe our own function to REAPER's "every action, any trigger"
// stream.
// - We delegate to each module's own Register___(rec) function.
// - We print "loaded successfully" — proof of everything up to that point,
// but NOT proof the action/subscription actually works.
// - Setup's done. We now just sit in memory, doing nothing.
// - Later, any action anywhere in REAPER (click, key, MIDI, OSC) calls our
// subscribed function.
// - It checks if the action was ours. If yes, react. If no, ignore.
// but NOT proof any individual module actually works.
// We tell the compiler which REAPER functions we need a box for. This file
// owns the real storage (REAPERAPI_IMPLEMENT), so this list has to cover
// everything used anywhere in the project, including tracking.cpp.
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_ShowConsoleMsg
#define REAPERAPI_WANT_CountAutomationItems
@@ -34,79 +28,76 @@
#include "reaper_plugin.h"
#include "reaper_plugin_functions.h"
#include "tracking.h"
#include "socket.h"
#include "hello/hello.h"
#include "tracking/tracking.h"
#include "socket/socket.h"
#include <cstdio>
// Return values REAPER expects back from us.
const int kUnloadOrIncompatible = 0;
const int kLoadedSuccessfully = 1;
static int action1_id = 0;
static const char *kAction1IdStr = "EXTENSION_REAPER_MACOS_HELLO";
static const char *kAction1Name = "Hello Action List Display Name";
// Testing hookcommand2 again (REAPER's docs specifically recommend it for
// custom_action-registered actions), this time with debug prints kept in.
static bool Action1(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd)
{
// Unconditional — proves whether this callback is being reached at all,
// and for which command IDs, regardless of whether it's ours.
char buf[128];
snprintf(buf, sizeof(buf), "[extension-reaper-macos] Action1 called: command=%d action1_id=%d\n", command, action1_id);
ShowConsoleMsg(buf);
if (command == action1_id)
{
ShowConsoleMsg("[extension-reaper-macos] Hello action triggered\n");
return true;
}
ShowConsoleMsg("[extension-reaper-macos] ignored — not ours\n");
return false;
}
static bool CheckerExtLoadingOrUnloading(reaper_plugin_info_t *rec);
static bool CheckerExtVersionMatches(reaper_plugin_info_t *rec);
static bool CheckerExtFunctionsLoaded(reaper_plugin_info_t *rec);
static void RegisterActions(reaper_plugin_info_t *rec);
// REAPER loads our .dylib and calls this once.
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec)
{
// Check we're actually loading, not unloading, and the version matches.
if (!rec)
return 0;
if (rec->caller_version != REAPER_PLUGIN_VERSION)
return 0;
if (CheckerExtLoadingOrUnloading(rec) == true)
{
return kUnloadOrIncompatible;
}
// Fill our function box(es) with their real address.
if (REAPERAPI_LoadAPI(rec->GetFunc) != 0)
return 0;
if (CheckerExtVersionMatches(rec) == false)
{
return kUnloadOrIncompatible;
}
// Describe a new action and hand it to REAPER — it creates it, gives us
// back a number, which we save.
custom_action_register_t actionDescription = {
0,
kAction1IdStr,
kAction1Name,
NULL,
};
action1_id = rec->Register("custom_action", &actionDescription);
if (CheckerExtFunctionsLoaded(rec) == false)
{
return kUnloadOrIncompatible;
}
// Verify registration actually succeeded — Register returns 0 on failure,
// and we've never checked that until now.
char buf[128];
snprintf(buf, sizeof(buf), "[extension-reaper-macos] action1_id = %d\n", action1_id);
ShowConsoleMsg(buf);
RegisterActions(rec);
// Subscribe Action1 via hookcommand2 — REAPER's documented pairing for
// custom_action-registered actions specifically.
rec->Register("hookcommand2", (void *)Action1);
// Envelope/automation-item polling now lives in tracking.cpp.
RegisterTracking(rec);
// Basic UDP socket listener lives in socket.cpp.
RegisterSocket(rec);
// Print "loaded successfully" — proof of everything above, but NOT proof
// the action/subscription actually works.
// Prove we got this far — but NOT proof any individual module works.
ShowConsoleMsg("[extension-reaper-macos] loaded successfully\n");
// Setup's done. Return 1 = "keep me loaded." We now just sit in memory,
// doing nothing, until Action1 gets called later.
return 1;
return kLoadedSuccessfully;
}
// Checks whether REAPER is unloading us (rec is NULL) rather than loading
// us. Safe to call no matter what — this IS the check for whether rec is
// even safe to use for anything else.
static bool CheckerExtLoadingOrUnloading(reaper_plugin_info_t *rec)
{
bool isUnloading = (rec == NULL);
return isUnloading;
}
// Checks whether this REAPER's plugin format matches what we expect. Only
// call after CheckerExtLoadingOrUnloading has confirmed rec is real.
static bool CheckerExtVersionMatches(reaper_plugin_info_t *rec)
{
bool versionMatches = (rec->caller_version == REAPER_PLUGIN_VERSION);
return versionMatches;
}
// Fetches every REAPER function we WANT'd above, and checks whether all of
// them were found. Only call once we know rec is real.
static bool CheckerExtFunctionsLoaded(reaper_plugin_info_t *rec)
{
int missingFunctionCount = REAPERAPI_LoadAPI(rec->GetFunc);
bool allFunctionsLoaded = (missingFunctionCount == 0);
return allFunctionsLoaded;
}
// Delegates to each module's own Register___(rec) function — one place
// that lists every module this extension is made of.
static void RegisterActions(reaper_plugin_info_t *rec)
{
RegisterHello(rec);
RegisterTracking(rec);
RegisterSocket(rec);
}
@@ -16,8 +16,8 @@
#include "reaper_plugin.h"
#include "reaper_plugin_functions.h"
#include "socket.h"
#include "tracking.h"
#include "socket/socket.h"
#include "tracking/tracking.h"
#include <cstdio>
#include <cstdlib>
@@ -20,7 +20,7 @@
#include "reaper_plugin.h"
#include "reaper_plugin_functions.h"
#include "tracking.h"
#include "tracking/tracking.h"
#include <cstdio>
#include <vector>