Omahub
← All plugins
V

Trofeo Vision

by Vladimir Cotric

Thermalright Trofeo Vision 9.16 LCD in the Omarchy bar: animated GIF backgrounds, live CPU/GPU/memory telemetry, and whole-machine RGB through OpenRGB.

Security review

Potentially dangerous behavior detected · 6 findings

Deterministic scan — not a security guarantee

High
Risk level
High
Analyzed commit
a4cbab9
Scanned
1 month ago
  • high persistence bin/trofeo-install:89

    Registers scheduled or boot-time system tasks.

    systemctl disable --now openrgb.service >/dev/null 2>&1 || true
  • high persistence bin/trofeo-install:167

    Registers scheduled or boot-time system tasks.

    systemctl disable --now openrgb.service"
  • high persistence bin/trofeo-install:128

    Bundles a systemd unit file.

    [Unit]
  • high persistence bin/trofeo-install:175

    Bundles a systemd unit file.

    [Unit]
  • Command runs with sudo, elevating the process beyond the plugin environment.

    sudo systemctl disable --now openrgb.service"
  • Docs sudo README.md:341

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo rm /etc/udev/rules.d/60-trofeo.rules /etc/modules-load.d/trofeo-openrgb.conf

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
a4cbab9
Reviewed
1 month ago

The deterministic scan's high/medium findings are mostly false positives: the systemctl/systemd operations are part of a transparent, user-invoked installer that writes udev rules and user systemd services, and the flagged `sudo` command appears in a README uninstall note and a warning string, not in an executed path. The code is straightforward, contains no obfuscation or hidden exfiltration, and the root elevation happens only when the user explicitly runs `trofeo install` with a password prompt.

  • The installer runs as root via pkexec to install packages, write a udev rule, and load the i2c-dev kernel module; this is expected for the hardware but should be reviewed and is only executed by explicit user action.
  • The plugin installs and enables two systemd user services, including an unauthenticated OpenRGB server bound to 127.0.0.1; this is disclosed in the README and can be masked by the user, but it is still a local attack surface.
  • Deterministic findings flag normal systemd unit creation and a display-only sudo string; those are not evidence of malicious behavior.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/thedarkcr0w/omarchy-trofeo --enable
Appearance #system

Trofeo Vision

Drives the LCD on a Thermalright Trofeo Vision cooler from Linux, and lights the rest of the machine to match.

Thermalright ships a Windows app for these screens and nothing at all for Linux, so the panel sits there dark on an otherwise finished build. This runs it properly: animated GIFs, video, and a live readout of what the machine is actually doing, at the panel's native 1920x462. The same daemon drives every RGB controller OpenRGB can see, so the case and the screen are one thing rather than two apps that disagree.

Roughly what InfoPanel does on Windows, except the panel is a systemd user service and everything it can do is one trofeo command away.

The control panel: backgrounds, stats overlay, frame rate and system lighting.

Hardware

Built for and tested on a Trofeo Vision 9.16 (0416:5408, 1920x462, reporting PM 65 / SUB 5). The 0416:5409 variant is handled by the same code path but I have not had one to try.

The panel is a vendor-class USB device with two bulk endpoints; it is not a display the compositor knows about, and nothing here touches your monitors. If lsusb shows 0416:5408 or 0416:5409, this should drive it. Other Thermalright screens speak different protocols and will not work — thermalright-trcc-linux covers far more of them.

Requirements

  • Omarchy 4 (Quattro shell)
  • python-pyusb, python-pillow, python-psutil, openrgb, ffmpeg
  • Optional: zenity for the file picker, nvidia-smi for GPU telemetry

The installer pulls the packages in for you; nothing needs to be set up first.

Install

omarchy plugin add https://github.com/thedarkcr0w/omarchy-trofeo --enable
trofeo install

trofeo install is the part that needs your password, once. It installs the packages, writes a udev rule so the panel is reachable without root, loads i2c-dev so SMBus lighting has its character devices, registers the daemon and the OpenRGB server as user services, and links the trofeo command into ~/.local/bin.

Privileges

Nothing this plugin installs keeps running as root. The password buys a udev rule, a module and the packages; after that everything is your own session.

The elevated half is handed to bash as an argument rather than written to a file and run by path. A script sitting in /tmp — or anywhere else this account can write — can be rewritten between the moment the password prompt goes up and the moment root reaches the shebang, which turns "may I install a udev rule" into "may I run whatever is in that file as root". Read exactly what runs with:

trofeo install --print-root-script

The panel and the RGB controllers are reachable because udev hands them to whoever is logged in at the seat — the same uaccess tag that hands over webcams and joysticks. That is why the OpenRGB server is a user service and not a root one: /dev/i2c-*, hidraw and /dev/port already belong to the logged-in user through openrgb's own udev rules, so SMBus never needed root, and a bug in that server's protocol parser is a bug in a process holding nothing your session does not already hold.

What that leaves: the OpenRGB SDK is a TCP server with no authentication, so anything on the machine that can open a socket can drive your lighting while trofeo runs. It is bound to 127.0.0.1 — openrgb's own default is 0.0.0.0, which would put it on every interface — it starts and stops with the daemon, and it holds no privileges. It is still not access-controlled, because the protocol has nothing to control access with. If that is not a trade you want, systemctl --user mask trofeo-openrgb: the panel and its telemetry carry on, and only the lighting goes.

Using it

Interaction Result
Left click Open the control panel
Right click Screen on / off
Middle click Force a refresh
Scroll on icon RGB brightness ±5%
p (panel open) Screen on / off
r (panel open) Cycle lighting mode
Esc Close
SUPER + SHIFT + T Open the control panel

The bar icon is a display normally and a power symbol when the screen is off or the daemon cannot reach it, so panel state reads at a glance.

Backgrounds

Animations — .gif, .webp, .apng, played at their own frame timing. Video — .mp4, .mkv, .webm, .mov, .avi, .m4v, decoded once through ffmpeg and looped. Stills — .png, .jpg, .bmp.

Video is decoded at import, not played live: ffmpeg scales it and emits an MJPEG stream that goes straight into the frame cache, so playback costs the same as a GIF and nothing shells out per frame. The first 30 seconds are kept (rgb-style knobs panel.background.video_fps and video_max_frames), then the clip loops. A 4K source is scaled by ffmpeg, so it is never decoded at full size in Python.

The quickest route is the bar widget: click the panel icon, then “Add GIF or image…” at the top of the BACKGROUND row. Right-click a stored background to delete it. The same picker is at Omarchy menu → Trofeo Vision → Add background…, and files dropped into ~/.config/trofeo/gifs/ are picked up too.

trofeo gif add ~/Pictures/loop.gif --use   # import and show it
trofeo gif list                            # what is stored
trofeo bg loop --fit contain               # switch, and letterbox it
trofeo bg solid                            # back to a flat colour

Or use Omarchy menu → Trofeo Vision → Add background…, which opens a file picker, or just drop files into ~/.config/trofeo/gifs/.

--fit is cover (fill and crop, the default), contain (letterbox) or stretch. The panel is 4.16:1, so a square GIF loses a lot of its height under cover — contain is usually the better answer for one of those.

Overlays

trofeo layout          # list them, marking the active one
trofeo layout dual
Layout Shows
full CPU, GPU, memory, disk and network
dual CPU and GPU, at the largest type the panel carries
temps Thermals and VRAM
clock A big clock, letting the GIF have the panel
none Background only

Temperature readings shift from their metric's accent through amber to red as they climb, so a hot part is visible from across the room without reading the number.

Tune the look with trofeo scrim 60 (how dark the backdrop behind text is, 0–100) and trofeo accent '#ff6e50'.

Tiles lay themselves out along whichever axis is longer, so trofeo orientation 90 (a panel mounted on its side) stacks them into a column instead of crushing five of them into 76px slivers. Composing the transposed 1920-tall canvas costs about twice as much, so expect roughly half the frame rate at 90° and 270°.

Lighting

Every controller OpenRGB can see — motherboard, DRAM, GPU, coolers, peripherals.

trofeo rgb devices          # what OpenRGB found
trofeo rgb modes            # effects, and which are common to all controllers
trofeo rgb color orange     # or '#ff6e50', or '255,110,0'
trofeo rgb mode breathing
trofeo rgb brightness 60
trofeo rgb ambient          # follow the panel background
trofeo rgb off

Addressable headers (fans, strips)

An ARGB header is write-only — the board cannot tell whether three fans or nothing at all is plugged into it, so OpenRGB reports the zone as 0 LEDs and silently displays nothing you send. This is the usual reason fans stay dark while the GPU changes colour fine.

The daemon therefore gives every resizable zone a length on connect, rgb.default_argb_leds (24) unless you say otherwise:

trofeo rgb zone                  # what each header is set to
trofeo rgb zone ARGB_V2_1 36     # this header has 36 LEDs on it

Over-provisioning is the safe direction: a shorter strip ignores the extra pixels, so 24 on an 8-LED fan lights all 8. A count that is too low leaves the rest of the strip dark. Raise it until the whole chain lights.

DRAM lighting

DDR5 RGB sits behind the AMD SMBus, and ACPI claims that IO range by default:

ACPI Warning: SystemIO range 0x0B00-0x0B08 conflicts with OpRegion (\GSA1.SMBI)
ACPI: OSL: Resource conflict; ACPI support missing from driver?

i2c_piix4 then never registers an adapter and the DIMMs are invisible to every lighting tool. The fix is a kernel parameter, installed here as /etc/limine-entry-tool.d/98-smbus-rgb.conf:

KERNEL_CMDLINE[default]+=" acpi_enforce_resources=lax"

It needs a reboot. To undo it, delete that file and run limine-update.

Ambient is the one worth trying: the daemon samples the GIF frame it just drew and pushes that colour to the whole machine, so the case tracks whatever is on the screen. The sample is weighted toward vivid pixels rather than a flat mean — a dark GIF with one neon element lights the room that colour instead of muddy grey — then eased so it glides rather than strobes.

Command reference

trofeo status                    panel and lighting state
trofeo on | off | toggle         screen power
trofeo bg <name|path|solid>      background        [--fit cover|contain|stretch]
trofeo layout [name]             stats overlay
trofeo fps <n>                   target frame rate (1-60)
trofeo orientation <0|90|180|270>
trofeo quality <30-97>           JPEG quality
trofeo scrim <0-100>             overlay backdrop opacity
trofeo accent <colour>           overlay accent
trofeo gif add|list|rm           manage backgrounds
trofeo rgb zone [name] [count]   LEDs on an addressable header
trofeo rgb ...                   see above
trofeo preview [file.png]        render a frame to a file, no hardware needed
trofeo probe                     handshake the panel directly
trofeo get|set <path> [value]    raw config access
trofeo start|stop|restart|logs   the user service
trofeo install                   udev rule and services

How it works

trofeod ── render loop ── USB bulk ──> panel        (main thread)
   ├───── sensors ─────────────────────────────      (background thread)
   ├───── OpenRGB ────── TCP 6742 ──> controllers    (background thread)
   └───── unix socket <── trofeo / Panel.qml

The daemon owns all state. The CLI and the bar widget only ask it for things over $XDG_RUNTIME_DIR/trofeo.sock, so there is one writer and no way for the widget and a shell command to disagree about what is on screen.

The wire protocol. The panel is a vendor-class USB device with two bulk endpoints. A fixed 2048-byte handshake returns the geometry (this unit answers PM 65 / SUB 5 → 1920×462); frames are JPEG, sliced into 512-byte chunks behind a 16-byte header, with the chunk count padded to a multiple of four. Two things that are easy to get wrong:

  • The OUT endpoint is 0x09 on this unit, not the 0x01 the protocol notes imply, so endpoints are read off the descriptor.
  • At orientation 0 the frame is rotated 180° before encoding — the panel is mounted upside down relative to its framebuffer. An unrotated image shows up inverted on the cooler.

The protocol is a port of thermalright-trcc-linux (GPL-3.0), which reverse-engineered it from TRCC 2.1.2's USBLCDNEW.dll. This plugin carries the same licence.

Frame cost. Animated backgrounds are scaled once at import and held as JPEG bytes, one decoded per tick — a 120-frame GIF is ~14 MB in RAM instead of 320 MB raw. When neither the background nor the overlay text has changed the cached frame is resent as-is rather than recomposed, which is why a static background at full costs almost nothing between the once-a-second stat updates. The panel blanks if the USB link goes idle, so frames never stop entirely; they drop to 4/s.

If something is wrong

trofeo status          # first stop
trofeo logs -f         # what the daemon is doing
trofeo probe           # talk to the panel without the daemon
trofeo preview             # is it the render or the link?

"permission denied on the USB device" — the udev rule is missing, or you are not the logged-in user at the seat. The rule hands the panel to the active session rather than to everyone; run trofeo install and replug the panel.

"panel is busy" — something else holds it. The vendor app under Wine, or a second copy of the daemon.

"cannot reach the OpenRGB server" — systemctl --user status trofeo-openrgb. It starts with the daemon and stops with it, as you rather than as root. If it will not bind, something else already holds 6742: usually a system-wide openrgb.service or the OpenRGB GUI. Motherboard and DRAM lighting also want the i2c-dev module, which the installer loads and pins at boot.

The picture is upside down — the encode base is keyed on the handshake SUB byte, and a different SKU reports a different one. trofeo orientation 180 corrects it.

Fans or strips stay dark while the GPU works — the header is sized 0. See "Addressable headers" above; trofeo rgb zone is the knob.

RAM stays dark — check i2cdetect -l for an SMBus adapter. If there is none, the ACPI conflict above is why; that needs the kernel parameter and a reboot.

Files

~/.config/omarchy/plugins/io.github.thedarkcr0w.trofeo/   this plugin
~/.config/trofeo/config.json                     settings (the daemon owns this)
~/.config/trofeo/gifs/                           backgrounds
~/.config/systemd/user/trofeo.service            the daemon
~/.config/systemd/user/trofeo-openrgb.service    the lighting server
/etc/udev/rules.d/60-trofeo.rules                panel permissions
/etc/modules-load.d/trofeo-openrgb.conf          i2c-dev at boot

Removing it

trofeo stop
systemctl --user disable trofeo.service
omarchy plugin remove io.github.thedarkcr0w.trofeo --yes
rm ~/.local/bin/trofeo ~/.config/systemd/user/trofeo.service \
   ~/.config/systemd/user/trofeo-openrgb.service

That leaves two root-owned files the installer put down, if you want them gone too:

sudo rm /etc/udev/rules.d/60-trofeo.rules /etc/modules-load.d/trofeo-openrgb.conf

Your settings and backgrounds are in ~/.config/trofeo/; delete that as well for a clean sweep. The kernel parameter, if you added one for DRAM lighting, is /etc/limine-entry-tool.d/98-smbus-rgb.conf or your bootloader's equivalent.

Credits

The USB wire protocol is a port of thermalright-trcc-linux, which did the reverse engineering against TRCC 2.1.2's USBLCDNEW.dll. Without that work this would not exist. That project is GPL-3.0, so this one is too.

License

GPL-3.0-or-later. See LICENSE for the licence text and COPYRIGHT for the copyright and upstream attribution.