diff options
| author | Josh Rahm <joshuarahm@gmail.com> | 2026-01-06 23:08:25 -0700 |
|---|---|---|
| committer | Josh Rahm <joshuarahm@gmail.com> | 2026-01-06 23:09:52 -0700 |
| commit | 37e5037b68fb35276f0c426779f61aa278d5b21b (patch) | |
| tree | 94e0b7a413b2899e7114c7453cd0f234a438ce15 | |
| parent | 09860c75bb129c70768692aaec3bd42d0b2735e3 (diff) | |
| download | montis-37e5037b68fb35276f0c426779f61aa278d5b21b.tar.gz montis-37e5037b68fb35276f0c426779f61aa278d5b21b.tar.bz2 montis-37e5037b68fb35276f0c426779f61aa278d5b21b.zip | |
[reorg] use X macro system rather than perl generation.
This remove the dependency on Perl and makes things less brittle.
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | arken/CMakeLists.txt | 20 | ||||
| -rw-r--r-- | arken/README.md | 5 | ||||
| -rw-r--r-- | arken/include/plugin.h | 88 | ||||
| -rw-r--r-- | arken/include/plugin_exports.h | 117 | ||||
| -rw-r--r-- | arken/include/plugin_interface.h | 23 | ||||
| -rw-r--r-- | arken/src/plugin_load.c | 39 |
7 files changed, 188 insertions, 106 deletions
@@ -144,7 +144,7 @@ Note: the `run` target currently starts `arken` with `-s foot`, so you’ll want If you disable the bundled plugin (`-DMONTIS_BUILD_BUNDLED_PLUGIN=OFF`), you can build and supply your own plugin `.so` at runtime. -- Plugin ABI: `arken/include/plugin.h` (generated convenience header: `build/plugin_interface.h`) +- Plugin ABI: `arken/include/plugin.h` (plugin header: `arken/include/plugin_interface.h`) - Optional bridge helpers: `erebor/include` + `build/erebor/liberebor.a` Runtime invocation looks like: diff --git a/arken/CMakeLists.txt b/arken/CMakeLists.txt index 2bfd1d7..9219879 100644 --- a/arken/CMakeLists.txt +++ b/arken/CMakeLists.txt @@ -58,24 +58,6 @@ else() message(FATAL_ERROR "Failed to execute pkg-config") endif() -set(PLUGIN_INTF ${CMAKE_BINARY_DIR}/plugin_interface.h) -add_custom_command( - OUTPUT ${PLUGIN_INTF} - COMMAND perl ${PROJECT_SOURCE_DIR}/tools/genintf.pl < - ${PROJECT_SOURCE_DIR}/include/plugin.h > ${PLUGIN_INTF} - DEPENDS ${PROJECT_SOURCE_DIR}/include/plugin.h - DEPENDS ${PROJECT_SOURCE_DIR}/tools/genintf.pl -) - -set(PLUGIN_LOAD ${CMAKE_BINARY_DIR}/gen_plugin_load.c) -add_custom_command( - OUTPUT ${PLUGIN_LOAD} - COMMAND perl ${PROJECT_SOURCE_DIR}/tools/genbuild.pl < - ${PROJECT_SOURCE_DIR}/include/plugin.h > ${PLUGIN_LOAD} - DEPENDS ${PROJECT_SOURCE_DIR}/include/plugin.h - DEPENDS ${PROJECT_SOURCE_DIR}/tools/genbuild.pl -) - add_custom_command( OUTPUT xdg-shell-protocol.h COMMAND ${WAYLAND_SCANNER} server-header @@ -95,7 +77,7 @@ file (GLOB_RECURSE SOURCES src/*.c) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -add_executable (arken ${SOURCES} ${PLUGIN_LOAD} ${PLUGIN_INTF} ${WLROOTS_LIB_LINK} +add_executable (arken ${SOURCES} ${WLROOTS_LIB_LINK} xdg-shell-protocol.c) find_package(PkgConfig REQUIRED) diff --git a/arken/README.md b/arken/README.md index 5e1ede8..edcddf4 100644 --- a/arken/README.md +++ b/arken/README.md @@ -20,10 +20,9 @@ defined in: - `arken/include/plugin.h` -At build time, a convenience header is generated from that ABI definition for -consumers that want a minimal include surface: +Plugins should include: -- `build/plugin_interface.h` (generated) +- `arken/include/plugin_interface.h` Key files --------- diff --git a/arken/include/plugin.h b/arken/include/plugin.h index 3098602..2407212 100644 --- a/arken/include/plugin.h +++ b/arken/include/plugin.h @@ -10,20 +10,7 @@ #include <wlr/types/wlr_pointer.h> #include "plugin_types.h" - -/* - * Marker macro to define what functions should be exported. This generates the - * interface which the plugin needs to implement. - */ -#define EXPORT(a) a - -#define EXPORT_INCLUDE(a) - -// clang-format off -EXPORT_INCLUDE(<wlr/types/wlr_keyboard.h>) -EXPORT_INCLUDE(<wlr/types/wlr_input_device.h>) -EXPORT_INCLUDE(<wlr/types/wlr_pointer.h>) -// clang-format on +#include "plugin_exports.h" #define MAX_QUEUED_ACTIONS 8 @@ -87,72 +74,10 @@ typedef struct PLUGIN { * will be NULL. */ const char *plugin_name; - /** - * Initializes the plugin on the first time, and only the first time, it is - * loaded. This is used to do things like setup a runtime that cannot be - * reliably torn down. It is up to the plugin to ensure this won't interfere - * with hot-reloading. - */ - EXPORT(void (*plugin_metaload)(int argc, char **argv)); - - /** Intializes the plugin with the given argc/argv. This is the first thing - * called on the plugin and is called immediately after the library is loaded. - */ - EXPORT(void (*plugin_load)(int argc, char **argv)); - - /* Start the plugin with the marshalled state from the previous plugin. - * - * This should return the opaque state from the mashalled_state. - * - * This function should not fail if the state cannot be demarshalled, rather a - * default state should be returned. This is because changing the plugin and - * hot-reloading can produce incompatibilities between the old state and the - * new state, and this should not cause a failure. - */ - EXPORT(opqst_t (*plugin_hot_start)(void *self, uint8_t *mashalled_state, - uint32_t n)); - - /* - * Starts the plugin without a marshalled state. Happens during the first boot - * when there is not state. - */ - EXPORT(opqst_t (*plugin_cold_start)(void* self)); - - /* - * Marshals the state to a bytestring. The returned pointer should be malloc'd - * on the heap. The harness takes ownership of the malloc'd pointer. - * - * This is usually called in preparation for a teardown followed by a - * hot-start. - */ - EXPORT(uint8_t *(*plugin_marshal_state)(opqst_t st, uint32_t *szout)); - - /* - * Teardown the plugin in preperation for the library's imminent unloading. - */ - EXPORT(void (*plugin_teardown)(opqst_t)); - - /* - * Handles a keybinding. - */ - EXPORT(opqst_t (*plugin_handle_keybinding)( - struct wlr_keyboard *keyboard, struct wlr_keyboard_key_event *event, - uint32_t modifiers, uint32_t keysym, uint32_t codepoint, int *out_handled, - opqst_t state)); - - EXPORT(opqst_t (*plugin_handle_button)(struct wlr_pointer_button_event *event, - uint32_t modifiers, opqst_t state)); - - /* Absolute motion only for now; relative motion stays in the runtime. */ - EXPORT(opqst_t (*plugin_handle_motion)(void *event, uint32_t modifiers, - uint32_t is_absolute, double lx, - double ly, opqst_t state)); - - /* - * Handles a surface being mapped, unmapped or destroyed. - */ - EXPORT(opqst_t (*plugin_handle_surface)(void *surface, surface_event_t event, - opqst_t)); + /* Plugin function table populated by the runtime loader. */ +#define PLUGIN_FN_PTR(ret, name, args) ret (*name) args; + MONTIS_PLUGIN_EXPORTS(PLUGIN_FN_PTR) +#undef PLUGIN_FN_PTR /* List of requested actions by the plugin. Right now there is a maximum of 8 * allowed at one time. That should be plenty. The actions should be flushed @@ -161,9 +86,6 @@ typedef struct PLUGIN { requested_action_t requested_actions[MAX_QUEUED_ACTIONS]; } plugin_t; -#undef EXPORT -#undef EXPORT_INCLUDE - /* Reloads the plugin. This tears down the existing plugin, marshals the state * for it and reloads it. * diff --git a/arken/include/plugin_exports.h b/arken/include/plugin_exports.h new file mode 100644 index 0000000..4518d92 --- /dev/null +++ b/arken/include/plugin_exports.h @@ -0,0 +1,117 @@ +#pragma once + +/* + * Single source of truth for the plugin ABI. + * + * Consumers must define an X-macro like: + * #define X(ret, name, args) ... + * and then invoke: + * MONTIS_PLUGIN_EXPORTS(X) + * + * Note: this file intentionally does not include headers. Include whatever you + * need (e.g. <stdint.h>, plugin_types.h, wlroots headers) before expanding. + */ + +#define MONTIS_PLUGIN_EXPORTS(X) \ + /* \ + * `plugin_metaload` \ + * \ + * Called at most once per process *per `plugin_name`*. This is the place \ + * for truly global initialization that should survive hot reloads (e.g. \ + * one-time language runtime init, registering global hooks, etc.). \ + * \ + * Notes: \ + * - May be a no-op. \ + * - Must be safe to call before any other plugin entrypoint. \ + */ \ + X(void, plugin_metaload, (int argc, char **argv)) \ + /* \ + * `plugin_load` \ + * \ + * Called every time the shared object is loaded (cold start and each hot \ + * reload). Use this for per-load initialization that must be re-done after \ + * `dlopen`. \ + */ \ + X(void, plugin_load, (int argc, char **argv)) \ + /* \ + * `plugin_hot_start` \ + * \ + * Called after a hot reload with the previous plugin's marshalled state. \ + * Must return the new live opaque state (`opqst_t`) to be used for future \ + * handler calls. \ + * \ + * Contract: \ + * - Must not fail hard on state incompatibility; fall back to a default. \ + * - `self` is an opaque pointer owned by the runtime; treat it as \ + * read-only. \ + */ \ + X(opqst_t, plugin_hot_start, \ + (void *self, uint8_t *marshalled_state, uint32_t n)) \ + /* \ + * `plugin_cold_start` \ + * \ + * Called on first boot when no previous state exists. Must construct and \ + * return an initial opaque state. \ + */ \ + X(opqst_t, plugin_cold_start, (void *self)) \ + /* \ + * `plugin_marshal_state` \ + * \ + * Called before unloading the current plugin during hot reload. Must \ + * serialize the provided opaque state into a newly allocated byte buffer. \ + * \ + * Ownership: \ + * - Return a heap-allocated buffer (e.g. `malloc`). \ + * - The runtime takes ownership and will `free()` it after \ + * `plugin_hot_start`. \ + */ \ + X(uint8_t *, plugin_marshal_state, (opqst_t st, uint32_t *szout)) \ + /* \ + * `plugin_teardown` \ + * \ + * Called immediately before the shared object is unloaded. Use this to \ + * release resources owned by the opaque state (and any per-load resources \ + * created in `plugin_load`). \ + */ \ + X(void, plugin_teardown, (opqst_t st)) \ + /* \ + * `plugin_handle_keybinding` \ + * \ + * Called for keyboard events. Returns the updated opaque state. Set \ + * `*out_handled` to non-zero if the event is consumed. \ + */ \ + X(opqst_t, plugin_handle_keybinding, \ + (struct wlr_keyboard * keyboard, struct wlr_keyboard_key_event * event, \ + uint32_t modifiers, uint32_t keysym, uint32_t codepoint, \ + int *out_handled, opqst_t state)) \ + /* \ + * `plugin_handle_button` \ + * \ + * Called for pointer button events (mouse/trackpad clicks). Returns the \ + * updated opaque state. \ + */ \ + X(opqst_t, plugin_handle_button, \ + (struct wlr_pointer_button_event * event, uint32_t modifiers, \ + opqst_t state)) \ + /* \ + * `plugin_handle_motion` \ + * \ + * Called for pointer motion. Returns the updated opaque state. \ + * \ + * Parameters: \ + * - `event` is an opaque pointer to the underlying wlroots event struct \ + * (type depends on `is_absolute`). \ + * - `lx`/`ly` are layout coordinates in compositor space. \ + */ \ + X(opqst_t, plugin_handle_motion, \ + (void *event, uint32_t modifiers, uint32_t is_absolute, double lx, \ + double ly, opqst_t state)) \ + /* \ + * `plugin_handle_surface` \ + * \ + * Called when a surface is mapped/unmapped/destroyed. `surface` is an \ + * opaque pointer to the runtime surface representation. Returns the updated \ + * opaque state. \ + */ \ + X(opqst_t, plugin_handle_surface, \ + (void *surface, surface_event_t event, opqst_t state)) diff --git a/arken/include/plugin_interface.h b/arken/include/plugin_interface.h new file mode 100644 index 0000000..0583289 --- /dev/null +++ b/arken/include/plugin_interface.h @@ -0,0 +1,23 @@ +#ifndef _PLUG_INTF +#define _PLUG_INTF + +#include <stdint.h> + +#include "plugin_types.h" +#include "plugin_exports.h" + +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_keyboard.h> +#include <wlr/types/wlr_pointer.h> + +/* + * Plugin ABI: plugins must export these symbols. + * + * This header is intended to be included by plugin implementations. + */ + +#define DECLARE_PLUGIN_EXPORT(ret, name, args) ret name args; +MONTIS_PLUGIN_EXPORTS(DECLARE_PLUGIN_EXPORT) +#undef DECLARE_PLUGIN_EXPORT + +#endif /* _PLUG_INTF */ diff --git a/arken/src/plugin_load.c b/arken/src/plugin_load.c new file mode 100644 index 0000000..6525969 --- /dev/null +++ b/arken/src/plugin_load.c @@ -0,0 +1,39 @@ +#include "plugin.h" + +#include <dlfcn.h> +#include <stdio.h> + +#include "plugin_exports.h" + +int load_plugin_from_dl_(dlhandle_t dl, plugin_t *plug) +{ + void *ptr; + int ret = 0; + + const char **name = dlsym(dl, "plugin_name"); + if (name) { + plug->plugin_name = *name; + } + else { + plug->plugin_name = NULL; + } + + plug->state = NULL; + plug->library_handle = dl; + +#define LOAD_SYM(ret_type, sym, args) \ + do { \ + ptr = dlsym(dl, #sym); \ + if (!ptr) { \ + fprintf(stderr, "Plugin missing %s\n", #sym); \ + ret |= 1; \ + } \ + plug->sym = (ret_type(*) args)ptr; \ + } while (0); + + MONTIS_PLUGIN_EXPORTS(LOAD_SYM); + +#undef LOAD_SYM + + return ret; +} |