import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Io import qs.Commons import qs.Ui Panel { id: root moduleName: "io.github.fabiopauli.omaharness" ipcTarget: "io.github.fabiopauli.omaharness" manageIpc: false property var anchorItem: null property var hostWidget: null readonly property var barIdentity: hostWidget || root property bool checking: false property bool installed: false property bool ready: false property var report: ({}) property var blockers: [] property var degraded: [] property var warnings: [] property var capabilities: [] property string errorMessage: "" property string doctorStdout: "" property string doctorStderr: "" property int doctorOutputChars: 0 property bool doctorOutputLimited: false property bool doctorTimedOut: false property bool doctorStarted: false property string backendVersion: "" property string backendCommit: "" readonly property int degradedCount: degraded ? degraded.length : 0 readonly property int maxDoctorOutputChars: 128 * 1024 readonly property int doctorTimeoutMs: 15000 readonly property int doctorKillGraceMs: 500 readonly property int maxDiagnosticItems: 25 readonly property int maxDiagnosticChars: 512 readonly property int maxErrorChars: 2048 readonly property string expectedBackendVersion: "0.1.3" readonly property string expectedBackendCommit: "2614ba03fe98ef711575f0c830a8eb518804e7d4" readonly property bool backendMatches: backendVersion === expectedBackendVersion && backendCommit === expectedBackendCommit readonly property color foreground: bar ? bar.foreground : Color.foreground readonly property color dim: Qt.darker(foreground, 1.4) readonly property color accent: Color.accent readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family readonly property string setupUrl: "https://github.com/fabiopauli/omarchy-omaharness-plugin#install" readonly property string installCommand: "uv tool install --python /usr/bin/python3 --with 'PyGObject==3.56.3' --constraints https://raw.githubusercontent.com/fabiopauli/omarchy-quattro-harness/2614ba03fe98ef711575f0c830a8eb518804e7d4/constraints.txt --build-constraints https://raw.githubusercontent.com/fabiopauli/omarchy-quattro-harness/2614ba03fe98ef711575f0c830a8eb518804e7d4/build-constraints.txt --upgrade git+https://github.com/fabiopauli/omarchy-quattro-harness.git@2614ba03fe98ef711575f0c830a8eb518804e7d4" readonly property string stateTitle: checking ? "Checking…" : !installed ? "Backend not installed" : !backendMatches ? "Backend update required" : ready && degradedCount === 0 ? "Ready" : ready ? "Ready with limitations" : "Needs attention" readonly property string stateMeta: checking ? "Inspecting desktop capabilities" : !installed ? "Install the Omaharness backend" : !backendMatches ? "Install the backend revision required by this plugin" : ready ? "Desktop automation is available" : "Review the diagnostics below" readonly property string compositorVersion: report && report.compositor ? boundedText(report.compositor.tag, 128) : "" function open() { refresh() root.controller.show() Qt.callLater(function() { if (root.opened) keyCatcher.forceActiveFocus() }) } function close() { root.controller.hide() } function toggle() { if (root.opened) close() else open() } function switchPanel(direction) { if (root.bar && typeof root.bar.switchPanelFrom === "function") return root.bar.switchPanelFrom(root.barIdentity, direction) return false } function openSetupGuide() { Quickshell.execDetached(["xdg-open", root.setupUrl]) } function refresh() { if (doctorProcess.running) return checking = true errorMessage = "" doctorStdout = "" doctorStderr = "" doctorOutputChars = 0 doctorOutputLimited = false doctorTimedOut = false doctorStarted = false doctorProcess.command = ["/usr/bin/env", "omaharness", "doctor"] doctorTimeout.restart() doctorProcess.running = true } function appendDoctorOutput(data, isStderr) { if (doctorOutputLimited || doctorTimedOut) return var chunk = String(data || "") var remaining = maxDoctorOutputChars - doctorOutputChars if (remaining > 0) { var kept = chunk.slice(0, remaining) if (isStderr) doctorStderr += kept else doctorStdout += kept doctorOutputChars += kept.length } if (chunk.length > remaining) { doctorOutputLimited = true stopDoctor("Omaharness doctor exceeded its output safety limit") } } function boundedText(value, maximum) { return String(value || "").slice(0, maximum) } function boundedMessages(value) { if (!Array.isArray(value)) return [] var rows = [] var count = Math.min(value.length, maxDiagnosticItems) for (var i = 0; i < count; i++) rows.push(boundedText(value[i], maxDiagnosticChars)) if (value.length > count) rows.push("… " + (value.length - count) + " additional entries omitted") return rows } function resetBackendIdentity() { backendVersion = "" backendCommit = "" } function stopDoctor(message) { doctorTimeout.stop() failDoctorRun(message) if (doctorProcess.running) { doctorProcess.running = false doctorKillTimeout.restart() } } function failDoctorRun(message) { installed = true ready = false checking = false report = ({}) blockers = [] degraded = [] warnings = [] capabilities = [] resetBackendIdentity() errorMessage = boundedText(message, maxErrorChars) } function capabilityRows(reportValue) { var source = reportValue && reportValue.capabilities ? reportValue.capabilities : {} var definitions = [ ["Window capture", "capture"], ["Targeted keys", "targeted_keys"], ["Semantic actions", "semantic_actions"], ["Text entry", "text_entry"], ["Raw pointer", "raw_pointer"], ["Native pointer", "native_pointer"] ] var rows = [] for (var i = 0; i < definitions.length; i++) { var label = definitions[i][0] var value = source[definitions[i][1]] || {} var backgroundSafe = value.background_safe === true || value.background_safe_available === true rows.push({ label: label, available: value.available === true, backgroundSafe: backgroundSafe, reason: boundedText(value.reason, maxDiagnosticChars) }) } return rows } function applyDoctorResult(exitCode, output, errorOutput) { checking = false var raw = String(output || "").trim() var stderrText = String(errorOutput || "").trim() if (exitCode === 127) { installed = false ready = false report = ({}) blockers = [] degraded = [] warnings = [] capabilities = [] resetBackendIdentity() errorMessage = "" return } try { var parsed = JSON.parse(raw) installed = true var backend = parsed && parsed.backend && typeof parsed.backend === "object" ? parsed.backend : {} backendVersion = boundedText(backend.version, 64) backendCommit = boundedText(backend.source_commit, 64).toLowerCase() report = ({ compositor: parsed && parsed.compositor ? parsed.compositor : {} }) ready = parsed.ready === true blockers = boundedMessages(parsed.blockers) degraded = boundedMessages(parsed.degraded_capabilities) warnings = boundedMessages(parsed.warnings) if (!backendMatches) warnings = warnings.concat([ "Installed backend does not match this plugin; run the v0.1.3 backend install command." ]) capabilities = capabilityRows(parsed) errorMessage = exitCode === 0 ? "" : boundedText( stderrText || "Doctor exited with code " + exitCode, maxErrorChars ) } catch (error) { installed = exitCode !== 127 ready = false report = ({}) blockers = [] degraded = [] warnings = [] capabilities = [] resetBackendIdentity() errorMessage = boundedText( stderrText || "Omaharness returned an unreadable diagnostic report", maxErrorChars ) } } Process { id: doctorProcess running: false stdout: SplitParser { splitMarker: "" onRead: function(data) { root.appendDoctorOutput(data, false) } } stderr: SplitParser { splitMarker: "" onRead: function(data) { root.appendDoctorOutput(data, true) } } onStarted: root.doctorStarted = true onRunningChanged: { if (running || !root.checking || root.doctorStarted || root.doctorOutputLimited || root.doctorTimedOut) return Qt.callLater(function() { if (!doctorProcess.running && root.checking && !root.doctorStarted) { doctorTimeout.stop() root.failDoctorRun("Omaharness doctor failed to start") } }) } onExited: function(exitCode) { doctorTimeout.stop() doctorKillTimeout.stop() Qt.callLater(function() { if (root.doctorOutputLimited || root.doctorTimedOut) return root.applyDoctorResult(exitCode, root.doctorStdout, root.doctorStderr) }) } } Timer { id: doctorTimeout interval: root.doctorTimeoutMs repeat: false onTriggered: { root.doctorTimedOut = true root.stopDoctor("Omaharness doctor timed out after 15 seconds") } } Timer { id: doctorKillTimeout interval: root.doctorKillGraceMs repeat: false onTriggered: { if (doctorProcess.running) doctorProcess.signal(9) } } Component.onCompleted: refresh() KeyboardPanel { id: panel anchorItem: root.anchorItem owner: root.barIdentity bar: root.bar open: root.opened focusTarget: keyCatcher contentWidth: panel.fittedContentWidth(Style.space(430)) contentHeight: panel.fittedContentHeight(contentColumn.implicitHeight, Style.space(600)) PanelKeyCatcher { id: keyCatcher anchors.fill: parent onActivateRequested: root.refresh() onCloseRequested: root.close() onTabRequested: function(direction) { root.switchPanel(direction) } onTextKey: function(text) { if (text === "r" || text === "R") root.refresh() else if (text === "g" || text === "G") root.openSetupGuide() } Flickable { anchors.fill: parent contentWidth: width contentHeight: contentColumn.implicitHeight clip: true boundsBehavior: Flickable.StopAtBounds flickableDirection: Flickable.VerticalFlick interactive: contentHeight > height ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } Column { id: contentColumn width: parent.width spacing: Style.space(12) PanelHero { width: parent.width title: "Omaharness" meta: root.stateMeta detail: root.stateTitle foreground: root.foreground fontFamily: root.fontFamily iconComponent: Component { Text { textFormat: Text.PlainText text: root.checking ? "◌" : root.installed && root.ready && root.backendMatches ? "●" : "○" color: root.installed && root.ready && root.backendMatches ? root.accent : root.foreground font.family: root.fontFamily font.pixelSize: Style.font.display } } } Text { textFormat: Text.PlainText visible: root.checking width: parent.width text: "Running omaharness doctor…" color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.body horizontalAlignment: Text.AlignHCenter } Column { visible: !root.checking && !root.installed width: parent.width spacing: Style.space(10) Text { textFormat: Text.PlainText width: parent.width text: "This widget is the dashboard. Install the Omaharness Python backend to connect agents to your Omarchy desktop." color: root.foreground font.family: root.fontFamily font.pixelSize: Style.font.body wrapMode: Text.WordWrap } Rectangle { width: parent.width implicitHeight: installText.implicitHeight + Style.space(20) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.06) radius: Style.cornerRadius Text { id: installText textFormat: Text.PlainText anchors.fill: parent anchors.margins: Style.space(10) text: root.installCommand color: root.foreground font.family: "monospace" font.pixelSize: Style.font.bodySmall wrapMode: Text.WrapAnywhere } } } Column { visible: !root.checking && root.installed width: parent.width spacing: Style.space(8) Text { textFormat: Text.PlainText visible: root.compositorVersion !== "" width: parent.width text: "Hyprland " + root.compositorVersion color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.bodySmall } PanelSeparator { visible: root.capabilities.length > 0 foreground: root.foreground } PanelSectionHeader { visible: root.capabilities.length > 0 text: "CAPABILITIES" foreground: root.foreground fontFamily: root.fontFamily } Repeater { model: root.capabilities delegate: Column { required property var modelData width: parent.width spacing: Style.space(2) Row { width: parent.width Text { textFormat: Text.PlainText text: modelData.label color: root.foreground font.family: root.fontFamily font.pixelSize: Style.font.body } Item { width: Math.max(0, parent.width - parent.children[0].implicitWidth - parent.children[2].implicitWidth) height: 1 } Text { textFormat: Text.PlainText text: !modelData.available ? "Unavailable" : modelData.backgroundSafe ? "Background-safe" : "Available" color: modelData.available ? root.foreground : root.dim font.family: root.fontFamily font.pixelSize: Style.font.bodySmall font.bold: true } } Text { textFormat: Text.PlainText visible: !modelData.available && modelData.reason !== "" width: parent.width text: modelData.reason color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.caption wrapMode: Text.WordWrap maximumLineCount: 2 elide: Text.ElideRight } } } PanelSeparator { visible: root.blockers.length + root.degraded.length + root.warnings.length > 0 foreground: root.foreground } PanelSectionHeader { visible: root.blockers.length > 0 text: "BLOCKERS" foreground: root.foreground fontFamily: root.fontFamily } Repeater { model: root.blockers delegate: Text { required property var modelData textFormat: Text.PlainText width: parent.width text: "• " + String(modelData) color: root.foreground font.family: root.fontFamily font.pixelSize: Style.font.bodySmall wrapMode: Text.WordWrap } } PanelSectionHeader { visible: root.degraded.length > 0 text: "DEGRADED" foreground: root.foreground fontFamily: root.fontFamily } Repeater { model: root.degraded delegate: Text { required property var modelData textFormat: Text.PlainText width: parent.width text: "• " + String(modelData) color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.bodySmall wrapMode: Text.WordWrap } } PanelSectionHeader { visible: root.warnings.length > 0 || root.errorMessage !== "" text: "WARNINGS" foreground: root.foreground fontFamily: root.fontFamily } Repeater { model: root.warnings delegate: Text { required property var modelData textFormat: Text.PlainText width: parent.width text: "• " + String(modelData) color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.bodySmall wrapMode: Text.WordWrap } } Text { textFormat: Text.PlainText visible: root.errorMessage !== "" width: parent.width text: root.errorMessage color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.bodySmall wrapMode: Text.WordWrap } } Row { width: parent.width spacing: Style.space(8) Button { text: root.checking ? "Checking…" : "Refresh" iconText: "↻" enabled: !root.checking foreground: root.foreground fontFamily: root.fontFamily bordered: true onClicked: root.refresh() } Button { text: "Setup guide" iconText: "↗" foreground: root.foreground fontFamily: root.fontFamily bordered: true onClicked: root.openSetupGuide() } } Text { textFormat: Text.PlainText width: parent.width text: "R refreshes · G opens setup · Esc closes" color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.caption horizontalAlignment: Text.AlignHCenter } } } } } }