feat(bridge): establish local pi status bridge

Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
This commit is contained in:
2026-07-27 14:51:45 +02:00
commit b7fea83ed6
91 changed files with 15029 additions and 0 deletions
@@ -0,0 +1,55 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
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 string screenName: screen?.name ?? ""
readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName)
readonly property string bridgeState: pluginApi?.pluginSettings?.bridgeState ?? "idle"
readonly property string projectLabel: pluginApi?.pluginSettings?.projectLabel ?? "Home"
readonly property int attentionCount: pluginApi?.pluginSettings?.attentionCount ?? 0
readonly property string glyph: bridgeState === "streaming" ? "◒" : bridgeState === "failed" ? "!" : attentionCount > 0 ? "●" : "●"
readonly property color stateColor: bridgeState === "failed" ? Color.mError : attentionCount > 0 ? Color.mPrimary : Color.mOnSurface
implicitWidth: content.implicitWidth + Style.marginM * 2
implicitHeight: capsuleHeight
Rectangle {
id: visualCapsule
anchors.centerIn: parent
width: root.implicitWidth
height: root.implicitHeight
radius: Style.radiusL
color: mouseArea.containsMouse ? Color.mHover : Style.capsuleColor
border.color: Style.capsuleBorderColor
border.width: Style.capsuleBorderWidth
RowLayout {
id: content
anchors.centerIn: parent
spacing: Style.marginS
Text { text: root.glyph; color: root.stateColor; font.bold: true }
Text { text: root.projectLabel; color: Color.mOnSurface; elide: Text.ElideRight; Layout.maximumWidth: 160 }
Text { visible: root.attentionCount > 0; text: root.attentionCount; color: root.stateColor; font.bold: true }
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (pluginApi)
pluginApi.togglePanel(root.screen, visualCapsule)
}
}
}
}
+25
View File
@@ -0,0 +1,25 @@
import QtQuick
import Quickshell.Io
Item {
id: root
property var pluginApi: null
function setBridgeState(state, projectLabel, attentionCount, detail) {
if (!pluginApi)
return
pluginApi.pluginSettings.bridgeState = state
pluginApi.pluginSettings.projectLabel = projectLabel
pluginApi.pluginSettings.attentionCount = attentionCount
pluginApi.pluginSettings.detail = detail
pluginApi.saveSettings()
}
IpcHandler {
target: "plugin:pi-status-bridge"
function setBridgeState(state, projectLabel, attentionCount, detail) {
root.setBridgeState(state, projectLabel, attentionCount, detail)
}
}
}
@@ -0,0 +1,36 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
Item {
id: root
property var pluginApi: null
readonly property var geometryPlaceholder: panelContainer
readonly property bool allowAttach: true
property real contentPreferredWidth: 680 * Style.uiScaleRatio
property real contentPreferredHeight: 420 * Style.uiScaleRatio
readonly property string bridgeState: pluginApi?.pluginSettings?.bridgeState ?? "idle"
readonly property string projectLabel: pluginApi?.pluginSettings?.projectLabel ?? "Home"
readonly property string detail: pluginApi?.pluginSettings?.detail ?? "Bridge not connected"
anchors.fill: parent
Rectangle {
id: panelContainer
anchors.fill: parent
color: "transparent"
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginL
spacing: Style.marginM
Text { text: "Pi Status Bridge"; color: Color.mOnSurface; font.bold: true; font.pixelSize: 20 }
Text { text: "Project: " + root.projectLabel; color: Color.mOnSurfaceVariant }
Text { text: "State: " + root.bridgeState; color: Color.mOnSurfaceVariant }
Rectangle { Layout.fillWidth: true; height: 1; color: Color.mOutlineVariant }
Text { text: root.detail; color: Color.mOnSurface; wrapMode: Text.Wrap; Layout.fillWidth: true }
Text { text: "Select a project explicitly before sending worktree-scoped actions."; color: Color.mOnSurfaceVariant; wrapMode: Text.Wrap; Layout.fillWidth: true }
Item { Layout.fillHeight: true }
}
}
}
@@ -0,0 +1,14 @@
# Pi Status Bridge Noctalia plugin
This Noctalia v4 plugin is deliberately **presentation-only**. It renders
state supplied through its `plugin:pi-status-bridge` IPC handler and opens its
panel with `pluginApi.togglePanel`.
Use `pi-status-bridge-client` to query the owner-only Unix bridge socket. A
local integration script should translate responses and events into the plugin
IPC handler's `setBridgeState(state, projectLabel, attentionCount, detail)`
call. The plugin never starts Pi, opens TCP, chooses a worktree silently, or
approves extension requests.
Install/register it through Noctalia's normal plugin workflow, then add its
bar widget in Noctalia settings.
@@ -0,0 +1,20 @@
{
"id": "pi-status-bridge",
"name": "Pi Status Bridge",
"version": "0.1.0",
"minNoctaliaVersion": "4.0.0",
"description": "Presentation-only status bar and panel for a local Pi Status Bridge.",
"entryPoints": {
"main": "Main.qml",
"barWidget": "BarWidget.qml",
"panel": "Panel.qml"
},
"metadata": {
"defaultSettings": {
"bridgeState": "idle",
"projectLabel": "Home",
"attentionCount": 0,
"detail": "Bridge not connected"
}
}
}