Skip to content

Add DS4 Color plugin (Hy4ri/ds4-color)#52

Merged
ItsLemmy merged 19 commits into
noctalia-dev:mainfrom
Hy4ri:feat/ps4-colors
Jul 19, 2026
Merged

Add DS4 Color plugin (Hy4ri/ds4-color)#52
ItsLemmy merged 19 commits into
noctalia-dev:mainfrom
Hy4ri:feat/ps4-colors

Conversation

@Hy4ri

@Hy4ri Hy4ri commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Plugin

  • Id: Hy4ri/ds4-color
  • New plugin
  • Update to an existing plugin (version bumped in plugin.toml)

What it does

Sets the DualShock 4 lightbar color from a Noctalia bar button and a color panel. Left-click the bar button applies the last saved color directly; right-click opens a panel with a native color picker, a hex field, and 9 presets. Save persists the chosen color (so left-click reapplies it later) without touching the controller; Apply sets the lightbar immediately and also saves it.

The entire DualShock 4 HID output-report protocol is implemented in pure Lua — no external binary or library. The plugin detects controllers by scanning /sys/class/hidraw/*/device/uevent for Sony VID 054c / PIDs 05c4 09cc 0ba0, builds the USB report (0x05, 32 B) and Bluetooth report (0x11, 78 B with CRC32), and writes them to /dev/hidrawN.

External dependencies

None. dependencies = [] in plugin.toml. The final byte write uses Noctalia's writeFile, with a python3 O_WRONLY fallback for the char device (only python3 is used, no downloaded/remote code).

Testing

  • Tested on Niri
  • Tested on Hyprland
  • Tested on Sway
  • Tested on another compositor:
  • Noctalia version tested against: current main (plugin_api 3)
  • Plugin API level: 3

Manually exercised: bar widget left-click applies saved color; right-click opens panel; preset click + Apply sets the lightbar on a connected DS4 (USB and Bluetooth); Save persists without writing; IPC noctalia msg plugin Hy4ri/ds4-color:service all apply <hex> confirmed.

Screenshots / Videos

thumbnail.webp is included (panel screenshot).

Checklist

  • The directory name matches the part of id after the / in plugin.toml exactly (ds4-color/).
  • It ships plugin.toml, README.md, thumbnail.webp, and translations/en.json.
  • README.md follows the README template, documents every entry id and dependency, and includes exact panel IPC commands.
  • I created thumbnail.webp with the thumbnail generator (used a screenshot conversion instead — see note above).
  • version follows semver (0.1.0) and is bumped in this PR; plugin_api is the oldest API level this plugin requires (3).
  • Every non-English translation in this PR uses a locale supported by Noctalia core (only en.json shipped; no machine translations).
  • I did not edit catalog.toml; CI generates it.
  • This PR touches exactly one plugin directory (ds4-color/).

Code review attestation

Plugins run as trusted, unsandboxed Luau in the user's session. Confirm:

  • The code is readable and not obfuscated, minified, or generated.
  • It does not download and execute remote code.
  • Every network call, filesystem write, and spawned process is something the description above accounts for (only python3 spawned for the raw hidraw write; no network calls).
  • I have the right to publish this code under the license declared in plugin.toml (MIT).

Hy4ri and others added 16 commits July 18, 2026 23:06
Wraps the ps4-colors CLI (github.com/Hy4ri/ps4-colors) as a Noctalia
plugin: bar button + color panel (native picker, hex field, presets)
that sets the DualShock 4 lightbar via the ps4-colors binary.

NOTE: thumbnail.webp still pending (generated in noctalia thumbnail
generator) — CI will flag the missing asset until added.
960x540 WebP, deep crimson (#990000) to match the plugin accent.
- widget: left-click applies the saved color via service; right-click opens panel
- service: add 'save' IPC (persist without touching controller)
- panel: Save (persist) + Apply (set now) buttons
- translations: add 'save' key
The plugin now writes the HID output report to /dev/hidrawN directly via
noctalia.writeFile. USB report 0x05 (32B) and Bluetooth report 0x11 (78B)
with CRC32 are built and the CRC verified against the kernel algorithm.
Device detection scans /sys/class/hidraw uevent for Sony DS4 PIDs.
No external binary or library required.
Move preset-click handler registration into onOpen (after presets are
loaded) instead of module top-level, where ipairs(nil) threw and aborted
the whole panel entry. Guard presets with 'or {}'. Replace unsupported
width='100%' string with a numeric width on the swatch.
Remove border='primary' (unknown value -> whole tree abort) and wrap=true.
Swatch now uses border='outline' + borderWidth to show selection, mirroring
oldirtty/color_picker. Aligns every ui.* node with a known-good panel.
- Resolve pluginDataDir lazily + nil-guard lastFile() so the service entry
  actually loads (top-level concat on nil threw -> no onIpc -> no apply ->
  no notification, which is why the color never changed).
- setLightbar tries noctalia.writeFile, falls back to a python3 O_WRONLY
  binary write for the /dev/hidrawN char device (writeFile is file-oriented).
Noctalia's Lua is strict (PUC-Rio semantic), not Luau: '&', '|', '~', '>>'
are syntax errors -> service failed to load at crc32_init -> no apply.
Added band/rshift/bxor/bnot32 math helpers; CRC32 + BT report now use them
and still produce the verified CRC 523bce6f.
Noctalia's _G is immutable at runtime, so assigning _G['onPreset'..i] in
onOpen threw 'attempt to modify a readonly table' and aborted the panel.
Presets are a fixed list, so define onPreset1..9 as plain module globals
calling a shared applyPreset(i). Remove dead makePresetHandler.
'textAlign' is a label prop, not an input prop; Noctalia ignored it with a
[WRN] ui-tree log line. Input defaults to left align anyway.
'controller' glyph was missing; swap to device-gamepad-2 for both the
plugin catalog icon and the bar widget default glyph.
Rename plugin id, display name, bar glyph default, translation key
(ps4_colors -> ds4_color) and all internal IPC references. Drop the stale
ps4_colors_missing string (plugin has no external binary dependency).
Keep accurate comments crediting the original ps4-colors C source.
…tch)

Save/Apply buttons were already the last row (bottom of the column); this
just compacts the overall panel height.
…[a-z0-9._-]*$)

'Hy4ri' -> 'hy4ri' in id, author, and all internal IPC references.
@ItsLemmy

Copy link
Copy Markdown
Contributor
  1. blocking - ds4-color/plugin.toml:10
    ds4-color/service.luau:175
    ds4-color/README.md:21

The plugin declares no dependencies and documents that none are required, but its HID write fallback invokes python3. If the direct write fails, systems without Python cannot apply the color. Declare and document python3, or remove the external fallback.

  1. blocking - ds4-color/service.luau:207
    ds4-color/widget.luau:13
    ds4-color/panel.luau:9

The value loaded from last.json is not validated before entering shared state, then is inserted between single quotes in commands executed through /bin/sh -c. A crafted persisted value can escape the quotes and execute arbitrary shell commands when the widget or panel action is used. Validate loaded values as exactly six hexadecimal characters and avoid unsafe shell interpolation.

@ItsLemmy
ItsLemmy marked this pull request as draft July 18, 2026 23:39
Hy4ri added 2 commits July 19, 2026 04:36
Reviewer flagged: plugin declared dependencies=[] but the hidraw write fallback
invokes python3, so systems without Python cannot apply the color if the
direct writeFile fails. Declare 'python3' in dependencies, note it in the
manifest description, and document the requirement in README Requirements.
Reviewer flagged: last.json value entered shared state unvalidated, then got
single-quote-interpolated into /bin/sh -c via runAsync -> shell injection.

- Add isHex6() (exactly ^[0-9a-fA-F]{6}$) and enforce it at: loadLast
  (poisoned last.json), onSubmitHex, onApply/onSave (from state), onIpc
  apply/save, and the apply()/save() service funcs.
- Since only 6 hex chars can reach runAsync, the single-quote interpolation
  is provably safe (no quote-breaker / metachar can survive).
- Also fixes a latent bug: the old %x regex captured only 5 hex, rejecting
  valid 6-char colors like 990000.
@Hy4ri
Hy4ri marked this pull request as ready for review July 19, 2026 02:05
Removed mention of 'hidraw write fallback' from the description.
@ItsLemmy

Copy link
Copy Markdown
Contributor

Thanks

@ItsLemmy
ItsLemmy merged commit 5fb0e0f into noctalia-dev:main Jul 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants