Skip to content

feat(niri-displays): add per-output enable/disable toggle - #665

Draft
jrohland wants to merge 2 commits into
noctalia-dev:mainfrom
jrohland:feat/niri-displays-output-toggle
Draft

feat(niri-displays): add per-output enable/disable toggle#665
jrohland wants to merge 2 commits into
noctalia-dev:mainfrom
jrohland:feat/niri-displays-output-toggle

Conversation

@jrohland

@jrohland jrohland commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Plugin

  • Id: raycursive/niri-displays
  • New plugin
  • Update to an existing plugin (version bumped in plugin.toml)

What it does

Adds a per-output Enabled toggle to each display card, so an output can be
switched off and back on without leaving Noctalia. It runs Niri's existing
temporary output IPC:

niri msg output <port> off
niri msg output <port> on

The last enabled output is protected — its toggle is locked and relabelled
"Only display", so a session can never be left without a screen. A disabled
output keeps its card and stays togglable, while its mode, refresh-rate,
rotation and scale controls remain greyed out.

This also fixes a latent draft bug the feature exposes: a disabled output
reports 0,0 geometry, and syncFromState only seeded positionDraft when it
was nil, so re-enabling a display left the layout editor's Apply pointing at
0,0. Drafts are now reset on the disabled → enabled transition.

No service change was needed: normalizeOutputs already derives enabled from
the JSON, and disabled outputs stay listed by niri msg outputs with
"current_mode": null, "logical": null.

External dependencies

None beyond the existing niri dependency already declared in plugin.toml.
The only new command is niri msg output <port> on|off, shell-quoted through
the plugin's existing quoteShellArgument and dispatched via the existing
runNiri wrapper.

Testing

Tested against a live 3-output Niri session (DP-1 5120x1440, DP-7 1920x1080,
eDP-1 disabled), with the plugin loaded from a local path plugin source.

  • Verified niri msg output eDP-1 on / off round-trips, with
    niri msg --json outputs showing logical flipping between non-null and
    null.

  • Opened the panel and confirmed the Enabled toggle renders and reflects state
    for both enabled and disabled outputs, and that a disabled output's other
    controls stay greyed while its toggle stays live.

  • Dumped the service snapshot via
    noctalia msg plugin raycursive/niri-displays:displays all dump and confirmed
    enabled is correct per output.

  • Ran the repo validator (.github/workflows/scripts/validate-plugins.py) —
    clean.

  • Tested on Niri

  • Tested on Hyprland

  • Tested on Sway

  • Tested on another compositor:

  • Noctalia version tested against: v5.0.1

  • Plugin API level: 9

Screenshots / Videos

Checklist

  • The directory name matches the part of id after the / in plugin.toml exactly.
  • 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 and launcher prefixes where applicable.
  • thumbnail.webp is present and relevant; for a new plugin I created it with the thumbnail generator, and for an update I regenerated it with the generator if the visual identity or user-facing appearance changed.
  • version follows semver and is bumped in this PR; plugin_api is the oldest API level this plugin requires.
  • Every non-English translation in this PR uses a locale supported by Noctalia core, and I can read, write, and
    understand that language well enough to review and maintain it (no unreviewed machine/LLM translations).
  • I did not edit catalog.toml; CI generates it.
  • This PR touches exactly one plugin directory.

Code review attestation

  • 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.
  • I have the right to publish this code under the license declared in plugin.toml.

Each display card gains an "Enabled" toggle that runs
`niri msg output <port> on|off`, reusing the existing runNiri wrapper so
it inherits the in-flight lock, stderr surfacing, and refresh nonce.

The last enabled output is protected: its toggle is locked and relabelled
"Only display" so a session can never be left without a screen. Disabled
outputs keep their card and stay togglable while their mode, rotation and
scale controls remain greyed.

Also resets the scale and position drafts when an output transitions back
to enabled. A disabled output reports 0,0 geometry, and syncFromState
only seeded positionDraft when it was nil, so re-enabling a display left
the layout editor's Apply pointing at 0,0.

No service change is required: normalizeOutputs already derives `enabled`
from the JSON, and disabled outputs stay listed by `niri msg outputs`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Noctalia-CI

Copy link
Copy Markdown
Contributor

CC @raycursive: this pull request was automatically moved to draft until you have had a chance to look at it. It will be marked ready for review automatically once you reply here.

The toggle was renamed from "Power" to "Enabled" to line up with Niri's
own wording and the existing Disabled badge, but the README still
described it as Power.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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