Skip to content

Changelog

RadiatorTwo edited this page Jun 28, 2026 · 4 revisions

Changelog

Release notes for LoupixDeck.PluginSdk. The version surfaced at runtime is SdkInfo.Version; the host enforces a major-version match against the value declared in plugin.json (sdkVersion).

1.12.0

Animated touch buttons. A command can now drive a touch button from the host's central animation scheduler at a target frame rate, instead of the fixed polling interval used by IDisplayImageCommand. The plugin draws each frame onto the same IRenderCanvas (90×90) and tells the host whether anything changed, so unchanged ticks cost no device I/O.

New contracts (additive — existing plugins unaffected)

Type Purpose
IAnimatedDisplayCommand Optional, independent command interface for a genuinely animated touch button. Exposes TargetFps and RenderAnimatedFrame(ctx, canvas, frame). A command may implement both this and IDisplayImageCommand — the host prefers the animated path.
AnimationFrameContext Per-frame timing snapshot: FrameNumber, Elapsed, Delta, EffectiveFps. Drive the visual from Elapsed so playback stays correct even when the host clamps the rate.
AnimationFrameInfo Return value of RenderAnimatedFrame. Factory helpers Skip() (nothing changed), Frame(n) (drew, continue) and Final(n) (last frame of a one-shot). The frame number is the host's dirty key for the device push.

This is a purely additive release: the assembly's Major is unchanged and no existing member changed, so every 1.x plugin keeps loading and rebuilding is optional — only a command that wants animation needs the new interface. (The release also fixes an MSB4044 pack error when the core app is published; that is build-tooling only and has no effect on the plugin contract.)

1.11.0

Canvas-based rendering. Plugin render paths no longer return PNG bytes — the host hands the plugin an IRenderCanvas it draws onto with the host's own primitives, so plugin text and symbols match the core font and symbol library. DrawImage(bytes) remains as the full-custom escape hatch. (Versions 1.9.0 and 1.10.0 were development steps folded into this release.)

New contracts

Type Purpose
IRenderCanvas The drawing surface: Clear; rectangles, rounded rectangles, circles, ellipses, arcs, lines; DrawText (centered or TextHAlign/TextVAlign) + MeasureText; DrawSymbol (tint or SymbolStyle); DrawImage (aspect-fit, + opacity/tint overload with same-array decode caching); a PushTransform/PopTransform/Translate/Rotate/Scale stack.
SymbolStyle Outline / drop-shadow / linear-gradient / rotation styling for the DrawSymbol overload.
TextHAlign / TextVAlign Independent horizontal/vertical text alignment for the box DrawText overload.

Changed contracts (rebuild required)

Member Change
IDisplayImageCommand byte[]? GetImage(ctx) + string? GetText(ctx) replaced by bool RenderImage(CommandContext ctx, IRenderCanvas canvas). The command's output is now a live, host-owned touch-button layer (movable/scalable in the editor, content read-only).
ISideStripSession.RenderStrip byte[]? RenderStrip()bool RenderStrip(IRenderCanvas canvas).
ISegmentStripSession.RenderSegment byte[]? RenderSegment(int)bool RenderSegment(int rotaryIndex, IRenderCanvas canvas).

PluginColor gained Transparent. The assembly's Major is unchanged, so non-rendering plugins keep loading; only a plugin that implements one of the three changed render members must rebuild against 1.11.0.

1.8.0

Side strips. A plugin can render and own a Razer Stream Controller side display strip (or a single dial segment of one) while the rest of the device keeps working. See Side Strips. (Version 1.7.0 was skipped.)

New contracts

Type Purpose
ISideStripProvider Discoverable, stateless renderer + session factory bound to a strip in plugin-override mode. Id (persisted, orphan-safe) + Title + CreateSession.
ISideStripSession One live per-side attachment: RenderStrip, StripChanged, OnStripTapped, OnStripSwiped; IDisposable.
ISegmentStripProvider / ISegmentStripSession Render an individual dial's strip segment in the host's segmented mode (RenderSegment); the rest stay host-rendered.
SideStripContext / SideStripRotary Strip geometry (Side/Width/Height), the adjacent dials' labels + bound commands, and RequestNextPage/RequestPreviousPage callbacks.
StripSide / StripSwipeDirection Which strip a session drives (Left/Right); vertical swipe direction (Up/Down).

LoupixPlugin additions

Member Purpose
virtual IEnumerable<ISideStripProvider> GetSideStripProviders() Returns the plugin's side-strip providers; collected after Initialize alongside GetCommands. Defaults to none.

1.6.0

Full-device takeover: a plugin can now seize the entire device for a HUD, overlay, screensaver, or video, and pick how the host pushes frames to it. (Version 1.5.0 was skipped — there is no 1.5.0 release.)

New contracts

Type Purpose
IExclusiveModeProvider Plugin-supplied controller for the global exclusive mode. While active, the host suppresses page mappings, freezes folder navigation, and routes every hardware input to the provider. RenderMode and SingleTileSlot are default-implemented, so the contract is forward-compatible.
ExclusiveRenderMode How the host pushes a provider's frames: FullScreen (default — one composited blit + DRAW), Grid, DirtyTiles (only changed tiles), SingleTile. The lever for higher frame rates.

IPluginHost additions

Member Purpose
bool RequestExclusiveMode(IExclusiveModeProvider provider) Hands the active device to provider. Returns false if another provider already owns it (no stealing); on success the host calls provider.OnEnter() before returning true.
void ReleaseExclusiveMode(IExclusiveModeProvider provider) Releases exclusive mode. No-op unless provider is the current owner; on a match the host calls provider.OnExit() and restores the normal page.
bool IsInExclusiveMode { get; } true while any provider currently owns the active device.

1.4.0

Host surface for browser launches and transient touch feedback, plus two forward-compatible command contracts.

IPluginHost additions

Member Purpose
bool OpenBrowser(string url) Opens url in the user's default browser. Host abstracts OS specifics (Windows shell-execute, Linux xdg-open) so OAuth flows don't need per-plugin platform branches.
void OverlayTouchText(int slot, string text, TimeSpan duration) Temporarily paints text on the touch slot at slot and restores the slot's normal content after duration. Fire-and-forget; later calls on the same slot supersede pending restores.
int GetTouchSlotForRotary(int rotaryIndex) Returns the touch slot adjacent to the rotary at rotaryIndex, or -1 when the active device has no such neighbour. Pair with OverlayTouchText for value-flash feedback without hard-coding device geometry.

CommandContext additions

Member Purpose
int? SourceIndex Identifier of the originating control when Target denotes an indexed source (rotary index for RotaryEncoder, touch slot for TouchButton, simple-button index for SimpleButton). null for chained or CLI invocations.

New command contracts (forward-compatible)

Type Purpose
IAdjustmentCommand Rotary-encoder value adjustment — turn invokes ApplyAdjustment, press invokes ApplyReset. Safe to implement now; the host wires it up when available.
IDisplayImageCommand Renders a dynamic PNG on a touch button. Safe to implement now alongside IDisplayCommand for graceful fallback.

1.3.0

Display-only setting headers and persisted-key enumeration.

PluginSettingKind additions

Member Purpose
Heading Display-only section header in dynamically built settings pages. Carries no value; used to group related controls.

IPluginSettings additions

Member Purpose
IEnumerable<string> Keys { get; } Enumerates every key currently present, in undefined order. Use it to discover prefixed entries — e.g. Keys.Where(k => k.StartsWith("alias:")) — without tracking them in a separate index.

Clone this wiki locally