add niri-outputs plugin to manage displays in niri#217
add niri-outputs plugin to manage displays in niri#217adrlau wants to merge 1 commit intonoctalia-dev:mainfrom
Conversation
niri-outputs/BarWidget.qml
Outdated
| readonly property real barHeight: Style.getBarHeightForScreen(screenName) | ||
| readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) | ||
|
|
||
| implicitWidth: capsuleHeight |
There was a problem hiding this comment.
NIconButton would be better here, it would respect all the capsule and hover state automatically.
e6ee1de to
e7c0a57
Compare
|
Used the NICONButton like you said. |
niri-outputs/BarWidget.qml
Outdated
| applyUiScale: false | ||
| icon: "device-desktop" | ||
| tooltipText: "Display Configuration" | ||
| tooltipDirection: BarService.getTooltipDirection() |
There was a problem hiding this comment.
you need BarService.getTooltipDirection(screenName) (for different bars per screen)
|
|
||
| colorBg: Style.capsuleColor | ||
| colorFg: Color.mOnSurface | ||
| colorBorder: "transparent" |
There was a problem hiding this comment.
You alsso need, So it follows styling
border.color: Style.capsuleBorderColor
border.width: Style.capsuleBorderWidth
fix: correctly access main instance in panel and add debug logging feat: Add niri outputs plugin # new file: niri-outputs/i18n/ru.json
There was a problem hiding this comment.
Pull request overview
Adds a new Noctalia Shell plugin (niri-outputs) that provides a GUI (bar widget + control center widget + panel) for managing Niri compositor outputs via niri msg.
Changes:
- Introduces plugin metadata, widgets, and a full panel UI for output arrangement and configuration.
- Adds a command-based backend (
niri msg --json outputs+ command runner) to read/apply output settings. - Adds README + preview image + multi-language i18n strings for the panel.
Reviewed changes
Copilot reviewed 23 out of 24 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| niri-outputs/preview.png | Adds plugin preview image for the registry/site. |
| niri-outputs/manifest.json | Declares plugin metadata and entry points. |
| niri-outputs/README.md | Documents features and usage. |
| niri-outputs/Main.qml | Implements Niri msg integration (read/apply settings). |
| niri-outputs/CommandRunner.qml | Lightweight Process wrapper for running commands. |
| niri-outputs/BarWidget.qml | Bar entry point button to open the panel. |
| niri-outputs/ControlCenterWidget.qml | Control-center entry point button to open the panel. |
| niri-outputs/Panel.qml | Main UI: layout preview + snapping + per-output settings + apply/discard workflow. |
| niri-outputs/i18n/*.json | Adds translations for panel strings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "author": "adrlau", | ||
| "official": false, | ||
| "license": "MIT", | ||
| "description": "Manage niri outputs: scale, mode, and more.", | ||
| "tags": ["Utility", "Bar", "Panel"], |
There was a problem hiding this comment.
manifest.json is missing the repository field. All other plugin manifests in this repo include it, and the repo-level README lists it as a required manifest property; omission may break registry tooling/metadata expectations.
| import QtQuick | ||
| import Quickshell | ||
| import qs.Commons | ||
| import qs.Widgets |
There was a problem hiding this comment.
BarWidget.qml uses BarService.getTooltipDirection(...) but does not import the module that provides BarService (other bar widgets import qs.Services.UI). This will cause a runtime ReferenceError when the widget loads.
| import qs.Widgets | |
| import qs.Widgets | |
| import qs.Services.UI |
| colorBorder: "transparent" | ||
| colorBorderHover: "transparent" | ||
|
|
||
| onClicked: pluginApi.openPanel(root.screen, this) |
There was a problem hiding this comment.
onClicked: pluginApi.openPanel(...) will throw if pluginApi is not set (unlike ControlCenterWidget.qml, which guards it). Please add a null-safe check (or use optional chaining) before calling into pluginApi.
| onClicked: pluginApi.openPanel(root.screen, this) | |
| onClicked: if (pluginApi) pluginApi.openPanel(root.screen, this) |
| Component.onCompleted: refresh() | ||
|
|
||
| function refresh() { | ||
| niriMsg.running = true; | ||
| } |
There was a problem hiding this comment.
refresh() unconditionally sets niriMsg.running = true. If the process is already running, Quickshell may ignore the assignment and the refresh request will be dropped. Other plugins guard with if (!process.running) process.running = true; (or restart explicitly).
| }); | ||
|
|
||
| discardChanges(); | ||
| } |
There was a problem hiding this comment.
applyChanges() calls discardChanges() immediately after issuing the niri msg commands. Since each command runner also triggers refresh() on exit, this extra immediate discard/refresh can temporarily reload old state and cause redundant refreshes. Consider delaying discard/refresh until after commands complete (or skipping refresh in this path).
| function discardChanges() { | ||
| pendingChanges = { positions: {}, scales: {}, modes: {}, transforms: {} }; | ||
| if (pluginCore) pluginCore.refresh(); | ||
| } |
There was a problem hiding this comment.
discardChanges() refreshes outputs unconditionally. When invoked from applyChanges(), this can cause redundant refreshes because each command runner already refreshes on process exit. Consider making refresh optional or only refreshing when explicitly discarding (not after apply).
| var updated = JSON.parse(JSON.stringify(dragPositions)); | ||
| updated[outputName] = { x: x, y: y }; | ||
| dragPositions = updated; | ||
| dragPositionsVersion++; | ||
| } | ||
|
|
||
| function clearDragPosition(outputName) { | ||
| var updated = JSON.parse(JSON.stringify(dragPositions)); |
There was a problem hiding this comment.
updateDragPosition() deep-copies dragPositions via JSON.parse(JSON.stringify(...)) on every drag movement. This runs at pointer-move frequency and can cause UI jank/GC churn. A shallow copy (or in-place mutation plus an explicit version counter) is sufficient here because values are primitives.
| var updated = JSON.parse(JSON.stringify(dragPositions)); | |
| updated[outputName] = { x: x, y: y }; | |
| dragPositions = updated; | |
| dragPositionsVersion++; | |
| } | |
| function clearDragPosition(outputName) { | |
| var updated = JSON.parse(JSON.stringify(dragPositions)); | |
| var updated = {}; | |
| for (var key in dragPositions) { | |
| if (dragPositions.hasOwnProperty(key)) { | |
| updated[key] = dragPositions[key]; | |
| } | |
| } | |
| updated[outputName] = { x: x, y: y }; | |
| dragPositions = updated; | |
| dragPositionsVersion++; | |
| } | |
| function clearDragPosition(outputName) { | |
| var updated = {}; | |
| for (var key in dragPositions) { | |
| if (dragPositions.hasOwnProperty(key)) { | |
| updated[key] = dragPositions[key]; | |
| } | |
| } |
| if (text.trim() === "") return; | ||
| try { | ||
| root.outputs = JSON.parse(text); |
There was a problem hiding this comment.
If niri msg --json outputs produces no stdout (common when the command fails and only writes to stderr), this handler returns early and can leave available/outputs stale from a previous successful run. Consider setting available = false (and clearing outputs) when text.trim() === "", and/or capturing stderr/exit code for better diagnostics.
| if (text.trim() === "") return; | |
| try { | |
| root.outputs = JSON.parse(text); | |
| const trimmed = text.trim(); | |
| if (trimmed === "") { | |
| // Command produced no JSON output; clear any stale state. | |
| root.available = false; | |
| root.outputs = ({}); | |
| console.warn("niri msg --json outputs produced no stdout; marking outputs unavailable."); | |
| return; | |
| } | |
| try { | |
| root.outputs = JSON.parse(trimmed); |
|
Do you plan to finish this? Even if I dislike copilot review, some of those need to be addressed |
|
Yes, just hadn't had much time right now. |
Adds a gui to rearrange displays in niri, using the niri msg command interface.
Ai tools where used to develop this, but it is tested and seemed to work nice.