Skip to main content

Extension API Reference

Complete reference for the ext global object available in extension plugins.

Properties

ext.data_dir

The plugin's persistent data directory path. Use this for storing configuration files, caches, etc.

local config_path = ext.data_dir .. "/config.json"

ext.plugin

Read-only table containing metadata about the current extension plugin, as declared in manifest.json.

local p = ext.plugin

ext.log(p.id) -- Plugin ID, e.g. "extension.my_plugin"
ext.log(p.name.raw) -- Display name (raw string)
ext.log(p.version) -- Version string, e.g. "1.0.0"
ext.log(p.publisher) -- Publisher name
ext.log(p.type) -- Always "extension"

-- Optional fields (nil if not declared in manifest)
if p.description then
ext.log(p.description.raw)
end
if p.repository then
ext.log(p.repository)
end
if p.license then
ext.log(p.license)
end
if p.page_path then
ext.log(p.page_path)
end
FieldTypeDescription
idstringPlugin ID
nameLocalizedTextPlugin display name
versionstringPlugin version
publisherstringPlugin publisher
permissionsstring[]Declared permissions
typestringAlways "extension"
descriptionLocalizedText?Plugin description (nil if not set)
repositorystring?Source repository URL (nil if not set)
licensestring?License identifier (nil if not set)
page_pathstring?Relative path to the extension HTML page (nil if not set)

System Information

Version

Available since 3.0.0-dev.2. Requires the "system:info" permission.

When the plugin declares the "system:info" permission, a read-only ext.system table is injected containing hardware and OS details.

ext.system

local sys = ext.system

-- OS
sys.os.platform -- "Windows" | "macOS" | "linux"
sys.os.version -- e.g. "Microsoft Windows 11 Pro"
sys.os.build -- e.g. "22631"
sys.os.arch -- e.g. "x86_64"
sys.os.hostname -- e.g. "MY-PC"

-- Motherboard
sys.motherboard.manufacturer -- e.g. "ASUSTeK COMPUTER INC."
sys.motherboard.model -- e.g. "ROG STRIX B550-F GAMING"
sys.motherboard.product -- same as model
sys.motherboard.serial_number -- board serial

-- BIOS
sys.bios.vendor -- e.g. "American Megatrends Inc."
sys.bios.version -- e.g. "2803"
sys.bios.date -- e.g. "12/01/2023"

-- CPU
sys.cpu.name -- e.g. "AMD Ryzen 9 5900X 12-Core Processor"
sys.cpu.manufacturer -- e.g. "AMD"
sys.cpu.cores -- physical core count
sys.cpu.threads -- logical thread count
sys.cpu.base_clock_mhz -- base clock in MHz
sys.cpu.architecture -- e.g. "x64"

-- GPU (array, 1-indexed)
for i, gpu in ipairs(sys.gpu) do
gpu.name -- e.g. "NVIDIA GeForce RTX 3080"
gpu.manufacturer -- e.g. "NVIDIA"
gpu.driver_version -- e.g. "537.58"
gpu.vram_mb -- VRAM in MB
end

-- RAM
sys.ram.total_memory_mb -- total physical memory in MB
for i, m in ipairs(sys.ram.modules) do
m.manufacturer -- module manufacturer
m.part_number -- module part number
m.capacity_mb -- module capacity in MB
m.speed_mhz -- module speed in MHz
m.form_factor -- e.g. "DIMM", "SO-DIMM"
end

Utility

ext.json_encode(value)

Version

Available since 3.0.0-dev.3.

Encode a Lua table or value into a JSON string.

local json_str = ext.json_encode({ hello = "world", count = 5 })

ext.json_decode(json_string)

Version

Available since 3.0.0-dev.3.

Decode a JSON string into a Lua table or value.

local data = ext.json_decode('{"hello": "world"}')
ext.log(data.hello)

ext.sleep(ms)

Sleep for the specified number of milliseconds.

ext.sleep(1000)  -- Sleep 1 second

Logging

ext.trace(msg)

Log at trace level.

ext.trace("USB scan tick")

ext.debug(msg)

Log at debug level.

ext.debug("Matched VID/PID candidate")

ext.info(msg)

Log at info level.

ext.info("Extension initialized")

ext.log(msg)

Legacy alias for ext.info(msg).

ext.log("Extension initialized")

ext.warn(msg)

Log at warning level.

ext.warn("Device not responding, retrying...")

ext.error(msg)

Log at error level.

ext.error("Connection failed: " .. err)

Notifications

ext.notify(title, description [, level])

Show a toast notification to the user.

ext.notify("Device Found", "Corsair Vengeance RGB connected")
ext.notify("Warning", "Connection unstable", "warning")
ext.notify("Done", "Firmware updated successfully", "success")
  • level"info" (default), "success", "warning", or "error"
  • title and description can be strings, manifest i18n keys, or localized text tables.
ext.notify(
{ key = "notifications.connected.title", fallback = "Connected" },
{
key = "notifications.connected.description",
fallback = "Connected to {server}",
args = { server = "OpenRGB" },
},
"success"
)

ext.notify_persistent(id, title, description)

Show a persistent notification that remains until dismissed. The notification level is always "info".

If a persistent notification with the same id already exists, its title and description will be updated in place.

title and description accept the same string, manifest key, and localized text table forms as ext.notify().

ext.notify_persistent("conn_status", "Connecting...", "Attempting to connect to server")

-- Update existing notification
ext.notify_persistent("conn_status", "Connected", "Successfully connected to server")

ext.dismiss_persistent(id)

Dismiss a persistent notification.

ext.dismiss_persistent("conn_status")

Device Management

ext.register_device(config)

Register a virtual device in Skydimo.

local port = ext.register_device({
controller_port = "bridge://device_0",
device_path = "bridge://device_0", -- optional, defaults to controller_port
nickname = "My Device", -- optional
manufacturer = "Vendor",
model = "Device Name",
serial_id = "SN123456",
description = "RGB Controller",
controller_id = "extension.my_bridge",
device_type = "light",
image_url = "https://example.com/device.png", -- optional
outputs = {
{
id = "zone0",
name = "Main Zone",
leds_count = 144,
output_type = "linear", -- "single", "linear", "matrix"
editable = false,
min_total_leds = 1,
max_total_leds = 300,
allowed_total_leds = {30, 60, 144}, -- optional: restrict to specific counts
matrix = nil, -- or {width, height, map}
default_effect = "rainbow_wave", -- optional
}
}
})
Version

The default_effect field is available since 3.0.0-dev.4. When set, this effect is automatically applied to the output when no user configuration exists.

Returns: string — the controller port identifier for this device.

ext.remove_extension_device(port)

Remove a previously registered virtual device.

ext.remove_extension_device("bridge://device_0")

ext.set_device_nickname(port, nickname)

Set a custom display name for a device.

ext.set_device_nickname("bridge://device_0", "Living Room Strip")

ext.get_devices()

Get all devices in the system.

local devices = ext.get_devices()
for _, dev in ipairs(devices) do
ext.log("Device: " .. dev.port .. " - " .. dev.model)
end

ext.get_device_info(port)

Get detailed information about a specific device.

local info = ext.get_device_info("COM3")
if info then
ext.log("Model: " .. info.model)
end

Output Management

ext.set_output_leds_count(port, output_id, count)

Change the LED count for an output.

ext.set_output_leds_count("bridge://device_0", "zone0", 120)

ext.update_output(port, output_id, config)

Update an output's configuration.

ext.update_output("bridge://device_0", "zone0", {
leds_count = 144,
matrix = {width = 12, height = 12, map = {...}}
})

LED Locking & Direct Control

ext.lock_leds(port, output_id, indices)

Lock specific LEDs for direct control, overriding the active effect.

local locked, rejected = ext.lock_leds("COM3", "out1", {0, 1, 2, 3, 4})
ext.log("Locked: " .. locked .. ", Rejected: " .. rejected)
  • indices — Zero-based LED indices to lock

Returns: integer, integer(locked_count, rejected_count). LEDs may be rejected if already locked by another extension.

ext.unlock_leds(port, output_id, indices)

Release LED locks.

ext.unlock_leds("COM3", "out1", {0, 1, 2, 3, 4})

ext.set_leds(port, output_id, colors)

Set colors on locked LEDs.

Index-based format:

ext.set_leds("COM3", "out1", {
{0, 255, 0, 0}, -- {index, r, g, b}
{1, 0, 255, 0},
{2, 0, 0, 255},
})

Flat RGB format:

ext.set_leds("COM3", "out1", {255, 0, 0, 0, 255, 0, 0, 0, 255})
-- Sets LED 0=red, LED 1=green, LED 2=blue

ext.get_led_locks([port [, output_id]])

Query current LED lock state.

local all_locks = ext.get_led_locks()
local device_locks = ext.get_led_locks("COM3")
local output_locks = ext.get_led_locks("COM3", "out1")

Effect Management

ext.get_effects()

Get all available effects (built-in + plugin effects).

local effects = ext.get_effects()
for _, effect in ipairs(effects) do
ext.log("Effect: " .. effect.id .. " - " .. effect.name.raw)
end

Each entry in the returned array has the following fields:

FieldTypeDescription
idstringEffect plugin ID
nameLocalizedTextEffect display name
descriptionLocalizedText?Short description (nil if not set)
groupLocalizedText?Category/group name (nil if not set)
iconstring?Icon identifier (nil if not set)

ext.get_effect_params(effect_id)

Get the parameter schema for an effect.

local params = ext.get_effect_params("rainbow")

ext.set_effect(port, output_id, effect_id [, params [, options]])

Legacy

This is a legacy convenience wrapper. For new plugins, prefer ext.set_scope_effect(scope, ...) which supports segment_id.

Set the active effect on a device output.

ext.set_effect("COM3", "out1", "rainbow")
ext.set_effect("COM3", "out1", "rainbow", {speed = 3.0, preset = 1})
ext.set_effect("COM3", "out1", "rainbow", nil, {skip_transition = true})

The optional options table uses the same transition options as ext.set_scope_effect. If you only need options, pass nil for params.


Resource Queries

Version

Available since 3.0.0-dev.3.

ext.get_media_session([max_edge])

note

Also available as ext.get_current_media().

Requires the "media:session" permission.

Get the current system media playback session snapshot, including metadata, playback status, timeline, and album artwork.

local session = ext.get_media_session(256) -- Optional: max edge size for artwork
if session then
ext.log("Playing: " .. session.title .. " by " .. session.artist)
ext.log("Status: " .. session.playback_status)
if session.artwork then
ext.log("Artwork size: " .. session.artwork.width .. "x" .. session.artwork.height)
end
end

Returns: A media session object or nil if no active session.

ext.get_displays()

Get a list of all connected displays.

local displays = ext.get_displays()
for _, d in ipairs(displays) do
ext.log("Display: " .. d.name .. " [" .. d.width .. "x" .. d.height .. "]")
end

Returns: array of display objects.

ext.get_audio_devices()

Get a list of all audio output devices.

local devices = ext.get_audio_devices()
for i, dev in ipairs(devices) do
ext.log(i .. ": " .. dev.name)
end

Returns: array of audio device objects.


System State

Version

Available since 3.0.0-dev.3.

System state APIs allow extensions to monitor OS-level state such as running processes and the currently focused window. Each topic requires its own permission.

Platform Support

System state monitoring is currently only supported on Windows. On unsupported platforms, topics report supported = false and return empty data.

ext.list_system_state_topics()

List available system state topics for this extension, filtered by declared permissions.

local topics = ext.list_system_state_topics()
for _, topic in ipairs(topics) do
ext.log(topic.id .. " supported=" .. tostring(topic.supported))
end

Returns: array of topic info objects:

FieldTypeDescription
idstringTopic identifier ("process", "window_focus")
permissionstringPermission required for this topic
supportedbooleanWhether the topic is supported on the current platform

ext.get_system_state(topic)

Get a snapshot of the current state for a given topic.

  • topic — Topic identifier string.
local state = ext.get_system_state("process")
local focus = ext.get_system_state("window_focus")

Returns: A snapshot table (structure depends on the topic). Raises an error if the topic is unknown or the required permission is not declared.

Topic: process

Requires the "system:process" permission.

Snapshot (ext.get_system_state("process")):

FieldTypeDescription
supportedbooleanWhether process monitoring is supported on the current platform
appsarrayList of running applications
apps[].namestringApplication executable name (lowercase, trimmed)
apps[].instance_countintegerNumber of running instances

Change event (on_system_state_changed("process", data)):

FieldTypeDescription
supportedbooleanWhether process monitoring is supported
appsarrayFull list of currently running applications
changesarrayList of applications whose instance count changed
changes[].namestringApplication executable name
changes[].previous_instance_countintegerInstance count before this change
changes[].current_instance_countintegerInstance count after this change
-- Snapshot
local state = ext.get_system_state("process")
for _, app in ipairs(state.apps) do
ext.log(app.name .. ": " .. app.instance_count)
end

-- Change callback
function plugin.on_system_state_changed(topic, data)
if topic == "process" then
for _, c in ipairs(data.changes) do
ext.log(c.name .. ": " .. c.previous_instance_count .. " → " .. c.current_instance_count)
end
end
end

Topic: window_focus

Requires the "system:window-focus" permission.

Snapshot (ext.get_system_state("window_focus")):

FieldTypeDescription
supportedbooleanWhether window focus monitoring is supported on the current platform
currentobject?Currently focused window, or nil if none
current.app_namestring?Application executable name (lowercase)
current.window_titlestring?Window title text

Change event (on_system_state_changed("window_focus", data)):

FieldTypeDescription
supportedbooleanWhether window focus monitoring is supported
reasonstringReason for the change: "snapshot", "foreground_changed", or "title_changed"
currentobject?Currently focused window, or nil
previousobject?Previously focused window, or nil
-- Snapshot
local focus = ext.get_system_state("window_focus")
if focus.current then
ext.log("Focused app: " .. (focus.current.app_name or "unknown"))
ext.log("Window title: " .. (focus.current.window_title or ""))
end

-- Change callback
function plugin.on_system_state_changed(topic, data)
if topic == "window_focus" then
ext.log("Focus reason: " .. data.reason)
if data.current then
ext.log("Now: " .. (data.current.app_name or ""))
end
if data.previous then
ext.log("Was: " .. (data.previous.app_name or ""))
end
end
end

Scope API

Version

Available since 3.0.0-dev.3.

All scope functions accept a scope table as their first argument:

-- Scope table structure
local scope = {
port = "COM3", -- required: device port
output_id = "out1", -- optional: specific output
segment_id = "seg0", -- optional: specific segment (requires output_id)
}

Scope State Queries

ext.get_scope_screen_state(scope)

Get the current screen capture state for a scope (selected screen index and region).

local state = ext.get_scope_screen_state({port = "COM3", output_id = "out1"})
-- state.screen_index, state.region ...

ext.get_scope_audio_device_state(scope)

Get the current audio device index assigned to a scope.

local index = ext.get_scope_audio_device_state({port = "COM3", output_id = "out1"})

Scope Media Management

ext.set_scope_screen_index(scope, screen_index)

Set the display index used for screen-capture effects on a scope.

  • screen_index — Zero-based display index, or nil to use default.
ext.set_scope_screen_index({port = "COM3", output_id = "out1"}, 0)
ext.set_scope_screen_index({port = "COM3", output_id = "out1"}, nil) -- reset

ext.set_scope_screen_region(scope, region)

Set the screen capture region for a scope.

  • region — A ScreenRegion table: {x, y, width, height}.
ext.set_scope_screen_region({port = "COM3", output_id = "out1"}, {
x = 0, y = 0, width = 1920, height = 1080
})

ext.set_scope_audio_device_index(scope, audio_device_index)

Set the audio device index for audio-reactive effects on a scope.

  • audio_device_index — Zero-based device index, or nil to use default.
ext.set_scope_audio_device_index({port = "COM3", output_id = "out1"}, 0)

Scope Mode Management

Version

The optional transition options table for ext.set_scope_effect, ext.set_scope_power, and legacy ext.set_effect is available in versions after 3.0.2.

Transition options

Pass an optional options table when an effect or power change should apply immediately instead of using the normal effect-switch fade. The option only affects the current mutation and is not persisted.

Any of these boolean keys can be set to true:

  • skip_transition
  • skipTransition
  • no_transition
  • immediate
local immediate = {skip_transition = true}

ext.set_scope_effect({port = "COM3", output_id = "out1"}, "rainbow", nil, immediate)
ext.set_scope_power({port = "COM3", output_id = "out1"}, false, {immediate = true})

ext.set_scope_effect(scope, effect_id [, params [, options]])

Set the active effect on a scope. Supports segment_id.

ext.set_scope_effect({port = "COM3", output_id = "out1"}, "rainbow")
ext.set_scope_effect(
{port = "COM3", output_id = "out1", segment_id = "seg0"},
"breathing",
{speed = 2.0}
)
ext.set_scope_effect({port = "COM3", output_id = "out1"}, nil) -- clear effect
ext.set_scope_effect({port = "COM3", output_id = "out1"}, "rainbow", nil, {skipTransition = true})

ext.update_scope_effect_params(scope, params)

Update only the parameters of the currently active effect on a scope, without changing the effect itself.

ext.update_scope_effect_params({port = "COM3", output_id = "out1"}, {
speed = 5.0,
color = {r = 255, g = 0, b = 0}
})

ext.reset_scope_effect_params(scope)

Reset the effect parameters of a scope to their defaults.

ext.reset_scope_effect_params({port = "COM3", output_id = "out1"})

ext.set_scope_mode_paused(scope, paused)

Pause or resume the active effect on a scope.

ext.set_scope_mode_paused({port = "COM3", output_id = "out1"}, true)  -- pause
ext.set_scope_mode_paused({port = "COM3", output_id = "out1"}, false) -- resume

ext.set_scope_power(scope, is_off [, options])

Turn a scope's output on or off.

ext.set_scope_power({port = "COM3", output_id = "out1"}, true)  -- turn off
ext.set_scope_power({port = "COM3", output_id = "out1"}, false) -- turn on
ext.set_scope_power({port = "COM3", output_id = "out1"}, false, {immediate = true})

ext.set_scope_brightness(scope, brightness)

Set the brightness level for a scope (0–100).

ext.set_scope_brightness({port = "COM3", output_id = "out1"}, 80)

Networking

Version

The new structured ext.net.* APIs are available since 3.0.0-dev.3.

Requires the "network", "network:tcp", or "network:http" permission depending on the feature used.

HTTP Client (ext.net.http)

Requires "network:http" or "network".

ext.net.http.request(options)

Make a one-shot HTTP request.

local response = ext.net.http.request({
method = "GET",
url = "https://api.github.com/repos/skydimo/light",
headers = { ["User-Agent"] = "SkydimoExtension" },
timeout_ms = 10000
})

if response.ok then
local data = ext.json_decode(response.body)
ext.log("Repo: " .. data.full_name)
end

Options:

  • method — HTTP method (default: "GET").
  • url — The full target URL.
  • headers — Table of HTTP headers.
  • body — String payload.
  • json — Table/Value to be sent as JSON (sets Content-Type: application/json automatically). Cannot be used together with body.
  • timeout_ms — Request timeout in milliseconds (default: 30000).
  • connect_timeout_ms — Connection timeout in milliseconds (default: 10000).
  • max_response_bytes — Max allowed response size (default: 4MB).
  • follow_redirects — Whether to follow HTTP redirects (default: true).
  • max_redirects — Limit the maximum number of redirects.

Returns: A HttpResponseData table containing ok (boolean), status (integer), url (string), headers (table), and body (string).

ext.net.http.stream(options)

note

Also available as ext.net.http.open().

Open a streamed HTTP request. Useful for Server-Sent Events (SSE) or downloading large payloads in chunks. Events must be consumed manually using read().

local handle = ext.net.http.stream({
url = "https://example.com/events"
})

Returns: integer — Stream handle.

ext.net.http.read(handle [, timeout_ms])

Read the next event from an HTTP stream.

local event = ext.net.http.read(handle, 5000)

if event then
if event.type == "headers" then
ext.log("Response status: " .. event.status)
elseif event.type == "chunk" then
ext.log("Received chunk of size: " .. #event.data)
elseif event.type == "done" then
ext.log("Stream finished")
elseif event.type == "error" then
ext.error("Stream error: " .. event.message)
end
end

Returns: Event table (type, status, data, message), or nil if it timed out. The available properties depend on the event type ("headers", "chunk", "done", or "error").

ext.net.http.close(handle)

Close an active HTTP stream. Streams are not closed automatically until they reach "done" or "error".

ext.net.http.close(handle)

TCP Client (ext.net.tcp)

Requires "network:tcp" or "network".

ext.net.tcp.connect(options)

Open a blocking TCP connection.

local handle = ext.net.tcp.connect({
host = "192.168.1.100",
port = 8080,
connect_timeout_ms = 5000,
read_timeout_ms = nil,
write_timeout_ms = nil,
no_delay = true
})

Options:

  • host — IP address or hostname.
  • port — Port number.
  • connect_timeout_ms — connection timeout (default: 5000).
  • read_timeout_ms — default timeout for reads on this connection.
  • write_timeout_ms — default timeout for writes on this connection.
  • no_delay — Enables TCP_NODELAY (default: true).

Returns: integer — Connection handle, or raises an error on failure.

ext.net.tcp.write(handle, data [, timeout_ms])

Write data to a TCP connection.

local bytes_written = ext.net.tcp.write(handle, "HELLO\n")

Returns: integer — Number of bytes successfully written.

ext.net.tcp.write_all(handle, data [, timeout_ms])

Write all data to a TCP connection. Blocks until the whole payload is sent.

ext.net.tcp.write_all(handle, "HELLO\n")

ext.net.tcp.read(handle, max_len [, timeout_ms])

Receive up to max_len bytes. If timeout_ms is 0 or omitted, it relies on the connection's default read timeout or blocks indefinitely.

local data = ext.net.tcp.read(handle, 4096)

Returns: string — received data.

ext.net.tcp.read_exact(handle, bytes [, timeout_ms])

Receive exactly bytes bytes. Blocks until all are received or an error/timeout occurs.

local data = ext.net.tcp.read_exact(handle, 4)

Returns: string — received data.

ext.net.tcp.close(handle)

Close a TCP connection.

ext.net.tcp.close(handle)

Legacy TCP API (Deprecated)

Deprecated

The following global ext.tcp_* interfaces are deprecated and will be removed in a future release. Please transition to ext.net.tcp.*.

  • ext.tcp_connect(host, port [, timeout_ms]) -> ext.net.tcp.connect({host=host, port=port, connect_timeout_ms=timeout_ms})
  • ext.tcp_send(handle, data [, timeout_ms]) -> ext.net.tcp.write(...)
  • ext.tcp_recv(handle, max_len [, timeout_ms]) -> ext.net.tcp.read(...)
  • ext.tcp_recv_exact(handle, bytes [, timeout_ms]) -> ext.net.tcp.read_exact(...)
  • ext.tcp_close(handle) -> ext.net.tcp.close(...)
  • ext.tcp_write_all(handle, data [, timeout_ms]) -> ext.net.tcp.write_all(...)

Legacy HTTP API (Deprecated)

Deprecated

The following global ext.http_* interfaces are deprecated and will be removed in a future release. Please transition to ext.net.http.*.

  • ext.http_request(options) -> ext.net.http.request(...)
  • ext.http_open(options) -> ext.net.http.stream(...)
  • ext.http_read(handle [, timeout_ms]) -> ext.net.http.read(...)
  • ext.http_close(handle) -> ext.net.http.close(...)

HID Hardware Access

Version

Available since 3.0.0-dev.3. Requires the "hardware:hid" permission.

Direct USB HID device communication. Handles are managed automatically and cleaned up when the extension stops.

ext.hid_enumerate([vid [, pid]])

Enumerate connected HID devices, optionally filtered by Vendor ID and Product ID.

-- List all HID devices
local devices = ext.hid_enumerate()

-- Filter by VID/PID
local devices = ext.hid_enumerate(0x1532, 0x0084)

for _, dev in ipairs(devices) do
ext.log(dev.product .. " @ " .. dev.path)
end

Returns: array of device info tables:

FieldTypeDescription
pathstringPlatform-specific device path
vidintegerUSB Vendor ID
pidintegerUSB Product ID
serialstringSerial number (may be empty)
manufacturerstringManufacturer string
productstringProduct string
interface_numberintegerUSB interface number
usageintegerHID usage ID
usage_pageintegerHID usage page

ext.hid_open(vid, pid [, serial])

Open a HID device by VID/PID, optionally specifying a serial number to disambiguate.

local handle = ext.hid_open(0x1532, 0x0084)

Returns: integer — Device handle.

ext.hid_open_path(path)

Open a HID device by its platform-specific path (from hid_enumerate).

local handle = ext.hid_open_path(dev.path)

Returns: integer — Device handle.

ext.hid_write(handle, data)

Write data to a HID device.

local bytes_written = ext.hid_write(handle, "\x00\x01\x02")
  • data — Binary string to write.

Returns: integer — Number of bytes written.

ext.hid_read(handle, length [, timeout_ms])

Read data from a HID device.

local data = ext.hid_read(handle, 64, 1000)
  • length — Maximum number of bytes to read.
  • timeout_ms — Read timeout in milliseconds (default: 0 for blocking).

Returns: string — Binary data read from the device.

ext.hid_send_feature_report(handle, data)

Send a HID feature report.

local bytes_written = ext.hid_send_feature_report(handle, "\x06\x00\x01")

Returns: integer — Number of bytes written.

ext.hid_get_feature_report(handle, length [, report_id])

Get a HID feature report.

local report = ext.hid_get_feature_report(handle, 64, 0x06)
  • length — Maximum report length.
  • report_id — Report ID (default: 0).

Returns: string — Binary report data.

ext.hid_close(handle)

Close a HID device handle.

ext.hid_close(handle)

Process Management

Requires the "process" permission.

ext.spawn_process(exe, args [, options])

Spawn an external process.

local handle = ext.spawn_process("openrgb", {"--server", "--port", "6742"}, {
hidden = true,
working_dir = ext.data_dir
})
  • args — Table of command-line arguments
  • options.hidden — Hide the process window (boolean)
  • options.working_dir — Working directory path

Returns: integer — process handle.

ext.is_process_alive(handle)

Check if a process is still running.

if ext.is_process_alive(handle) then
ext.log("Process is running")
end

Returns: boolean

ext.kill_process(handle)

Terminate a process.

ext.kill_process(handle)

Page Communication

ext.page_emit(data)

Send data to the extension's embedded HTML page.

ext.page_emit({type = "devices_update", devices = ext.get_devices()})
  • data — Any Lua table (serialized to JSON for the page)

Lifecycle Hooks Summary

HookSignatureDescription
on_start()function()Extension loaded
on_scan_devices()function()Manual scan triggered
on_devices_changed(devices)function(table)Device list changed
on_led_locks_changed(locks)function(table)LED lock state changed
on_system_media_changed(session)function(table)System media metadata/artwork changed (requires media:session; ≥ 3.0.0-dev.3)
on_system_media_playback_changed(session)function(table)System media playback status changed (requires media:session; ≥ 3.0.0-dev.3)
on_system_media_timeline_changed(session)function(table)System media timeline/progress changed (requires media:session; ≥ 3.0.0-dev.3)
on_system_state_changed(topic, data)function(string, table)System state topic changed (requires topic permission; ≥ 3.0.0-dev.3)
on_device_frame(port, outputs)function(string, table)Real-time LED frame data
on_page_message(data)function(table)Message from HTML page
on_stop()function()Extension stopping