Skip to content

API Host Services

RadiatorTwo edited this page May 31, 2026 · 3 revisions

Host Services

IPluginHost is the only sanctioned bridge from a plugin back into the LoupixDeck core. The host hands an instance to the plugin in Initialize and exposes everything a plugin needs through it.

IPluginHost

public interface IPluginHost
{
    IPluginLogger   Logger        { get; }
    IPluginSettings Settings      { get; }
    DeviceInfo?     ActiveDevice  { get; }

    void RequestButtonRefresh(string commandName);
    void ExecuteCommand(string command);
    void OpenFolder(IFolderProvider provider);

    bool OpenBrowser(string url);
    void OverlayTouchText(int slot, string text, TimeSpan duration);
    int  GetTouchSlotForRotary(int rotaryIndex);

    bool RequestExclusiveMode(IExclusiveModeProvider provider);
    void ReleaseExclusiveMode(IExclusiveModeProvider provider);
    bool IsInExclusiveMode { get; }
}
Member Notes
Logger Log sink scoped to this plugin — output is tagged with the plugin's Id. See IPluginLogger.
Settings Per-plugin JSON-backed key/value store under plugins/<plugin-id>/settings.json. See IPluginSettings.
ActiveDevice Currently driven device, or null if none. Mirrored on CommandContext.Device.
RequestButtonRefresh(commandName) Asks the host to re-render every touch button bound to commandName. Use after data backing an IDisplayCommand changes via push so the user sees the new value immediately instead of at the next poll tick.
ExecuteCommand(command) Runs a command string through the host's command pipeline (Plugin.Command(arg1,arg2) syntax). Enables chaining across plugin boundaries — e.g. an action that triggers another plugin's command.
OpenFolder(provider) Pushes a folder navigation view onto the touch screen. The host calls provider.OnEnter() and starts rendering from provider.BuildEntries().
OpenBrowser(url) Opens url in the user's default browser. Returns true when the launch was dispatched. The host abstracts OS specifics (Windows: shell-execute, Linux: xdg-open) so OAuth and similar flows don't need per-plugin platform branches.
OverlayTouchText(slot, text, duration) Temporarily paints text on the touch slot at slot, restoring the slot's normal content after duration. A later call on the same slot supersedes any pending restore — use it for transient feedback like a volume change or playback skip without dedicating a button. Fires and forgets; the host does the timing.
GetTouchSlotForRotary(rotaryIndex) Returns the touch slot that visually sits next to the rotary encoder at rotaryIndex, or -1 if the active device has no such neighbour. Pair with OverlayTouchText to flash a value (e.g. "75 %") on the slot adjacent to the rotary that fired the command, without hard-coding per-device geometry.
RequestExclusiveMode(provider) Asks the host to hand the active device to provider for a full-device takeover. Returns false if another provider already owns the device (the host never steals); on success it calls provider.OnEnter() before returning true.
ReleaseExclusiveMode(provider) Releases exclusive mode. A no-op unless provider is the current owner; on a match the host calls provider.OnExit() and restores the normal page.
IsInExclusiveMode true while any provider currently owns the active device.

Keep the IPluginHost you received in Initialize for the plugin's lifetime — do not try to access host services from a static field or before Initialize runs.

Flashing a value on the rotary's neighbour slot

The combination of CommandContext.SourceIndex, GetTouchSlotForRotary, and OverlayTouchText is the canonical way to show transient feedback (volume, brightness, zoom level) next to the rotary the user just turned, without dedicating a touch button to it:

public Task Execute(CommandContext ctx)
{
    var newValue = AdjustValue(ctx);

    if (ctx.SourceIndex is int rotaryIdx)
    {
        var slot = ctx.Host.GetTouchSlotForRotary(rotaryIdx);
        if (slot >= 0)
            ctx.Host.OverlayTouchText(slot, $"{newValue} %", TimeSpan.FromSeconds(1.5));
    }
    return Task.CompletedTask;
}

IPluginLogger

public interface IPluginLogger
{
    void Info(string message);
    void Warn(string message);
    void Error(string message, Exception? exception = null);
}

All log output is scoped to the owning plugin. Use Error with the actual exception object (not just ex.Message) so the host can record the full stack trace.

Always log inside Execute. Exceptions thrown from Execute are caught by the host but appear as a generic execution failure without context. Wrap risky calls in try/catch and call Logger.Error("what was being attempted", ex) yourself.

IPluginSettings

public interface IPluginSettings
{
    T?                  Get<T>(string key, T? defaultValue = default);
    void                Set<T>(string key, T value);
    bool                Contains(string key);
    void                Remove(string key);
    IEnumerable<string> Keys { get; }
    void                Save();
}

Isolated per-plugin store backed by plugins/<plugin-id>/settings.json. Plugins never write into the core's config.json.

  • Set<T> updates the in-memory state; nothing is persisted until Save() is called.

  • Get<T> returns defaultValue when the key is absent or the stored value cannot be deserialized to T.

  • JSON numeric values come back as long / double — read PluginSettingKind.Number values with Get<long>("key").

  • Keys enumerates every key currently present, in undefined order. Use it to discover prefixed entries the schema doesn't list — e.g. aliases stored as alias:<id>, per-account tokens stored as token:<account> — and iterate without hard-coding the suffixes:

    foreach (var key in host.Settings.Keys.Where(k => k.StartsWith("alias:")))
        RegisterAlias(key["alias:".Length..], host.Settings.Get<string>(key)!);

Example

public override void Initialize(IPluginHost host)
{
    _host = host;
    _endpoint = host.Settings.Get<string>("endpoint", "http://localhost:4455")!;
    _pollSec  = host.Settings.Get<long>("pollSec", 5L);
}

private void UpdateEndpoint(string newEndpoint)
{
    _host.Settings.Set("endpoint", newEndpoint);
    _host.Settings.Save();
}

For user-editable settings rendered by the host, expose them via IPluginSettingsPage — the host then reads and writes the same IPluginSettings keys for you.

DeviceInfo

public sealed record DeviceInfo(string Name, string VendorId, string ProductId, string Slug);

Read-only description of the currently driven device. The Slug is a stable, filesystem-safe identifier (use it when caching per-device state). Plugins use this to adapt to hardware variants without referencing any core type.

Clone this wiki locally