LoupixDeck is a Cross-platform (Linux/Windows) GUI application β open-source alternative to Loupedeck software for Loupedeck Live S and Razer Stream Controller.
Built with Avalonia and .NET 9, it provides a fully customizable interface to design multi-layered touch buttons, assign commands, drive external tools (OBS, Elgato, Cooler Control, Argus Monitor, β¦) and manage dynamic page layouts for both touchscreen and rotary inputs.
LoupixDeck is experimental but actively developed. Features evolve quickly between releases; expect the occasional rough edge. Bug reports and PRs are welcome.
Distro-agnostic one-liner β downloads the latest release binary, installs it system-wide,
sets up udev rules and a desktop entry, and installs the .NET 9 runtime if it's missing.
Works on Arch/CachyOS/Manjaro, Debian/Ubuntu/Mint/Pop!_OS, Fedora/RHEL, openSUSE,
Alpine, Void, Gentoo, Solus (anything with one of the common package managers; falls
back to Microsoft's dotnet-install.sh otherwise).
curl -fsSL https://raw.githubusercontent.com/RadiatorTwo/LoupixDeck/master/install-loupixdeck.sh | bashOr with wget:
wget -qO- https://raw.githubusercontent.com/RadiatorTwo/LoupixDeck/master/install-loupixdeck.sh | bashPrefer to inspect first:
curl -fsSLO https://raw.githubusercontent.com/RadiatorTwo/LoupixDeck/master/install-loupixdeck.sh
less install-loupixdeck.sh
bash install-loupixdeck.shAfter install, launch with loupixdeck or from your application menu.
| Device | VID:PID | Layout |
|---|---|---|
| Loupedeck Live S | 2ec2:0006 |
5Γ3 touch grid, 2 rotary encoders, 8 physical buttons |
| Razer Stream Controller | 1532:0d06 |
4Γ3 touch grid, 2 side panels, 6 rotary encoders, 8 LED buttons |
Only one device is driven per app session. Each supported model keeps its own persisted
configuration (config_loupedeck-live-s.json, config_razer-stream-controller.json, β¦),
so switching the connected hardware and restarting the app picks up the matching layout
automatically. See Registry/DeviceRegistry.cs.
- Layer-based editor β stack image, text, and symbol layers per button.
- Live 600Γ600 preview with direct manipulation: drag layers, resize via corner handles,
hold
Shiftto unlock aspect ratio, holdAltto crop instead of scale. - Per-page wallpaper β each touch page can have its own tiled background image with independent opacity control.
- Visual touch feedback β optional colored, translucent flash overlay on the pressed slot; color and opacity are configurable in the settings.
- Touch-sliding prevention β when enabled (default), sliding a finger across the touchscreen will not trigger neighbouring buttons; toggleable in the settings.
- Content-addressed asset store β image assets are deduplicated automatically.
- Searchable browser for the bundled Material Design Icons font.
- Assign any glyph to a symbol layer with custom color, size, and position.
- Independent rotary pages with per-rotation, per-click, and per-press commands.
- Multi-command sequences per action.
- Per-button vibration patterns driven by the device's native DRV2605 haptic driver
(firmware op-codes reverse-engineered β see
docs/NATIVE_HAPTIC.md). - Up to two chained effects per button with configurable strength and delay.
- Vibration stops on touch release.
- Huge thanks to @Athorus for the reverse-engineering work that made native haptic support possible.
The Command menu is filtered per OS β Windows-only and Linux-only commands only appear where they apply.
- Shell β execute arbitrary shell commands.
- Macros β visual macro editor with keyboard, mouse, delay and command steps; injection via
uinput(Linux) or SendInput / Interception (Windows, see Third-Party Software below). See Macro Editor for a full description. - OBS Studio β start/stop recording, virtual camera, replay buffer, scene switching (via obs-websocket).
- Elgato Key Lights β toggle, brightness, color temperature, hue, saturation (auto-discovered via Zeroconf).
- Cooler Control β set fan/cooling modes via the Cooler Control daemon API.
- Argus Monitor (Windows) β CPU/GPU temperatures, clocks, multipliers and other sensors as dynamic text.
- Windows Audio (Windows) β input/output device folder with volume and mute controls on rotary encoders.
- Page navigation β next/previous/go-to page for touch and rotary pages.
- Device control β Device OFF (blank display + LEDs), Device ON, Toggle, Wake-up, Show/Hide window.
- Button control β update button content or remove layers at runtime (also via the CLI channel).
- Enable When OFF β individual touch buttons, physical buttons and rotary inputs can be flagged to stay active while the device is OFF, so controls like "Device ON" or "Toggle window" remain reachable on a blanked deck.
- Per-page command wraps β each touch and rotary page can define pre- and post-commands
that get chained around every button command on that page (
pre && command && post), with independent enable flags so you can park a definition without losing the text.
- Independent page sets for touch buttons and rotary encoders.
- Per-device configuration files β when a different supported model is connected, its saved layout is loaded on the next start.
- Optional pre- and post-execution commands per page.
- Automatic page switching β when the OS foreground window changes, the deck switches to a bound touch (and optionally rotary) page, so the right controls follow whatever app you're in.
- Rule-based binding β a simple editor on the App Switching settings page lets you map rules (process name, optional title substring) to page indices; first match wins, so rule order is priority. An optional fallback page handles the no-match case.
- Stays out of the way β switching is skipped while the device is off, a plugin folder is open, or a plugin holds exclusive mode; rapid Alt-Tab is debounced and re-focusing the same app causes no flicker.
- Platforms β Windows (via
SetWinEventHook) and Linux under X11/XWayland (viaxprop, part ofx11-utils). Pure Wayland sessions are not covered (no common focus protocol) and fall back to a silent no-op; the settings page stays available either way.
- System tray with Device On/Off toggle and window visibility.
- D-Bus notifications (Linux).
- Windows audio control via WASAPI (Windows).
- Suspend/resume awareness β the device is blanked when the OS suspends and reconnected/restored automatically on resume (Linux via logind, Windows via power events).
- CLI channel β Unix domain socket (Linux) / named pipe (Windows) for external scripts to update buttons, switch pages, or trigger commands while the app is running.
- Single-instance enforcement on both platforms.
- Sidebar-driven navigation across settings categories.
- Dedicated editors for touch wallpaper, haptic effects, rotary assignments, device colors, app-focus page switching, OBS/Elgato/Argus integrations, and more.
LoupixDeck is extensible through third-party plugins that contribute their own commands, dynamic text providers, and settings UI to the main app. Plugins are discovered from a per-user plugin directory at startup and integrate seamlessly into the command picker and menu tree.
The plugin SDK β including interfaces, base classes, and documentation for building your own plugins β is developed in a separate repository:
π github.com/RadiatorTwo/LoupixDeck.PluginSdk
It is published as the LoupixDeck.PluginSdk NuGet package; reference it from your plugin
project to get started.
The visual macro editor lets you build named, reusable macros from a sequence of typed steps. Open it via Commands β Macros β Edit Macros in any button's command picker.
| Step | Description |
|---|---|
| Text | Type a string β each character is injected as individual key presses. |
| Key Combination | Send a chord (e.g. Ctrl+Shift+Esc). |
| Key Down / Key Up | Hold or release a single key β useful for custom press/release timings. |
| Mouse | Click, press/release a button, move (relative or absolute), or scroll. Supports left, right, and middle buttons. |
| Delay | Pause execution for a configurable number of milliseconds. |
| Command | Run any LoupixDeck command (shell, OBS, page navigation, β¦) as a macro step. |
After creating a macro in the editor, it appears under Commands β Macros in every command
picker. Assign it to a touch button, a rotary press, or a physical button exactly like any
other command. Macros are stored in macros.json in the user-config directory and are shared
across all devices and pages.
| Platform | Backend | Notes |
|---|---|---|
| Linux | uinput |
Requires /dev/uinput access (see Build Instructions). |
| Windows | SendInput (default) |
Works with most applications. |
| Windows | Interception driver (optional) | Driver-level injection for raw-input apps / games. See Third-Party Software. |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Requires the .NET 9 SDK.
git clone https://github.com/RadiatorTwo/LoupixDeck.git
cd LoupixDeck
dotnet publish LoupixDeck.csproj -c Release -r linux-x64 --self-contained true \
/p:PublishSingleFile=true \
/p:PublishTrimmed=false \
/p:EnableCompressionInSingleFile=true \
/p:ReadyToRun=true \
-o publish/linux-x64On Linux, the macro command relies on uinput; make sure your user has access to /dev/uinput
(for example via a udev rule or by adding the user to the input group).
If the serial device for your deck is not accessible without sudo, add a udev rule
matching the device's USB VID/PID. Example for the Loupedeck Live S
(/etc/udev/rules.d/99-loupixdeck.rules):
SUBSYSTEM=="usb", ATTRS{idVendor}=="2ec2", ATTRS{idProduct}=="0006", MODE="0666"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2ec2", ATTRS{idProduct}=="0006", MODE="0666"
For the Razer Stream Controller replace 2ec2:0006 with 1532:0d06. Reload with
sudo udevadm control --reload-rules && sudo udevadm trigger and reconnect the device.
git clone https://github.com/RadiatorTwo/LoupixDeck.git
cd LoupixDeck
dotnet publish LoupixDeck.csproj -c Release -r win-x64 --self-contained true `
/p:PublishSingleFile=true `
/p:PublishTrimmed=false `
/p:EnableCompressionInSingleFile=true `
/p:ReadyToRun=true `
-o publish/win-x64LoupixDeck auto-detects supported devices by USB VID/PID on startup. If exactly one supported
device is connected, it is used immediately; subsequent launches remember the last-used model
via an .active-device marker file. The setup dialog (serial port + baud rate) only appears
when auto-detection is ambiguous β multiple supported devices without a saved choice, or no
device detected and no existing configuration.
Configuration is stored as JSON files in the user-config directory:
config.jsonβ global application settings.config_<device-slug>.jsonβ per-device layout (touch pages, rotary pages, button colors, β¦).obs.json,elgato.json, β¦ β integration-specific data.
If a config file becomes corrupted it is backed up automatically before a fresh one is written.
While LoupixDeck is running, external scripts can drive it via a local IPC channel β useful for build status indicators, scene-aware overlays, system events, or anything that should poke the deck without going through the device. Commands are dispatched on the UI thread; the channel replies with a short status line.
| Platform | Endpoint |
|---|---|
| Linux | Unix domain socket /tmp/loupixdeck_app.sock |
| Windows | Named pipe LoupixDeck_Pipe |
If LoupixDeck is already running, launching the same binary again with arguments forwards them
to the running instance and exits β no separate CLI tool, no nc, no PowerShell glue needed:
Linux
./LoupixDeck nextpage
./LoupixDeck page 3
./LoupixDeck updatebutton 6 text=Build_OK backColor=LimeGreen
./LoupixDeck System.ObsStartRecordWindows (PowerShell)
.\LoupixDeck.exe nextpage
.\LoupixDeck.exe page 3
.\LoupixDeck.exe updatebutton 6 text=Build_OK backColor=LimeGreenHex colors like
#00AA33are valid, but in a real shell you must quote them ('backColor=#00AA33'in bash,"backColor=#00AA33"in PowerShell) β otherwise the#starts a comment.
The reply from the running instance is printed to stdout. Exit code is non-zero if the channel can't be reached. The second instance neither opens a window nor touches the device.
echo 'nextpage' | nc -U /tmp/loupixdeck_app.sock
echo 'page 3' | nc -U /tmp/loupixdeck_app.sock
echo 'updatebutton 6 text=Build_OK backColor=#00AA33' | nc -U /tmp/loupixdeck_app.sockUseful when piping content into the channel or when LoupixDeck is not on PATH.
$pipe = New-Object System.IO.Pipes.NamedPipeClientStream(".", "LoupixDeck_Pipe", "InOut")
$pipe.Connect(2000)
$bytes = [System.Text.Encoding]::UTF8.GetBytes("nextpage")
$pipe.Write($bytes, 0, $bytes.Length); $pipe.WaitForPipeDrain()
$buf = New-Object byte[] 256
$n = $pipe.Read($buf, 0, $buf.Length)
[System.Text.Encoding]::UTF8.GetString($buf, 0, $n)
$pipe.Dispose()| Input | Effect |
|---|---|
on / off / on-off / toggle-device |
Device power (display + LEDs) |
wakeup |
Reconnect serial and turn device on |
nextpage / previouspage |
Cycle touch pages |
nextrotarypage / previousrotarypage |
Cycle rotary pages |
page<N> (e.g. page3) |
Jump to touch page N |
rotarypage<N> |
Jump to rotary page N |
show / hide / toggle |
Main window visibility |
quit |
Quit LoupixDeck |
For dynamic content like build status, monitoring badges, or live counters:
updatebutton <index> [text=<value>] [textColor=<color>] [backColor=<color>] [image=<path>]
removelayer <index> <layerName>
<index>is the button index on the current touch page.- Use
_for spaces in values (text=Build_OKβBuild OK). - Colors accept hex (
#FF8800) or Avalonia color names (Red,LimeGreen). image=clearremoves the image.
Anything that is not a known shortcut is forwarded verbatim, so the full System.* syntax works:
echo 'System.ObsStartRecord()' | nc -U /tmp/loupixdeck_app.sock
echo 'System.GotoPage(2)' | nc -U /tmp/loupixdeck_app.sock
echo 'System.UpdateButton(6,text=Hi,backColor=Red)' | nc -U /tmp/loupixdeck_app.sockImplementation: Program.cs (CommandChannel.Dispatch).
LoupixDeck can write a crash log to help track down a hard-to-reproduce crash. It is off by default β pass a command-line switch when starting the binary to turn it on:
| Switch | Effect |
|---|---|
--crashlog |
Log unhandled managed exceptions (on any thread, including background/timer threads) to crash.log with a full stack trace. |
--firstchance |
Additionally log every thrown exception, even ones that are caught. Very noisy β useful to capture the last exception before a crash. Implies --crashlog. |
# Linux
./LoupixDeck --crashlog
# Windows (PowerShell)
.\LoupixDeck.exe --crashlogThe log is written to ~/.config/LoupixDeck/crash.log (the same folder as the config and the
startup log), so it works even for installed builds where the program folder is read-only.
Native crashes: a pure native access violation (e.g. inside SkiaSharp) fast-fails the runtime and is not captured by
--crashlog. For those, run with the .NET minidump environment variables instead:DOTNET_DbgEnableMiniDump=1,DOTNET_DbgMiniDumpType=2,DOTNET_DbgMiniDumpName=<path>. The two are complementary β--crashlogfor managed crashes, the minidump for native ones.
The optional Windows Macro Driver feature uses the Interception kernel driver by Francisco Lopes to inject keyboard and mouse input at driver level, so macros also reach applications that read raw input (games / anti-cheat).
- The driver is not bundled with LoupixDeck. It is downloaded from the official GitHub release only when you choose to install it from the settings ("Macro Driver" page).
- Interception is dual-licensed: it is free for non-commercial use only. Commercial use requires a separate license from its author β see the Interception repository for details.
- Without the driver, macros fall back to the standard
SendInputAPI and remain fully functional (injected input just may not reach raw-input applications).
Released under the MIT License.
The license above covers LoupixDeck itself. Third-party components (such as the optional Interception driver) are subject to their own licenses β see Third-Party Software above.







