Skip to content

add niri-outputs plugin to manage displays in niri#217

Draft
adrlau wants to merge 1 commit intonoctalia-dev:mainfrom
adrlau:niri-outputs
Draft

add niri-outputs plugin to manage displays in niri#217
adrlau wants to merge 1 commit intonoctalia-dev:mainfrom
adrlau:niri-outputs

Conversation

@adrlau
Copy link

@adrlau adrlau commented Feb 5, 2026

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.

readonly property real barHeight: Style.getBarHeightForScreen(screenName)
readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName)

implicitWidth: capsuleHeight
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIconButton would be better here, it would respect all the capsule and hover state automatically.

@adrlau adrlau force-pushed the niri-outputs branch 2 times, most recently from e6ee1de to e7c0a57 Compare February 6, 2026 09:49
@adrlau
Copy link
Author

adrlau commented Feb 6, 2026

Used the NICONButton like you said.
Cleaned up the code a bit as well and added more localisations. (Also used the i18n in the code)

applyUiScale: false
icon: "device-desktop"
tooltipText: "Display Configuration"
tooltipDirection: BarService.getTooltipDirection()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need BarService.getTooltipDirection(screenName) (for different bars per screen)


colorBg: Style.capsuleColor
colorFg: Color.mOnSurface
colorBorder: "transparent"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copilot AI review requested due to automatic review settings February 9, 2026 22:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +6 to +10
"author": "adrlau",
"official": false,
"license": "MIT",
"description": "Manage niri outputs: scale, mode, and more.",
"tags": ["Utility", "Bar", "Panel"],
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
import QtQuick
import Quickshell
import qs.Commons
import qs.Widgets
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
import qs.Widgets
import qs.Widgets
import qs.Services.UI

Copilot uses AI. Check for mistakes.
colorBorder: "transparent"
colorBorderHover: "transparent"

onClicked: pluginApi.openPanel(root.screen, this)
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
onClicked: pluginApi.openPanel(root.screen, this)
onClicked: if (pluginApi) pluginApi.openPanel(root.screen, this)

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +33
Component.onCompleted: refresh()

function refresh() {
niriMsg.running = true;
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +83
});

discardChanges();
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +88
function discardChanges() {
pendingChanges = { positions: {}, scales: {}, modes: {}, transforms: {} };
if (pluginCore) pluginCore.refresh();
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +239 to +246
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));
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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];
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +19
if (text.trim() === "") return;
try {
root.outputs = JSON.parse(text);
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
@ItsLemmy
Copy link
Contributor

Do you plan to finish this? Even if I dislike copilot review, some of those need to be addressed

@adrlau adrlau marked this pull request as draft February 17, 2026 18:18
@adrlau
Copy link
Author

adrlau commented Feb 17, 2026

Yes, just hadn't had much time right now.
Convetærted to draft until I get things patched up. (accidentally triggered the copilot review, and sorry)

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