From b04ae6c22ff9bf3c5f972e17531e429b58b90d77 Mon Sep 17 00:00:00 2001 From: "J. Pablo Puerta" Date: Sat, 23 May 2026 00:26:01 -0700 Subject: [PATCH 1/3] feat: add niri-ribbon viewport widget --- niri-ribbon/BarWidget.qml | 57 ++++++++++++ niri-ribbon/Main.qml | 32 +++++++ niri-ribbon/README.md | 40 +++++++++ niri-ribbon/manifest.json | 18 ++++ niri-ribbon/niri_ribbon.py | 179 +++++++++++++++++++++++++++++++++++++ niri-ribbon/preview.png | Bin 0 -> 21136 bytes 6 files changed, 326 insertions(+) create mode 100644 niri-ribbon/BarWidget.qml create mode 100644 niri-ribbon/Main.qml create mode 100644 niri-ribbon/README.md create mode 100644 niri-ribbon/manifest.json create mode 100755 niri-ribbon/niri_ribbon.py create mode 100644 niri-ribbon/preview.png diff --git a/niri-ribbon/BarWidget.qml b/niri-ribbon/BarWidget.qml new file mode 100644 index 000000000..5c31be9e5 --- /dev/null +++ b/niri-ribbon/BarWidget.qml @@ -0,0 +1,57 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Widgets +import qs.Services.UI + +Item { + id: root + property var pluginApi: null + property ShellScreen screen + + readonly property var mainInstance: pluginApi?.mainInstance + readonly property real ratioStart: mainInstance?.ratioStart ?? 0 + readonly property real ratioWidth: mainInstance?.ratioWidth ?? 1 + + implicitWidth: 160 + implicitHeight: Style.capsuleHeight + + Rectangle { + id: track + anchors.fill: parent + anchors.margins: 8 + color: Color.mSurfaceVariant + opacity: 0.3 + radius: 4 + clip: true + + border.width: 1 + border.color: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.1) + + Rectangle { + id: viewportIndicator + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: Math.max(12, parent.width * Math.min(1.0, root.ratioWidth)) + x: Math.max(0, Math.min(parent.width - width, parent.width * root.ratioStart)) + + color: Color.mPrimary + radius: 3 + opacity: 0.8 + + Behavior on x { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } } + Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } } + + // Inner shine for a polished look + Rectangle { + anchors.fill: parent + anchors.margins: 1 + color: "white" + opacity: 0.15 + radius: 2 + } + } + } +} diff --git a/niri-ribbon/Main.qml b/niri-ribbon/Main.qml new file mode 100644 index 000000000..7eb85b369 --- /dev/null +++ b/niri-ribbon/Main.qml @@ -0,0 +1,32 @@ +import QtQuick +import Quickshell +import Quickshell.Io + +Item { + id: root + + property var pluginApi: null + + property int leftCount: 0 + property int rightCount: 0 + property real ratioStart: 0 + property real ratioWidth: 1 + + Process { + id: niriRibbonScript + command: ["python3", Quickshell.env("HOME") + "/projects/noctalia-niri-ribbon/niri_ribbon.py"] + running: true + + stdout: SplitParser { + onRead: line => { + const parts = line.trim().split("|"); + if (parts.length === 4) { + root.leftCount = parseInt(parts[0]); + root.rightCount = parseInt(parts[1]); + root.ratioStart = parseFloat(parts[2]); + root.ratioWidth = parseFloat(parts[3]); + } + } + } + } +} diff --git a/niri-ribbon/README.md b/niri-ribbon/README.md new file mode 100644 index 000000000..b5429b10b --- /dev/null +++ b/niri-ribbon/README.md @@ -0,0 +1,40 @@ +# Noctalia Niri Ribbon Widget + +An independent widget for [Noctalia](https://github.com/noctalia-dev/noctalia) that tracks off-screen windows in the [niri](https://github.com/YaLTeR/niri) window manager's horizontal ribbon. + +Developed by **J Pablo Puerta** . + +## Features +- Real-time tracking of hidden windows to the left and right. +- Native Quickshell implementation via Noctalia's plugin system. +- Muted/Active color states based on window counts. +- Robust state handling via niri's `event-stream`. + +## Installation + +1. Clone this repository into your Noctalia plugins directory (or symlink it): + ```bash + mkdir -p ~/.config/noctalia/plugins + ln -s ~/projects/noctalia-niri-ribbon ~/.config/noctalia/plugins/niri-ribbon + ``` + +2. Enable the plugin in `~/.config/noctalia/plugins.json`: + ```json + "niri-ribbon": { + "enabled": true + } + ``` + +3. Add the widget to your bar in `~/.config/noctalia/settings.json`: + ```json + "widgets": { + "center": [ + { "id": "niri-ribbon" } + ] + } + ``` + +## Requirements +- `niri` window manager +- `python3` +- `noctalia` / `quickshell` diff --git a/niri-ribbon/manifest.json b/niri-ribbon/manifest.json new file mode 100644 index 000000000..7276eb213 --- /dev/null +++ b/niri-ribbon/manifest.json @@ -0,0 +1,18 @@ +{ + "id": "niri-ribbon", + "name": "Niri Ribbon", + "version": "1.0.0", + "minNoctaliaVersion": "4.7.0", + "license": "MIT", + "description": "Tracks off-screen windows in niri ribbon", + "author": "J Pablo Puerta ", + "tags": [ + "Utility", + "Bar", + "Niri" + ], + "entryPoints": { + "main": "Main.qml", + "barWidget": "BarWidget.qml" + } +} diff --git a/niri-ribbon/niri_ribbon.py b/niri-ribbon/niri_ribbon.py new file mode 100755 index 000000000..cd5675782 --- /dev/null +++ b/niri-ribbon/niri_ribbon.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +import json +import subprocess +import sys +import os + +# Log file for debugging +DEBUG_LOG = os.path.expanduser("~/noctalia_niri_ribbon.log") + +def log(msg): + with open(DEBUG_LOG, "a") as f: + f.write(f"{msg}\n") + +def get_niri_state(): + """Fetches the current windows, workspaces and outputs from niri.""" + try: + # Get windows + res_win = subprocess.run(["niri", "msg", "-j", "windows"], capture_output=True, text=True) + if res_win.returncode != 0: return None, None, None + windows = json.loads(res_win.stdout) + + # Get workspaces + res_ws = subprocess.run(["niri", "msg", "-j", "workspaces"], capture_output=True, text=True) + if res_ws.returncode != 0: return None, None, None + workspaces = json.loads(res_ws.stdout) + + # Get outputs + res_out = subprocess.run(["niri", "msg", "-j", "outputs"], capture_output=True, text=True) + if res_out.returncode != 0: return None, None, None + outputs = json.loads(res_out.stdout) + + return windows, workspaces, outputs + except Exception as e: + log(f"Error fetching state: {e}") + return None, None, None + +def calculate_ribbon(last_view_data=None): + """Calculates the ribbon viewport ratios.""" + windows, workspaces, outputs = get_niri_state() + if not windows or not workspaces or not outputs: + print("0|0|0|1", flush=True) + return + + # Find the focused workspace (active interaction), fallback to first active + focused_ws = next((ws for ws in workspaces if ws.get("is_focused")), None) + if not focused_ws: + focused_ws = next((ws for ws in workspaces if ws.get("is_active")), None) + + if not focused_ws: + print("0|0|0|1", flush=True) + return + + ws_id = focused_ws.get("id") + output_name = focused_ws.get("output") + active_win_id = focused_ws.get("active_window_id") + + output_data = outputs.get(output_name, {}) + view_width = output_data.get("logical", {}).get("width", 1920) + + ws_windows = [w for w in windows if w.get("workspace_id") == ws_id and not w.get("is_floating")] + + if not ws_windows: + print("0|0|0|1", flush=True) + return + + columns = {} + for w in ws_windows: + layout = w.get("layout", {}) + pos = layout.get("pos_in_scrolling_layout") + if pos: + col_idx = pos[0] + width = layout.get("tile_size", [0])[0] + if col_idx not in columns: + columns[col_idx] = width + + sorted_col_indices = sorted(columns.keys()) + total_width = sum(columns.values()) + + # Logic for proportions: + # If there is only one column, it should fill the whole track. + # Otherwise, it should be view_width / total_width. + if len(sorted_col_indices) <= 1: + ratio_start = 0.0 + ratio_width = 1.0 + else: + canvas_width = max(total_width, view_width) + + view_x = 0 + # Use real-time view data if available and for the correct workspace + if last_view_data and last_view_data.get("workspace_id") == ws_id: + view_x = last_view_data.get("x", 0) + else: + # Fallback: Use the workspace's active window or globally focused window + target_win_id = active_win_id + if target_win_id is None: + f_win = next((w for w in windows if w.get("is_focused")), None) + if f_win and f_win.get("workspace_id") == ws_id: + target_win_id = f_win.get("id") + + target_win = next((w for w in ws_windows if w.get("id") == target_win_id), None) + if not target_win and ws_windows: + target_win = ws_windows[0] + + if target_win: + f_layout = target_win.get("layout", {}) + f_idx = f_layout.get("pos_in_scrolling_layout", [0])[0] + start_px_from_first = sum(columns[idx] for idx in sorted_col_indices if idx < f_idx) + f_width = columns.get(f_idx, 0) + # niri centers focused window, so offset view center to screen center + view_x = -(start_px_from_first + f_width/2 - view_width/2) + else: + view_x = 0 + + ratio_start = -view_x / canvas_width + ratio_width = view_width / canvas_width + + # Clamp to [0, 1] + ratio_start = max(0.0, min(1.0, ratio_start)) + ratio_width = max(0.01, min(1.0, ratio_width)) + + if ratio_start + ratio_width > 1.0: + ratio_start = 1.0 - ratio_width + + print(f"0|0|{ratio_start:.4f}|{ratio_width:.4f}", flush=True) + +def main(): + if os.path.exists(DEBUG_LOG): + os.remove(DEBUG_LOG) + + calculate_ribbon() + + try: + process = subprocess.Popen( + ["niri", "msg", "-j", "event-stream"], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True + ) + + last_view = None + + # Events that should trigger a full recalculation + for line in process.stdout: + try: + event = json.loads(line) + + # Check for explicit view scroll + if "WorkspaceViewChanged" in event: + last_view = event["WorkspaceViewChanged"] + calculate_ribbon(last_view) + continue + + # Check for workspace change - invalidate view data + if "WorkspaceActivated" in event: + last_view = None + calculate_ribbon() + continue + + # Check for other structural changes + if any(k in event for k in [ + "WindowOpenedOrChanged", + "WindowClosed", + "WorkspaceActiveWindowChanged", + "WindowFocusChanged", + "WindowsChanged", + "WorkspacesChanged" + ]): + calculate_ribbon(last_view) + except Exception: + continue + + except KeyboardInterrupt: + pass + finally: + if 'process' in locals(): + process.terminate() + +if __name__ == "__main__": + main() diff --git a/niri-ribbon/preview.png b/niri-ribbon/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..56a8bc52b5bf8285ee329beae74ce428b047b873 GIT binary patch literal 21136 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 PQMI#{b{55_FUS24)-;YQ literal 0 HcmV?d00001 From d14df5ba13a091923c36054f722adff393cc01ab Mon Sep 17 00:00:00 2001 From: "J. Pablo Puerta" Date: Sun, 24 May 2026 11:05:43 -0700 Subject: [PATCH 2/3] fix(pr): resolve code quality issues by using Style singleton and adding required properties --- niri-ribbon/BarWidget.qml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/niri-ribbon/BarWidget.qml b/niri-ribbon/BarWidget.qml index 5c31be9e5..2e2e4d3ea 100644 --- a/niri-ribbon/BarWidget.qml +++ b/niri-ribbon/BarWidget.qml @@ -9,6 +9,10 @@ Item { id: root property var pluginApi: null property ShellScreen screen + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 readonly property var mainInstance: pluginApi?.mainInstance readonly property real ratioStart: mainInstance?.ratioStart ?? 0 @@ -20,13 +24,13 @@ Item { Rectangle { id: track anchors.fill: parent - anchors.margins: 8 + anchors.margins: Style.marginS color: Color.mSurfaceVariant opacity: 0.3 - radius: 4 + radius: Style.radiusS clip: true - border.width: 1 + border.width: Style.borderS border.color: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.1) Rectangle { @@ -38,7 +42,7 @@ Item { x: Math.max(0, Math.min(parent.width - width, parent.width * root.ratioStart)) color: Color.mPrimary - radius: 3 + radius: Math.max(0, Style.radiusS - Style.borderS) opacity: 0.8 Behavior on x { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } } @@ -47,10 +51,10 @@ Item { // Inner shine for a polished look Rectangle { anchors.fill: parent - anchors.margins: 1 + anchors.margins: Style.borderS color: "white" opacity: 0.15 - radius: 2 + radius: Math.max(0, Style.radiusS - Style.borderS * 2) } } } From 7804cedc82c5af21628a10cec2a99b8b984e648e Mon Sep 17 00:00:00 2001 From: "J. Pablo Puerta" Date: Sun, 24 May 2026 11:06:59 -0700 Subject: [PATCH 3/3] fix(pr): add required repository property to manifest --- niri-ribbon/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/niri-ribbon/manifest.json b/niri-ribbon/manifest.json index 7276eb213..874c3ea69 100644 --- a/niri-ribbon/manifest.json +++ b/niri-ribbon/manifest.json @@ -4,6 +4,7 @@ "version": "1.0.0", "minNoctaliaVersion": "4.7.0", "license": "MIT", + "repository": "https://github.com/noctalia-dev/noctalia-plugins", "description": "Tracks off-screen windows in niri ribbon", "author": "J Pablo Puerta ", "tags": [