Core API
Waxed Core API
⚠️ Unstable API
This API is under active development and is subject to change without notice. There are no stability guarantees until a V1.0.0 release. Plugin authors should expect interface changes and keep their code in sync with the latest Waxed source.
Overview
The Core API is the primary interface through which plugins interact with the Waxed compositor core. It provides controlled access to core functionality including plugin management, background rendering, Vulkan device access, input handling, display configuration, and cursor management.
The API follows a handle-based design: plugins receive a CoreAPI* handle during initialization that contains function pointers to all available core services. This design allows:
- ABI stability: Functions can be added/removed without breaking existing plugins
- Namespace isolation: Each plugin knows its own name via
own_name - Controlled access: Plugins can only interact with core through defined interfaces
Core Architecture
CoreAPI Structure
The CoreAPI structure is the entry point for all plugin-to-core communication:
struct CoreAPI {
void* internal_handle; // PluginManager* (opaque to plugins)
void* ipc_server_handle; // IPCServer* (opaque to plugins)
const CoreAPIFuncs* funcs; // Function pointer table
const char* own_name; // This plugin's name
};
Usage notes:
internal_handleandipc_server_handleare opaque pointers used internally by core functionsfuncspoints to the global function table containing all callable functionsown_nameis the plugin’s own name for self-identification- Plugins should never access or modify
internal_handleoripc_server_handledirectly
Function Reference
All Core API functions are accessed through the funcs member:
// Example pattern
core->funcs->load_plugin(core, "desktop");
Plugin Management Functions
load_plugin
auto (*load_plugin)(CoreAPI* core, const char* name) -> int;
Load another plugin by name.
Parameters:
core: Core API handlename: Plugin name to load (e.g., “desktop”)
Return value:
0on success- Non-zero on error (plugin not found, load failed)
Usage example:
if (core->funcs->load_plugin(core, "transitioner") != 0) {
// Handle error
}
Thread safety: Safe to call from any thread
Constraints:
- Plugin path must be in the configured plugin directory
- Plugin’s
waxed_plugin_initwill be called - Failed loads return error without modifying state
unload_plugin
auto (*unload_plugin)(CoreAPI* core, const char* name) -> int;
Unload a plugin by name.
Parameters:
core: Core API handlename: Plugin name to unload
Return value:
0on success- Non-zero on error (plugin not found, cannot unload)
Usage example:
core->funcs->unload_plugin(core, "transitioner");
Thread safety: Safe to call from any thread
Constraints:
- Plugin’s
waxed_plugin_cleanupwill be called - Vulkan provider cannot be unloaded if still in use
- Self-unload should use
request_self_unloadinstead
request_self_unload
auto (*request_self_unload)(CoreAPI* core) -> int;
Request that the current plugin be unloaded.
Parameters:
core: Core API handle
Return value:
0on success- Non-zero on error
Usage example:
// In a plugin's init function, if initialization fails:
if (init_failed) {
core->funcs->request_self_unload(core);
return nullptr;
}
Thread safety: Safe to call from any thread
Notes:
- Safer than calling
unload_plugin(core, core->own_name) - Uses the plugin’s own name from the CoreAPI handle
- Cleanup will be called after the current function returns
is_plugin_loaded
auto (*is_plugin_loaded)(CoreAPI* core, const char* name) -> int;
Check if a plugin is currently loaded.
Parameters:
core: Core API handlename: Plugin name to check
Return value:
1if plugin is loaded0if plugin is not loaded- Negative value on error
Usage example:
if (core->funcs->is_plugin_loaded(core, "desktop") == 1) {
// Desktop plugin is loaded
}
Thread safety: Safe to call from any thread
set_visible_plugin
auto (*set_visible_plugin)(CoreAPI* core, const char* name) -> int;
Set the currently visible plugin.
Parameters:
core: Core API handlename: Plugin name to make visible
Return value:
0on success- Non-zero on error (plugin not found)
Usage example:
// Switch to desktop plugin
core->funcs->set_visible_plugin(core, "desktop");
Thread safety: Safe to call from any thread
Architectural notes:
- Only one plugin is visible at a time
- Visible plugin receives render callbacks
- During transitions, both old and new plugins may be visible briefly
get_visible_plugin
auto (*get_visible_plugin)(CoreAPI* core, char* out_name, size_t max_len) -> int;
Get the name of the currently visible plugin.
Parameters:
core: Core API handleout_name: Buffer to receive plugin namemax_len: Maximum buffer size (including null terminator)
Return value:
0on success- Non-zero on error (no visible plugin, buffer too small)
Usage example:
char visible_name[256];
if (core->funcs->get_visible_plugin(core, visible_name, sizeof(visible_name)) == 0) {
printf("Visible plugin: %s\n", visible_name);
}
Thread safety: Safe to call from any thread
get_own_name
auto (*get_own_name)(CoreAPI* core, char* out_name, size_t max_len) -> int;
Get this plugin’s own name.
Parameters:
core: Core API handleout_name: Buffer to receive plugin namemax_len: Maximum buffer size (including null terminator)
Return value:
0on success- Non-zero on error (buffer too small)
Usage example:
char my_name[256];
if (core->funcs->get_own_name(core, my_name, sizeof(my_name)) == 0) {
printf("My name: %s\n", my_name);
}
Thread safety: Safe to call from any thread
Notes:
- Same value as
core->own_name, but copies to a buffer - Useful for logging or diagnostics
get_plugin_state
auto (*get_plugin_state)(CoreAPI* core, const char* name) -> PluginState*;
Get the opaque state pointer for a plugin.
Parameters:
core: Core API handlename: Plugin name to query
Return value:
PluginState*pointer on successnullptron error (plugin not found)
Usage example:
PluginState* desktop_state = core->funcs->get_plugin_state(core, "desktop");
if (desktop_state) {
// State pointer is valid - but don't cast or modify it!
// Only pass it back to other Core API functions
}
Thread safety: Safe to call from any thread
CRITICAL CONSTRAINTS:
- DO NOT cast this pointer to any other type
- DO NOT modify the data pointed to
- DO NOT dereference this pointer
- Only valid to pass back to other Core API functions
- Pointer becomes invalid if the target plugin is unloaded
Architectural note:
- Returns pointer to plugin’s opaque state
- The state is owned by the plugin itself
- This is for inter-plugin communication only
Property Registration
register_property
using PropertySetterFn = auto (*)(const char* value, void* user_data) -> void;
auto (*register_property)(CoreAPI* core, const char* namespace_,
const char* key, const char* description,
PropertySetterFn setter, void* user_data) -> int;
Register a runtime-configurable property.
Parameters:
core: Core API handlenamespace: Property namespace (e.g., “yaml”, “config”)key: Property key (e.g., “background”, “opacity”)description: Human-readable descriptionsetter: Callback function invoked when property is setuser_data: User data passed to the setter callback
Return value:
0on success- Non-zero on error
Usage example:
void background_setter(const char* value, void* user_data) {
auto* ctx = static_cast<MyContext*>(user_data);
// Parse and apply value
ctx->background_path = std::string(value);
}
core->funcs->register_property(
core,
"yaml", // namespace
"background", // key
"Path to background image",
background_setter,
&my_context
);
Thread safety: Safe to call from initialization
Property key format:
- Full key becomes:
plugin_name:namespace:key - Example:
desktop:yaml:background
waxedctl integration:
- Properties can be set via
waxedctl set <plugin>:<namespace>:<key> <value> - The setter callback is invoked when the value is set
IPC Commands
enqueue_command
auto (*enqueue_command)(CoreAPI* core, const char* command) -> int;
Enqueue an IPC command to be processed by the worker thread.
Parameters:
core: Core API handlecommand: Command string to enqueue
Return value:
0on success (command enqueued)- Non-zero on error
Usage example:
// From a transitioner plugin after completing transition
core->funcs->enqueue_command(core, "plugin show desktop");
core->funcs->enqueue_command(core, "plugin unload transitioner");
Thread safety: Safe to call from any thread, including render callbacks
Architectural notes:
- Enqueues request to IPC queue for async processing
- Safe to call from render callbacks (avoids deadlock)
- Worker thread processes request outside render lock
- No response sent back (internal request)
Use cases:
- Transitioner calling “plugin show desktop” after transition completes
- Avoiding deadlock from
set_visible_pluginduring render - Chaining commands that need to execute outside render context
Background Management Functions
set_background
auto (*set_background)(CoreAPI* core, const char* image_path, const char* mode) -> int;
Set background image from file path.
Parameters:
core: Core API handleimage_path: Path to image file (jpg, png, etc.)mode: Display mode (“contain”, “cover”, “tile”, “stretch”)
Return value:
0on success (load enqueued)- Non-zero on error
Usage example:
core->funcs->set_background(core, "/path/to/image.png", "cover");
Thread safety: Safe to call from any thread
Architectural notes:
- Enqueues async load request via BackgroundService
- Returns immediately; actual loading happens in worker thread
- Case-insensitive mode parsing (defaults to “contain”)
- Validates file existence and format before enqueueing
Display modes:
contain: Scale image to fit entirely within display (preserve aspect ratio)cover: Scale image to cover entire display (preserve aspect ratio, may crop)tile: Display image at native resolution, tiling if smallerstretch: Stretch image to fill display (may distort)
get_background_descriptor
struct BackgroundDescriptor {
VkDescriptorSet descriptor_set{};
VkImageLayout image_layout{};
BackgroundMode mode{BackgroundMode::Contain};
BackgroundType type{BackgroundType::None};
uint32_t width{0};
uint32_t height{0};
uint64_t timeline_value{0};
};
auto (*get_background_descriptor)(CoreAPI* core, BackgroundDescriptor* out_descriptor) -> int;
Get current background descriptor for rendering.
Parameters:
core: Core API handleout_descriptor: Pointer to BackgroundDescriptor to populate
Return value:
0on success (background set, descriptor populated)- Non-zero on error or no background
Usage example:
BackgroundDescriptor bg_desc;
if (core->funcs->get_background_descriptor(core, &bg_desc) == 0) {
if (bg_desc.descriptor_set != VK_NULL_HANDLE) {
// Render background using descriptor_set
vkCmdBindDescriptorSets(cmd, ..., bg_desc.descriptor_set, ...);
}
}
Thread safety: Safe to call from any thread (concurrent calls supported)
Architectural notes:
- Thread-safe: Multiple plugins can call this concurrently
- Returns null descriptor (VK_NULL_HANDLE) when no background is set
- Descriptor contains all info needed for rendering
Descriptor contents:
descriptor_set: Vulkan descriptor set for samplingimage_layout: Current image layoutmode: Display mode (contain/cover/tile/stretch)type: Content type (None/StaticImage/Video)width/height: Image dimensionstimeline_value: Timeline semaphore value for synchronization
is_background_ready
auto (*is_background_ready)(CoreAPI* core) -> int;
Check if background is ready to render.
Parameters:
core: Core API handle
Return value:
1if background descriptor is available0if not ready or no background
Usage example:
// Before first render, check if background is ready
if (core->funcs->is_background_ready(core) == 0) {
// Background still loading, render black
return;
}
Thread safety: Safe to call from any thread
Architectural notes:
- Thread-safe: Multiple plugins can call this concurrently
- Returns 1 if a background has been set AND the image has been uploaded
- Returns 0 if no background is set OR image is still loading
- Plugins should call this before first render to avoid black frames
set_background_video
auto (*set_background_video)(CoreAPI* core, const char* video_path, const char* mode) -> int;
Set background video (placeholder for future implementation).
Parameters:
core: Core API handlevideo_path: Path to video filemode: Display mode (“contain”, “cover”, “tile”, “stretch”)
Return value:
- Currently always returns
-1(not implemented)
Architectural notes:
- Currently returns NotImplemented
- Future: Will integrate with video player plugin
- Will use ring buffer for frame streaming
set_background_dma_buf
struct BackgroundDmaBuf {
core::utils::UniqueFd dma_buf_fd; // Ownership transferred to caller
uint32_t width; // Image width in pixels
uint32_t height; // Image height in pixels
uint32_t stride; // Row stride in bytes
uint32_t format; // DRM fourcc format code
uint64_t modifier; // DRM format modifier
VkDeviceSize allocation_size; // Total allocation size
uint32_t memory_type_index; // Vulkan memory type index
// Pre-calculated push constants
float scale_x; // UV/Position scale X
float scale_y; // UV/Position scale Y
float offset_x; // UV/Position offset X
float offset_y; // UV/Position offset Y
int32_t mode; // 0=contain, 1=cover, 2=tile
};
using BackgroundDmaBufCallbackFn = auto (*)(const BackgroundDmaBuf* dma_buf, void* user_data) -> void;
auto (*set_background_dma_buf)(CoreAPI* core, const char* image_path, const char* mode,
uint32_t target_width, uint32_t target_height,
BackgroundDmaBufCallbackFn callback, void* user_data) -> int;
Set background image and export as DMA-BUF (async, callback-based).
Parameters:
core: Core API handleimage_path: Path to image filemode: Display mode (“contain”, “cover”, “tile”, “stretch”)target_width: Target display width for push constant calculationtarget_height: Target display height for push constant calculationcallback: Function to call when DMA-BUF is ready (called from worker thread)user_data: User data pointer passed to callback
Return value:
0on success (load enqueued)- Non-zero on error
Usage example:
void on_dma_buf_ready(const BackgroundDmaBuf* dma_buf, void* user_data) {
auto* ctx = static_cast<MyContext*>(user_data);
// Import DMA-BUF into Vulkan
VkImportSemaphoreFdInfoKHR import_info = {
.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
.fd = dma_buf->dma_buf_fd.get(),
// ...
};
// Use pre-calculated push constants
vkCmdPushConstants(cmd, ..., dma_buf->scale_x, dma_buf->scale_y,
dma_buf->offset_x, dma_buf->offset_y, dma_buf->mode);
}
core->funcs->set_background_dma_buf(
core,
"/path/to/image.png",
"cover",
1920, 1080,
on_dma_buf_ready,
&my_context
);
Thread safety: Safe to call from any thread
Architectural notes:
- Enqueues async background load - NON-BLOCKING, returns immediately
- When texture is loaded and DMA-BUF exported, callback is invoked
- Plugin should render black until callback is invoked
- All operations are non-blocking and async
- Callback is called from BackgroundService worker thread
Ownership model:
dma_buf_fdownership is transferred to caller in callback- RAII-managed cleanup via
UniqueFd - Callback must consume or take ownership of the FD
Push constants:
- Pre-calculated by BackgroundService based on target resolution
- Plugins should use these directly without additional calculation
Vulkan Device Access
register_vulkan_provider
auto (*register_vulkan_provider)(CoreAPI* core,
vk::raii::Instance* instance,
vk::raii::Device* device,
vk::raii::PhysicalDevice* physical_device,
vk::raii::Queue* graphics_queue,
uint32_t graphics_queue_family,
vk::raii::DescriptorPool* descriptor_pool,
vk::raii::DescriptorSetLayout* descriptor_layout) -> int;
Register Vulkan context provider.
Parameters:
core: Core API handleinstance: Vulkan instance (RAII)device: Vulkan device (RAII)physical_device: Vulkan physical device (RAII)graphics_queue: Vulkan graphics queue (RAII)graphics_queue_family: Queue family indexdescriptor_pool: Descriptor pool for allocating descriptor sets (RAII)descriptor_layout: Descriptor set layout (RAII)
Return value:
0on success- Non-zero on error (already registered, invalid handles)
Usage example:
auto result = core->funcs->register_vulkan_provider(
core,
&my_instance,
&my_device,
&my_physical_device,
&my_graphics_queue,
my_queue_family,
&my_descriptor_pool,
&my_descriptor_layout
);
Thread safety: Call during initialization only
Architectural notes:
- First plugin to register becomes the Vulkan provider
- BackgroundService is initialized with provider’s handles
- Subsequent registration attempts return error
- Provider plugin cannot be unloaded (would invalidate handles)
MANDATORY: All Vulkan handles MUST be RAII types from vulkan_raii.hpp
get_vulkan_device
struct VulkanDeviceInfo {
vk::raii::Instance* instance;
vk::raii::Device* device;
vk::raii::PhysicalDevice* physical_device;
vk::raii::Queue* graphics_queue;
uint32_t graphics_queue_family;
VkPhysicalDeviceProperties device_properties;
VkPhysicalDeviceMemoryProperties memory_properties;
bool supports_dma_buf;
bool supports_timeline_semaphore;
};
auto (*get_vulkan_device)(CoreAPI* core, VulkanDeviceInfo* out_info) -> int;
Get Vulkan device information.
Parameters:
core: Core API handleout_info: Pointer to VulkanDeviceInfo to populate
Return value:
0on success-1on invalid parameters-2if device not initialized
Usage example:
VulkanDeviceInfo vk_info;
if (core->funcs->get_vulkan_device(core, &vk_info) == 0) {
// Access device info
uint32_t queue_family = vk_info.graphics_queue_family;
bool dma_buf = vk_info.supports_dma_buf;
// Use RAII pointers
vk::raii::Device& device = *vk_info.device;
}
Thread safety: Thread-safe (reads from shared device)
Architectural notes:
- Returns the registered Vulkan provider’s device information
- Thread-safe: reads from shared device structure
- Plugins can query device capabilities and extensions
- Returns error if no Vulkan provider has been registered
Pointer lifetime:
- Returned pointers point to shared Vulkan objects
- Objects outlive the plugin (owned by core)
- Plugin must not destroy these objects
Shader Service
get_shader_service
auto (*get_shader_service)(CoreAPI* core) -> ShaderService*;
Get ShaderService for runtime shader compilation.
Parameters:
core: Core API handle
Return value:
ShaderService*pointer on successnullptron error
Usage example:
ShaderService* shader_svc = core->funcs->get_shader_service(core);
if (shader_svc) {
// Register shaders for hot-reload
shader_svc->watch_shader("my_shader.vert", "main", vert_spirv);
}
Thread safety: Thread-safe (ShaderService handles its own synchronization)
Architectural notes:
- Returns pointer to core’s ShaderService instance
- Thread-safe: ShaderService handles its own synchronization
- Plugins use this to register shaders for hot-reload
Display Configuration
get_display_config
struct DisplayConfig {
uint32_t display_id; // Unique identifier (0, 1, 2, ...)
DisplayRole role; // Primary or Secondary
DisplayLayoutMode layout_mode; // Mirror or Extend
uint32_t width; // Display width in pixels
uint32_t height; // Display height in pixels
uint32_t refresh_rate; // Refresh rate in Hz
int32_t position_x; // X position in extended mode (0 for mirror)
int32_t position_y; // Y position in extended mode (0 for mirror)
bool vrr_capable; // Display/mode supports Variable Refresh Rate
bool vrr_enabled; // VRR is currently enabled on this display
};
enum class DisplayRole : uint32_t {
Primary = 0, // Main display (display_id = 0)
Secondary = 1, // Other displays (display_id > 0)
};
enum class DisplayLayoutMode : uint32_t {
Mirror = 0, // All displays show identical content
Extend = 1, // Each display can show different content
};
auto (*get_display_config)(const CoreAPI* core, DisplayConfig* out_config) -> int;
Get the current display configuration.
Parameters:
core: Core API handleout_config: Pointer to DisplayConfig to populate
Return value:
0on success-EINVALon error or called outside render context
Usage example:
void my_render_function(CoreAPI* core, RenderTarget* target, void* user_data) {
DisplayConfig config;
if (core->funcs->get_display_config(core, &config) == 0) {
printf("Rendering for display %u: %ux%u\n",
config.display_id, config.width, config.height);
if (config.layout_mode == DisplayLayoutMode::Extend) {
printf("Position: %d, %d\n", config.position_x, config.position_y);
}
}
// Render...
}
Thread safety: Only valid during render callback
Architectural notes:
- Core populates thread-local storage before each render call
display_ididentifies which display is being renderedlayout_modedetermines if rendering is mirrored or extended- In mirror mode, all displays render the same content
- In extend mode, each display may render different content
get_display_count
auto (*get_display_count)(const CoreAPI* core, uint32_t* out_count) -> int;
Get number of active displays.
Parameters:
core: Core API handleout_count: Pointer to receive number of displays
Return value:
0on success- Non-zero on error
Usage example:
uint32_t display_count = 0;
if (core->funcs->get_display_count(core, &display_count) == 0) {
printf("Active displays: %u\n", display_count);
}
Thread safety: Safe to call from any thread
Notes:
- Currently returns
1(single display) - Phase 7: Will return actual count from DisplayManager
register_display_callback
using plugin_display_changed_fn = auto (*)(
PluginState* state,
const DisplayConfig* config
) noexcept -> void;
auto (*register_display_callback)(CoreAPI* core, plugin_display_changed_fn callback) -> int;
Register display change notification callback.
Parameters:
core: Core API handlecallback: Function to call when display config changes
Return value:
0on success-EINVALon error
Usage example:
void on_display_changed(PluginState* state, const DisplayConfig* config) {
// Handle display configuration change
printf("Display %u changed: %ux%u\n", config->display_id, config->width, config->height);
}
core->funcs->register_display_callback(core, on_display_changed);
Thread safety: Safe to call from initialization
Architectural notes:
- Callback is invoked when display configuration changes
- Called from core thread
- Plugin should update its internal state accordingly
unregister_display_callback
auto (*unregister_display_callback)(CoreAPI* core) -> void;
Unregister display change callback.
Parameters:
core: Core API handle
Usage example:
core->funcs->unregister_display_callback(core);
Thread safety: Safe to call from any thread
API Registration
register_api_call
enum class APIArgType : uint8_t {
Int, // Integer (validated with stoi)
Float, // Float (validated with stof)
String, // String (no validation)
Bool, // true/false
FourCC, // DRM FourCC format code (validated as hex)
};
struct APIArgDef {
const char* name; // "x", "width", "format", etc.
APIArgType type;
bool required; // Must be provided
};
using PluginAPICallback = void (*)(const char* api_name,
int argc,
const char** argv);
auto (*register_api_call)(CoreAPI* core,
const char* api_name,
const void* args, // APIArgDef* array
size_t arg_count,
const char* description,
void* callback // PluginAPICallback
) -> int;
Register an API call for the plugin.
Parameters:
core: Core API handleapi_name: Name of the API (e.g., “surface”)args: Array of argument definitions (can be null if arg_count is 0)arg_count: Number of arguments in the arraydescription: Human-readable description of the APIcallback: Function to call when API is invoked
Return value:
0on success- Non-zero on error
Usage example:
// Define arguments
static const APIArgDef surface_args[] = {
{ "x", APIArgType::Int, true },
{ "y", APIArgType::Int, true },
{ "width", APIArgType::Int, false },
{ "height", APIArgType::Int, false },
};
// Callback function
void surface_api_handler(const char* api_name, int argc, const char** argv) {
// Parse arguments and handle the command
}
// Register
core->funcs->register_api_call(
core,
"surface",
surface_args,
sizeof(surface_args) / sizeof(surface_args[0]),
"Create or manipulate a surface",
(void*)surface_api_handler
);
Thread safety: Safe to call from initialization
waxedctl integration:
- Registered APIs can be invoked via
waxedctl <plugin> <api_name> <args> - Arguments are validated against the registered definition
unregister_api_call
auto (*unregister_api_call)(CoreAPI* core, const char* api_name) -> int;
Unregister a previously registered API call.
Parameters:
core: Core API handleapi_name: Name of the API to unregister
Return value:
0on success- Non-zero on error (API not found)
Usage example:
core->funcs->unregister_api_call(core, "surface");
Thread safety: Safe to call from any thread
Cursor Management
register_cursor_provider
struct CursorBuffer {
const uint8_t* pixels; // RGBA pixel data (premultiplied alpha)
uint32_t width; // Cursor width in pixels
uint32_t height; // Cursor height in pixels
int32_t hotspot_x; // Hotspot X offset from top-left
int32_t hotspot_y; // Hotspot Y offset from top-left
uint32_t stride; // Row stride in bytes (usually width * 4)
};
using CursorProviderCallback = auto (*)(void* user_data) -> const CursorBuffer*;
auto (*register_cursor_provider)(CoreAPI* core,
CursorProviderCallback callback,
void* user_data) -> int;
Register cursor provider callback.
Parameters:
core: Core API handlecallback: Function that returns current cursor bufferuser_data: Plugin’s user data passed to callback
Return value:
0on success- Non-zero on error
Usage example:
const CursorBuffer* get_cursor(void* user_data) {
auto* ctx = static_cast<MyContext*>(user_data);
return &ctx->current_cursor; // Return pointer to cached cursor
}
core->funcs->register_cursor_provider(core, get_cursor, &my_context);
Thread safety: Safe to call from initialization
Architectural notes:
- Core calls the callback when it needs to update the hardware cursor
- Plugin returns pointer to its cached CursorBuffer
- Cursor buffer is valid until next call or cleanup
- Only one cursor provider at a time (last registration wins)
Cursor buffer requirements:
pixels: RGBA data with premultiplied alphawidth/height: Cursor size (typically 64x64 or smaller)hotspot_x/y: Point within cursor that represents click positionstride: Bytes per row (usually width * 4)
notify_cursor_shape_changed
auto (*notify_cursor_shape_changed)(CoreAPI* core) -> int;
Notify core that cursor shape has changed.
Parameters:
core: Core API handle
Return value:
0on success- Non-zero on error
Usage example:
// When cursor shape changes (e.g., hover over link)
update_my_cursor_buffer("link");
core->funcs->notify_cursor_shape_changed(core);
Thread safety: Safe to call from any thread
Architectural notes:
- Plugin calls this when cursor shape changes
- Core will reload cursor buffer from provider on next render
- Triggers immediate cursor buffer refresh
request_cursor_takeover
auto (*request_cursor_takeover)(CoreAPI* core, uint32_t display_id) -> int;
Request cursor takeover for a display.
Parameters:
core: Core API handledisplay_id: Display to take over (from RenderTarget.display_id)
Return value:
0on success- Non-zero on error
Usage example:
void my_render_function(CoreAPI* core, RenderTarget* target, void* user_data) {
// Take over cursor rendering for this display
core->funcs->request_cursor_takeover(core, target->display_id);
// Now render cursor in compositor...
}
Thread safety: Safe to call from any thread
Architectural notes:
- Plugin calls this to take over cursor rendering for a specific display
- Core hides hardware cursor; plugin draws cursor in compositor
- Multiple displays can have independent takeover states
release_cursor_takeover
auto (*release_cursor_takeover)(CoreAPI* core, uint32_t display_id) -> int;
Release cursor takeover for a display.
Parameters:
core: Core API handledisplay_id: Display to release
Return value:
0on success- Non-zero on error
Usage example:
core->funcs->release_cursor_takeover(core, 0);
Thread safety: Safe to call from any thread
Architectural notes:
- Plugin calls this to return cursor control to core
- Core resumes drawing on hardware cursor plane
is_cursor_takeover_active
auto (*is_cursor_takeover_active)(const CoreAPI* core, uint32_t display_id) -> bool;
Check if cursor takeover is active for a display.
Parameters:
core: Core API handledisplay_id: Display to check
Return value:
trueif takeover activefalseotherwise
Usage example:
if (core->funcs->is_cursor_takeover_active(core, target->display_id)) {
// Render software cursor
} else {
// Core handles cursor, don't render
}
Thread safety: Safe to call from any thread
Input State
get_input_state
struct MouseEvent {
enum Type : uint8_t {
ButtonPress, // Mouse button pressed
ButtonRelease, // Mouse button released
WheelScroll, // Mouse wheel scrolled
DoubleClick // Double-click detected
};
Type type;
uint8_t button; // Button code (BTN_LEFT, BTN_RIGHT, etc.)
int16_t delta; // Scroll delta (for WheelScroll)
int32_t x, y; // Position at event time
uint64_t timestamp; // Event timestamp (monotonic nanoseconds)
};
struct KeyboardEvent {
enum Type : uint8_t { KeyPress, KeyRelease };
Type type;
uint32_t key_code; // Linux KEY_* code
uint32_t modifiers; // Modifier bitmask
uint64_t timestamp; // Event timestamp (monotonic nanoseconds)
};
constexpr uint32_t KEYBOARD_MOD_CTRL = 1 << 0;
constexpr uint32_t KEYBOARD_MOD_ALT = 1 << 1;
constexpr uint32_t KEYBOARD_MOD_SHIFT = 1 << 2;
struct InputState {
std::atomic<int32_t>* cursor_x; // Global cursor X position
std::atomic<int32_t>* cursor_y; // Global cursor Y position
std::atomic<uint32_t>* buttons_held; // Bitmask of currently held buttons
void* mouse_event_queue; // Lock-free mouse event queue (opaque)
void* keyboard_event_queue; // Lock-free keyboard event queue (opaque)
std::atomic<uint32_t>* modifiers_held; // Bitmask of currently held modifiers
};
auto (*get_input_state)(CoreAPI* core, InputState* out_state) -> int;
Get input state for zero-copy access.
Parameters:
core: Core API handleout_state: Output: InputState with atomic pointers
Return value:
0on success- Non-zero on error
Usage example:
InputState input;
if (core->funcs->get_input_state(core, &input) == 0) {
// Read cursor position atomically
int x = input.cursor_x->load(std::memory_order_relaxed);
int y = input.cursor_y->load(std::memory_order_relaxed);
// Check button state
uint32_t buttons = input.buttons_held->load(std::memory_order_relaxed);
bool left_pressed = buttons & (1 << 0);
// Pop events
MouseEvent event;
while (core->funcs->mouse_event_pop(input.mouse_event_queue, &event)) {
handle_mouse_event(event);
}
}
Thread safety: Thread-safe (atomic access)
Architectural notes:
- Returns pointers to atomics for lock-free position/button tracking
- Plugins can read input state without syscalls
- Event queues use lock-free SPSC (single producer, single consumer)
Button codes:
- Uses Linux input button codes (BTN_*)
buttons_heldis a bitmask of currently pressed buttons
Modifier flags:
KEYBOARD_MOD_CTRL: Ctrl key heldKEYBOARD_MOD_ALT: Alt key heldKEYBOARD_MOD_SHIFT: Shift key held
mouse_event_pop
auto (*mouse_event_pop)(void* mouse_event_queue, MouseEvent* out_event) -> bool;
Pop a mouse event from the queue.
Parameters:
mouse_event_queue: Mouse event queue from InputState.mouse_event_queueout_event: Output: popped event
Return value:
trueif event poppedfalseif queue empty
Usage example:
MouseEvent event;
while (core->funcs->mouse_event_pop(input.mouse_event_queue, &event)) {
if (event.type == MouseEvent::ButtonPress) {
printf("Button %u pressed at (%d, %d)\n", event.button, event.x, event.y);
}
}
Thread safety: Non-blocking, thread-safe
Notes:
- Non-blocking: returns false if queue is empty
- Events are consumed (removed from queue)
keyboard_event_pop
auto (*keyboard_event_pop)(void* keyboard_event_queue, KeyboardEvent* out_event) -> bool;
Pop a keyboard event from the queue.
Parameters:
keyboard_event_queue: Keyboard event queue from InputState.keyboard_event_queueout_event: Output: popped event
Return value:
trueif event poppedfalseif queue empty
Usage example:
KeyboardEvent event;
while (core->funcs->keyboard_event_pop(input.keyboard_event_queue, &event)) {
if (event.type == KeyboardEvent::KeyPress) {
printf("Key %u pressed, modifiers: 0x%x\n", event.key_code, event.modifiers);
}
}
Thread safety: Non-blocking, thread-safe
Notes:
- Non-blocking: returns false if queue is empty
- Events are consumed (removed from queue)
- Key codes are Linux KEY_* values
Thread Safety Summary
| Function | Thread Safety | Notes |
|---|---|---|
load_plugin | Safe | Any thread |
unload_plugin | Safe | Any thread |
request_self_unload | Safe | Any thread |
is_plugin_loaded | Safe | Any thread |
set_visible_plugin | Safe | Any thread, but prefer enqueue_command during render |
get_visible_plugin | Safe | Any thread |
get_own_name | Safe | Any thread |
register_property | Safe | Any thread (typically init) |
get_plugin_state | Safe | Any thread |
enqueue_command | Safe | Any thread, including render callbacks |
set_background | Safe | Any thread |
get_background_descriptor | Safe | Any thread (concurrent) |
is_background_ready | Safe | Any thread (concurrent) |
set_background_video | Safe | Any thread (not implemented) |
register_vulkan_provider | Init only | Call during initialization only |
get_vulkan_device | Safe | Any thread (read-only) |
set_background_dma_buf | Safe | Any thread |
get_shader_service | Safe | Any thread (concurrent) |
get_display_config | Render only | Only valid during render callback |
get_display_count | Safe | Any thread |
register_display_callback | Safe | Any thread (typically init) |
unregister_display_callback | Safe | Any thread |
register_api_call | Safe | Any thread (typically init) |
unregister_api_call | Safe | Any thread |
register_cursor_provider | Safe | Any thread (typically init) |
notify_cursor_shape_changed | Safe | Any thread |
request_cursor_takeover | Safe | Any thread |
release_cursor_takeover | Safe | Any thread |
is_cursor_takeover_active | Safe | Any thread (read-only) |
get_input_state | Safe | Any thread |
mouse_event_pop | Safe | Any thread (concurrent per queue) |
keyboard_event_pop | Safe | Any thread (concurrent per queue) |
Return Value Conventions
Integer return values (most functions):
0: Success- Negative values: Error
- Positive values: Status (e.g.,
is_plugin_loadedreturns 1 for true)
Pointer return values:
- Non-null: Success, valid pointer/value
nullptr: Error or not found
Boolean return values:
true: Affirmativefalse: Negative or empty
Common Patterns
Pattern 1: Safe Plugin Loading
auto load_plugin_safe(CoreAPI* core, const char* name) -> bool {
if (core->funcs->is_plugin_loaded(core, name)) {
return true; // Already loaded
}
return core->funcs->load_plugin(core, name) == 0;
}
Pattern 2: Background Rendering
void render_background(CoreAPI* core, VkCommandBuffer cmd) {
BackgroundDescriptor bg_desc;
if (core->funcs->get_background_descriptor(core, &bg_desc) == 0) {
if (bg_desc.descriptor_set != VK_NULL_HANDLE) {
vkCmdBindDescriptorSets(cmd, ..., bg_desc.descriptor_set, ...);
// Draw fullscreen quad
}
}
}
Pattern 3: Cursor Provider
class CursorManager {
public:
const CursorBuffer* get_cursor(void* user_data) {
auto* self = static_cast<CursorManager*>(user_data);
return &self->current_cursor_;
}
void set_shape(const std::string& shape_name) {
current_cursor_ = load_cursor(shape_name);
// Notify core to reload
core_->funcs->notify_cursor_shape_changed(core_);
}
private:
CoreAPI* core_;
CursorBuffer current_cursor_;
};
Pattern 4: Input Handling
void process_input(CoreAPI* core) {
InputState input;
if (core->funcs->get_input_state(core, &input) != 0) {
return;
}
// Read current cursor position
int x = input.cursor_x->load(std::memory_order_relaxed);
int y = input.cursor_y->load(std::memory_order_relaxed);
// Process pending events
MouseEvent mouse_event;
while (core->funcs->mouse_event_pop(input.mouse_event_queue, &mouse_event)) {
// Handle event
}
}