***
> [!NOTE]
> This repo has been archived by the author. It's no longer receiving updates and remains public just for reference. Please continue to respect [LICENSE](LICENSE).
***
# Omgato
[](https://github.com/data-goblin/omgato/releases)
[](LICENSE)
[](https://omarchy.org)
An unofficial Omarchy plugin for controlling studio hardware: lights,
stream deck, CamLink and teleprompter.
<p align="center">
<img src="docs/images/demo.gif" alt="Switching the camera overlay on from the panel, watching it park clear, raising one Key Light and dimming the other, restoring the saved default, then switching everything back off" width="700">
</p>
<p align="center"><a href="docs/images/demo.mp4">Watch the same demo as video</a></p>
<p align="center">
<img src="docs/images/lights.png" alt="Key Lights view" width="250">
<img src="docs/images/deck.png" alt="Stream Deck view" width="250">
<img src="docs/images/camera.png" alt="Cam Link view" width="250">
</p>
> [!NOTE]
> I am not a programmer. I used Claude Code and Codex to build this for myself. The code and docs are fully generated by AI.
> Excuse my ignorance to etiquette or common practice here. Any feedback and contributions welcome!
---
## Supported hardware
Legend: `✓` yes, `✗` no, `?` plausible but unconfirmed.
I could only test hardware that I own.
> [!NOTE]
> If you own hardware that is not supported, please feel free to test, add it,
> and submit a PR.
### Stream Deck
Layout comes from the `elgato-streamdeck` crate, so every model it recognises is
laid out correctly with no change here. All are USB vendor `0fd9`.
| Device | Grid | USB ID | Supported | Tested | What the plugin does with it |
| --- | :---: | :---: | :---: | :---: | --- |
| Stream Deck MK.2 | 5x3 | `0080` | ✓ | ✓ | Key grid, drag to rearrange, pages, multi-key editing, brightness, display power |
| Stream Deck | 5x3 | `0060` | ✓ | ✗ | Same grid and paging as the MK.2 |
| Stream Deck V2 | 5x3 | `006d` | ✓ | ✗ | Same grid and paging as the MK.2 |
| Stream Deck Scissor Keys | 5x3 | `00a5` | ✓ | ✗ | Same as the MK.2; only the key switches differ |
| Stream Deck Mini | 3x2 | `0063` | ✓ | ✗ | Three by two grid, paging, same editing surface |
| Stream Deck Mini MK.2 | 3x2 | `0090` | ✓ | ✗ | Three by two grid, paging, same editing surface |
| Stream Deck Mini: Discord Edition | 3x2 | `00b3` | ✓ | ✗ | Driven identically to the Mini |
| Stream Deck XL | 8x4 | `006c` | ✓ | ✗ | Eight by four grid, paging, same editing surface |
| Stream Deck XL V2 | 8x4 | `008f` | ✓ | ✗ | Eight by four grid, paging, same editing surface |
| Stream Deck Neo | 4x2 | `009a` | ✓ | ✗ | Key grid and paging; the two touch keys are not addressable |
| Stream Deck + | 4x2 | `0084` | ✓ | ✗ | Key grid and paging; dials and touch strip are not addressable |
| Stream Deck Pedal | 3 | `0086` | ✓ | ✓ | Tap, hold and double bindings for each of the three pedals |
| Stream Deck Module (6 keys) | 3x2 | `00b8` | ✓ | ✗ | OEM panel-mount module, driven as its desktop equivalent |
| Stream Deck Module (15 keys) | 5x3 | `00b9` | ✓ | ✗ | OEM panel-mount module, driven as its desktop equivalent |
| Stream Deck Module (32 keys) | 8x4 | `00ba` | ✓ | ✗ | OEM panel-mount module, driven as its desktop equivalent |
| Stream Deck + XL | 9x4 | `00c6` | ✗ | ✗ | Not yet in the device library, so nothing here recognises it |
| Stream Deck Studio | 16x2 | `00aa` | ✗ | ✗ | Not in the device library; the rack unit is unrecognised |
| Stream Deck Mobile, Virtual Stream Deck | - | - | ✗ | ✗ | Software, not USB hardware; out of scope |
Auto-pagination puts its arrows at the two ends of the bottom row, which is
sensible on every grid but has only been seen on a 5x3.
### Lights
Discovered over mDNS as `_elg._tcp` and driven over the local `/elgato/lights`
HTTP API on port 9123. The plugin reads and writes `on`, `brightness` and
`temperature`, so temperature-based lights are fully covered and hue-based ones
are not.
| Device | Colour model | Supported | Tested | What the plugin does with it |
| --- | :---: | :---: | :---: | --- |
| Key Light | Temperature | ✓ | ✓ | Power, brightness, temperature, saved default, rename and reorder |
| Key Light Air | Temperature | ✓ | ✓ | Power, brightness, temperature, saved default, rename and reorder |
| Key Light Mini | Temperature | ✓ | ✗ | Same API; discontinued; battery level is not read |
| Ring Light | Temperature | ✓ | ✗ | Power, brightness and temperature; discontinued |
| Light Strip | Hue | ✓ | ✗ | Power and brightness only; it is hue based, so no colour control |
| Key Light Neo | Temperature | ? | ✗ | Local HTTP API is not confirmed on this model |
| Key Light Air MK.2 | Temperature | ? | ✗ | Local HTTP API is not confirmed on this model |
| Light Strip Pro | Hue, addressable | ? | ✗ | Per-LED addressing does not fit the flat API this plugin speaks |
Three of these are marked `?` because the vendor does not document the local API and
the newer models are absent from every source that lists it. One command settles
it on your own hardware, and the answer is worth an issue either way:
```bash
curl -s http://<light-ip>:9123/elgato/lights
```
### Cameras
`camlink-ctl` matches `/dev/v4l/by-id` against a `device_pattern`, so any device the
kernel exposes as a UVC webcam can drive the overlay. You can change it to something else
by editing `~/.config/camlink-ctl/config.toml`:
```toml
device_pattern = "Facecam"
```
| Device | Supported | Tested | What the plugin does with it |
| --- | :---: | :---: | --- |
| Cam Link 4K | ✓ | ✓ | Overlay placement by corner or dragged area, plus recording |
| Facecam MK.2 | ✓ | ✗ | UVC 1.5; set `device_pattern` and the overlay works |
| Facecam 4K | ✓ | ✗ | UVC 1.5; set `device_pattern` and the overlay works |
| Facecam Neo | ✓ | ✗ | UVC 1.5; set `device_pattern` and the overlay works |
| Facecam | ✓ | ✗ | UVC; discontinued |
| Facecam Pro | ✓ | ✗ | UVC; discontinued |
| Cam Link Pro | ✗ | ✗ | PCIe card exposing DirectShow, not UVC; no Linux support |
### Capture cards
These present as video devices too, so the same overlay and recording path
applies where the kernel binds them.
| Device | Supported | Tested | What the plugin does with it |
| --- | :---: | :---: | --- |
| Game Capture HD60 X | ✓ | ✗ | UVC compatible; usable as an overlay source |
| Game Capture 4K X | ✓ | ✗ | UVC; needs the mainline `USB_QUIRK_NO_BOS` fix for full bandwidth |
| Game Capture 4K S | ✓ | ✗ | UVC compliant; usable as an overlay source |
| Video Capture | ✓ | ✗ | Analogue capture bound by `em28xx` or `cx231xx` in mainline |
| Game Capture Neo | ? | ✗ | Plug and play, but UVC is not documented |
| Game Capture 4K Pro | ✗ | ✗ | PCIe with a proprietary Windows-only driver |
### Audio
Nothing in this plugin yet drives audio hardware.
| Device | Supported | Tested | Why not |
| --- | :---: | :---: | --- |
| Wave:3, Wave:3 MK.2 | ✗ | ✗ | USB audio class; capture works, hardware controls do not |
| Wave XLR, Wave XLR MK.2, Wave XLR Pro | ✗ | ✗ | Interface controls need a protocol this plugin does not speak |
| Wave Neo | ✗ | ✗ | USB audio class; capture works, hardware controls do not |
| Wave DX | ✗ | ✗ | A passive XLR microphone; there is nothing to control |
| XLR Dock MK.2 | ✗ | ✗ | Attaches to a Stream Deck +, but exposes no controls here |
### Teleprompter
| Device | Supported | Tested | Notes |
| --- | :---: | :---: | --- |
| Prompter | ✓ | ✓ | Through the DisplayLink driver; tested on an NVIDIA desktop |
| Prompter XL | ? | ✗ | Same DisplayLink path, untested |
The Prompter has no video input: it is a DisplayLink screen over USB-C, which
only the closed Synaptics driver can talk to. Install the AUR packages
`evdi-dkms` and `displaylink`, then the udev rule the installer prints. The rule
moves the virtual DisplayLink card off your seat, so Hyprland never opens it
(on NVIDIA, Hyprland cannot draw to it and would leave a dead monitor), and it
starts `prompter-ctl.service` whenever the Prompter appears.
`prompter-ctl` then owns the glass in one of three modes:
```yaml
script: draws a saved script itself, white on black, with a marker on the
reading line; scrolls only while playing, so it idles at no cost
monitor: adds a 1024x600 Hyprland output named PROMPTER and copies it to the
glass, so any window can go there
off: blanks the glass and removes the PROMPTER output
```
Brightness dims the picture rather than the backlight: the DisplayLink driver
passes no DDC/CI, so the Prompter's backlight cannot be reached from Linux.
Scripts live in `~/.local/share/prompter-ctl/scripts/` as Markdown. Write them in
the panel, or open them in the Omarchy editor from it; saving the loaded script
redraws the glass in place. For monitor mode, give the output its size in
`~/.config/hypr/monitors.lua`:
```lua
hl.monitor({ output = "PROMPTER", mode = "1024x600@60", position = "auto", scale = 1 })
```
Stream Deck and Pedal keys can drive it: `exec:prompter-ctl toggle` starts and
stops scrolling, and `exec:prompter-ctl back` and `exec:prompter-ctl forward` step
one line, for example on the left and right Pedal taps.
## Install
```bash
omarchy plugin add https://github.com/data-goblin/omgato --enable
~/.config/omarchy/plugins/io.github.data-goblin.omgato/scripts/install
streamdeck-ctl enable
```
`omarchy plugin add` only clones the plugin and places the widget on the right of
the bar. `scripts/install` is what builds the workspace, links the five binaries
onto PATH, installs the systemd user units, and applies a starter Stream Deck
layout **only if you have no configuration yet**; pass `--no-preset` to skip it.
It links the agent skill only when you pass `--with-skill`, or run
`scripts/install-skill` yourself. It prints the one privileged step it will not
take for you: installing the udev rule.
## Update
Omarchy installs plugins as git checkouts and never pulls them, so an update is
two steps. The second one rebuilds the binaries, and skipping it leaves you
running the old ones:
```bash
omarchy plugin update io.github.data-goblin.omgato
~/.config/omarchy/plugins/io.github.data-goblin.omgato/scripts/install
```
## Remove
Order matters. `scripts/uninstall` uses the binaries to undo their own Hyprland
edits, so run it before the plugin directory goes away:
```bash
~/.config/omarchy/plugins/io.github.data-goblin.omgato/scripts/uninstall
omarchy plugin remove io.github.data-goblin.omgato
```
What survives on purpose: your Stream Deck and Pedal configuration, your light
names and display order, and the udev rule at
`/etc/udev/rules.d/70-streamdeck-ctl.rules`, which needs root to remove. Pass
`--purge` to `scripts/uninstall` to drop the saved state as well.
## Lights, defaults and undo
Ten changes of undo history are kept, and separately you can pin one arrangement
as the default and come back to it whenever a session has wandered:
```bash
omgato-panel save-default # remember the lights exactly as they are
omgato-panel restore-default # put every light back to it
omgato-panel undo # or step back one change at a time
```
Both are buttons in the Key Lights view. Restore is greyed out until a default
has been saved.
## Colouring a page
A page can name its own background, which every key on that page picks up
unless it sets `bg` itself:
```bash
streamdeck-ctl deck page-bg media '#221a2c'
streamdeck-ctl deck page-bg media # back to the deck-wide bg_color
```
```toml
[deck.pages.media]
bg = "#221a2c"
```
A tint per page makes the current page obvious at a glance. Generated
pagination keys and empty keys deliberately keep the deck-wide `bg_color`, so
they stay visually separate from the page's own keys.
Because the colour lives on the page rather than on every button, a script can
recolour the deck without touching the layout, for instance to follow the
desktop theme from a hook.
## Rearranging deck keys
Drag a key in the grid and drop it where you want it. Dropping on an empty key
moves it there; dropping on another key swaps the two. Nothing is retyped, and
the deck's undo button steps the move back. The same thing from a script:
```bash
streamdeck-ctl deck move omarchy 3 7 # 3 and 7 trade places
streamdeck-ctl deck move omarchy 3 0 --to-page apps
```
A click still selects a key for editing, so only a drag of a few pixels starts a
move, and only real keys move; the paging arrows are generated, not configured.
`streamdeck-ctl tui` has the same move on the Deck tab: `m` picks the selected
key up, the arrows and `[` `]` choose where it lands, including on another page,
then `Enter` drops it and `Esc` puts it back.
## Following the Omarchy theme
The panel already retints itself when the theme changes. The keys can too:
```bash
streamdeck-ctl deck follow-theme true # opt in; off by default
streamdeck-ctl deck theme # apply now
streamdeck-ctl deck theme --dry-run # print the colours, change nothing
streamdeck-ctl deck follow-theme false # opt out and remove the hook
```
Opting in installs a `theme-set` hook through `omarchy-hook-install`, so from
then on the deck recolours itself whenever you switch theme. Opting out removes
it again.
Colours come from `omarchy-theme-color`, so they resolve exactly as every other
Omarchy consumer sees them. The deck background and text follow the theme's
background and foreground; each page takes a colour of its own.
Choosing those page colours is the fiddly part. Reading four fixed palette keys
falls apart on a theme whose palette is warm or monochrome throughout, where
they all land on the same colour. So the whole palette is considered and the
entries furthest apart perceptually are chosen. Where a palette genuinely holds
nothing distinct, Vantablack say, the pages are separated by weight instead,
one accent laid on progressively thicker. Either way each colour is eased back
toward the background if it would not leave the label readable.
`theme_strength` in the config, or `--strength`, sets how strongly a colour
tints its page. It defaults to 0.38; lower is subtler.
```bash
streamdeck-ctl deck theme --dry-run --strength 0.5
streamdeck-ctl deck theme --dry-run --colors ~/.config/omarchy/themes/nord/colors.toml
```
## The camera overlay and the panel
The panel opens over the top-right corner, which is where the camera overlay
usually sits. Rather than hiding it, the panel tells `camlink-ctl` the rectangle it
occupies and the overlay slides clear, returning to its own position when the
panel closes. A placement that does not overlap is left exactly where you put
it, and an overlay switched on while the panel is already open still lands clear
of it.
The overlay switch is the toggle in the top right of the Cam Link view.
## Settings
Each section can be switched off, so a rig with only Key Lights shows only Key
Lights. Set them in the widget settings, or from a script:
```bash
omarchy bar set io.github.data-goblin.omgato showDeck false --json
```
| Setting | Default | What it controls |
| --- | :---: | --- |
| `showLights` | `true` | Brightness, temperature and per-light control for Key Lights |
| `showDeck` | `true` | Key grid, pages and pedal bindings for a Stream Deck |
| `showCamera` | `true` | Camera overlay placement and quick screen recording |
| `showPrompter` | `true` | Prompter scripts, scrolling, mirroring and monitor mode |
## Layout
```
manifest.json plugin manifest, entry point and settings schema
ui/Panel.qml the bar widget and panel
src/keylight-ctl/ Key Lights over their local HTTP API
src/streamdeck-ctl/ Stream Deck and Pedal daemons, rendering, TUI, preset
src/camlink-ctl/ Cam Link overlay and status
src/prompter-ctl/ Prompter daemon: scripts, scrolling, monitor mode, udev rule
src/omgato-panel/ status aggregation, names, shortcuts, undo/redo history
src/skill/ the agent skill describing the tools
scripts/install build, link, install units; the skill with --with-skill
scripts/install-skill link only the agent skill
scripts/uninstall reverse the installer
```
`omgato-panel` is the only crate the panel asks for state. It gathers one JSON
document from the other four tools, keeps local light names and display order
keyed by MAC so a DHCP lease change cannot orphan them, and owns the undo
histories. Device commands go straight from the panel to
`keylight-ctl`, `streamdeck-ctl` and `camlink-ctl`, so nothing sits between a click and
the hardware.
## Shortcuts
The plugin writes `~/.config/hypr/omgato-bindings.lua` and adds a single guarded
line to your `bindings.lua` to source it. Your own bindings file is never
rewritten, and removing the plugin cannot break your config. Install them from
the panel, or:
```bash
omgato-panel install-shortcuts
omgato-panel set-shortcut --id lights.toggle --keys "SUPER + ALT + L"
omgato-panel uninstall-shortcuts
```
Only a handful are bound out of the box: one for the lights, none for the deck,
and the camera and recording ones. Everything else is listed unbound in the
panel, ready to be given a combination. Shortcuts are listed per section,
rebindable from the panel, and warn you when a combination is already claimed.
## What it installs, and where
Plugins run unsandboxed with your user permissions, so here is everything this
one touches outside its own directory.
```yaml
~/.local/bin/: symlinks to the five binaries it builds
~/.config/systemd/user/: streamdeck-ctl.service and streamdeck-ctl-deck.service,
the Stream Deck and Pedal daemons, and prompter-ctl.service,
started only by its udev rule
~/.local/share/prompter-ctl/scripts/: your Prompter scripts
~/.local/state/prompter-ctl/settings.json: Prompter mode, script, speed, size, mirror
$XDG_RUNTIME_DIR/prompter-ctl/: the Prompter control socket, mode 0700
~/.config/hypr/: omgato-bindings.lua, plus one guarded pcall line added
to bindings.lua, only when you install shortcuts
~/.local/state/omgato-panel/: light display names, display order, undo history
~/.cache/omgato-panel/: rendered key previews
~/.config/streamdeck-ctl/config.toml: your deck and pedal configuration
~/.config/omarchy/hooks/theme-set.d/omgato-deck.hook: recolours the deck on a
theme change, only after `deck follow-theme true`;
`deck follow-theme false` or scripts/uninstall removes it
$XDG_RUNTIME_DIR/camlink-ctl/: overlay position and pid, cleared on reboot
agent skill directories: a symlink to src/skill/, only when you pass --with-skill
or run scripts/install-skill, and only where the
directory already exists
```
`scripts/uninstall` reverses all of it. It keeps your device configuration
unless you pass `--purge`.
### Privileges
No sudoers rule ships with the plugin, and `scripts/install` performs no
privileged step itself. Three operations need root:
- installing the udev rule that grants access to Stream Deck hardware. The
installer prints these and leaves them to you:
```bash
sudo install -m 0644 \
~/.config/omarchy/plugins/io.github.data-goblin.omgato/src/streamdeck-ctl/udev/70-streamdeck-ctl.rules \
/etc/udev/rules.d/70-streamdeck-ctl.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
```
- installing the Prompter udev rule, only if you have a Prompter. It assigns
the DisplayLink card to a seat of its own, hands it to your user and starts
`prompter-ctl.service`. The installer prints it with your user name filled in:
```bash
sed "s/@USER@/$USER/" \
~/.config/omarchy/plugins/io.github.data-goblin.omgato/src/prompter-ctl/udev/72-prompter-ctl.rules \
| sudo tee /etc/udev/rules.d/72-prompter-ctl.rules >/dev/null
sudo udevadm control --reload && sudo systemctl restart displaylink
```
- re-authorizing the Cam Link over USB, which clears the wedged state the device
enters when its capture stream is cut. This is the one place the code calls
`sudo` for you: `camlink-ctl` runs
`sudo -n tee /sys/bus/usb/devices/<dev>/authorized`, writing `0` and then `1`.
It fires on `camlink-ctl reset`, and again inside `camlink-ctl show` when the
previous session left the device wedged. The `-n` means it never prompts, so
with no cached credential and no passwordless rule of your own the reset fails,
a warning goes to stderr, and the overlay starts anyway.
Runtime pid state lives in `$XDG_RUNTIME_DIR/camlink-ctl/`, created mode 0700.
With `XDG_RUNTIME_DIR` unset it falls back to a uid-suffixed directory under the
temp dir, also 0700.
### Network and processes
`keylight-ctl` talks HTTP to Key Lights on your local network, discovered over mDNS
with `avahi-browse` (`_elg._tcp`). No other code in the plugin makes network calls.
Discovery accepts at most 32 unique services from 256 KiB of browse output, and
each HTTP JSON response is limited to 16 KiB. Device names, addresses and the
64 KiB cache are validated before retention. The cache is opened without
following symlinks or blocking on special files, then accepted only when that
same descriptor is a regular file owned by the current user. Live probes use a
fixed pool of at most four threads, so an untrusted or noisy LAN peer cannot grow
memory, cached device state or worker creation without bound.
The daemons are ordinary systemd user services; no second Quickshell process is
ever started.
## Requirements
```yaml
Omarchy Quattro: the Quickshell shell this plugin draws into
Rust 1.89+: to build the workspace (rust)
avahi-browse: Key Light discovery over mDNS (avahi)
mpv: the Cam Link overlay window (mpv)
pw-dump: PipeWire state for camera status (pipewire)
gpu-screen-recorder: screen recording (gpu-screen-recorder)
ffmpeg: recording post-pass (ffmpeg)
slurp: region picking for the overlay and recording (slurp)
fonts: a text font and a Nerd Font for rendering deck keys;
fontconfig supplies a stand-in if the configured paths
do not exist
systemd --user: the Stream Deck, Pedal and Prompter daemons
evdi-dkms, displaylink: the Prompter only (AUR); displaylink is closed source
fc-match: the Prompter script font (fontconfig)
```
Package names in brackets are the Arch packages that provide each binary.
## Troubleshooting
```yaml
No Stream Deck found: the udev rule is missing. Install it as shown above,
then unplug and replug the device
Keys never update: the daemons are not running. Check with
systemctl --user status streamdeck-ctl-deck
No lights discovered: run keylight-ctl discover. Key Lights answer over mDNS,
so the machine must be on the same subnet as the lights
A light reads unreachable: probes retry inside a 500ms budget, then every command
rediscovers over mDNS once and retries against the
addresses it finds, so a DHCP lease change heals itself.
A light that still reports unreachable is genuinely not
answering. Confirm with keylight-ctl ls --json
Camera overlay is black: the Cam Link is single-open. If a user service holds
it, the overlay borrows the device and gives it back
on hide. A plain process is named rather than killed.
If it stays wedged, camlink-ctl reset
```
## Contributing
Issues and pull requests are welcome, particularly for hardware not marked tested
above. Please run `cargo clippy --release --workspace` and `cargo build --release`
before opening one. Release history is in [CHANGELOG.md](CHANGELOG.md).
## Licence
MIT. See [LICENSE](LICENSE).
Elgato, Stream Deck, Key Light and Cam Link are trademarks of Corsair Gaming and
its Elgato brand, which does not sponsor or endorse this project.
This is an unofficial, community-built plugin and is not affiliated with Elgato.