diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml
index 91ff4e4..09f812b 100644
--- a/.github/workflows/package-release.yml
+++ b/.github/workflows/package-release.yml
@@ -24,6 +24,7 @@ jobs:
set -euo pipefail
bash -n packaging/aur/PKGBUILD
bash -n packaging/shibumi-shell
+ python3 tests/production-boundary-regression.py
python3 tests/test_package_release.py
python3 tests/test_shibumi_manager.py
python3 tests/test_shibumi_suite.py
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index e5f35de..16adf0a 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -560,12 +560,12 @@ Current Phase 2 foundation:
feature data and workers live in independently validated plugins;
- fail-closed schema-1 parser for V1 group order, splits, and resource-bounded
widget settings;
-- one root-owned procfs telemetry service shared by every output;
+- one process-wide `hancore.shibumi.telemetry` procfs service shared by every output;
- internal CPU and memory widgets with compact/full horizontal presentations;
- screen-local, on-demand CPU and memory panels;
- GPU telemetry is inactive unless a CPU panel is open and tolerates missing or
unusable `nvidia-smi` by falling back to DRM sysfs or no GPU row.
-- one root-owned Hyprland workspace model with V1 persist-10, persist-5, active,
+- one process-wide `hancore.shibumi.workspaces` Hyprland model with V1 persist-10, persist-5, active,
default, numbers, and magic presentation contracts;
- a validated Quattro workspace action adapter, per-screen Shibumi workspace
widget, and lifecycle-lazy keyboard panel whose backend-free content is
@@ -573,8 +573,8 @@ Current Phase 2 foundation:
- the Control Center owns Shibumi settings while Omarchy exclusively owns the
application launcher menu. Disabled widget loaders and the default-off G7
service construct no backend work;
-- one root-owned minute-precision clock service and one root-owned weather
- service feed a single V1-compatible G8 center composite on every output;
+- one process-wide `hancore.shibumi.center` clock and weather service feeds a
+ single V1-compatible G8 center composite on every output;
the composite owns weather, clock/date/calendar, and active-only status
presentation while retaining the official weather detail workflow and
official Omarchy idle/notification state owners;
@@ -615,7 +615,7 @@ Current Phase 2 foundation:
registration, model release, and teardown. Real Wayland panel mapping remains
an acceptance gate.
- G7 replaces the stock AI presentation with one selected-provider Shibumi
- pill and one lazy local panel. A single root-owned `AiUsageService` consumes
+ pill and one lazy local panel. The process-wide `hancore.shibumi.ai` service consumes
the primitive schema-v1 records produced by current `omarchy.agents`; it
never loads the host Agents panel or exposes host backend objects to views.
The service owns one bounded update process and two watched Claude/Codex
@@ -649,7 +649,7 @@ Current Phase 2 foundation:
paths per frame. the validation system accepts the real-player, unavailable/crash,
retry/cleanup, Top/Bottom, single-output visual, and resource slices.
Multiple real players and physical multi-output acceptance remain gates.
-- G11 uses one root-owned `NetworkService`, regardless of output count. It
+- G11 uses one process-wide `hancore.shibumi.network` service, regardless of output count. It
hosts the registered `omarchy.network` component as the authoritative
`Quickshell.Networking`, status, scan, DNS, and visible-network action owner
while suppressing its stock button, popup, and IPC handler;
@@ -666,8 +666,8 @@ Current Phase 2 foundation:
is not restored. Top Wayland mapping
and cleanup pass on the validation system; the new direct speed-test path,
bottom, mutation, and physical multi-output remain runtime gates.
-- G13 has one root-owned `MonitorService` around the registered
- `omarchy.monitor` component. That hidden component remains the only
+- G13 has one process-wide `hancore.shibumi.brightness` service around the
+ registered `omarchy.monitor` component. That hidden component remains the only
brightness, display, scale, IPC, poller, and command owner;
- each output owns only its V1 brightness presentation and a lazy local Shibumi
panel. The panel delegates brightness, scale, and display mutations to the
@@ -676,20 +676,20 @@ Current Phase 2 foundation:
mapping, reversible laptop brightness mutation, and a real `1.0 -> 1.25 ->
1.0` scale round trip pass on the validation system; physical display enable/disable and
multi-output behavior remain gates.
-- G12 and G14 are separate V1 battery and power-profile presentations over one
- root-owned `PowerService`. Battery state stays event-driven through the
+- G12 and G14 are separate V1 battery and power-profile presentations over the
+ process-wide `hancore.shibumi.power-state` service. Battery state stays event-driven through the
shared UPower singleton, battery details are panel-lifecycle gated, and one
profile refresh/set owner serves every output. The combined `omarchy.power`
alias is consumed so it cannot run beside the split views; G14 remains
available on batteryless desktops. the validation system passes a real
discharging-to-charging transition with matching kernel, UPower, helper,
widget, and panel state.
-- G15 has one root-owned `BluetoothService` and one native
+- G15 has one process-wide `hancore.shibumi.bluetooth` service and one native
`BluetoothBackendAdapter`. The adapter owns Quickshell's BlueZ/PipeWire
models, pairing/device actions, pending state, and Bluetooth-audio handoff;
no complete Omarchy Bluetooth UI component is instantiated as a backend;
- each output owns only its V1 Bluetooth presentation and lazy Shibumi device
- panel. The root service leases discovery across open panels and owns one
+ panel. The process-wide service leases discovery across open panels and owns one
symmetric six-method `omarchy.bluetooth` IPC target. Presentation has no
Bluetooth/PipeWire import, process, timer, or file watcher; the service facade
owns one bounded discovery-reconciliation timer and the adapter owns four
@@ -700,7 +700,7 @@ Current Phase 2 foundation:
lifecycle pass on the validation system; real device/audio, bottom, and
physical multi-output gates remain.
- the interaction foundation has a pure fixed-group layout model, one
- root-owned persistent controller, and one transient drag session per output;
+ bar-owned persistent controller, and one transient drag session per output;
- the group renderer resolves Shibumi and official Quattro widgets without
duplicate state owners, preserves custom host-layout extras, omits empty
groups without reserving space, and renders persisted within-section split
diff --git a/adapters/AudioPanelBridge.qml b/adapters/AudioPanelBridge.qml
deleted file mode 100644
index 9bea85b..0000000
--- a/adapters/AudioPanelBridge.qml
+++ /dev/null
@@ -1,301 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-
-// Keeps Quattro's audio component as the single PipeWire/device/action owner.
-// Shibumi renders its own bar and popup presentation, while this bridge exposes
-// only state and commands from the hidden official instance.
-Item {
- id: root
-
- required property var bar
- required property Item ownerWidget
- property Component panelComponent: null
- property url panelSource: ""
- property var panelSettings: ({})
-
- readonly property var panel: panelLoader.item
- readonly property bool ready: panel !== null
- readonly property var sink: ready && panel.sink !== undefined ? panel.sink : null
- readonly property var source: ready && panel.source !== undefined ? panel.source : null
- readonly property var audioSinks: ready && panel.audioSinks !== undefined
- ? groupedDeviceNodes(panel.audioSinks) : []
- readonly property var audioSources: ready && panel.audioSources !== undefined
- ? panel.audioSources : []
- readonly property var audioStreams: ready && panel.audioStreams !== undefined
- ? panel.audioStreams : []
- readonly property real outputVolume: ready
- && panel.outputVolume !== undefined ? Number(panel.outputVolume) : 0
- readonly property bool outputMuted: ready
- && panel.outputMuted !== undefined ? panel.outputMuted === true : false
- readonly property real inputVolume: ready
- && panel.inputVolume !== undefined ? Number(panel.inputVolume) : 0
- readonly property bool inputMuted: ready
- && panel.inputMuted !== undefined ? panel.inputMuted === true : false
-
- function toggleOutputMute() {
- if (!ready || typeof panel.toggleOutputMute !== "function") return false
- panel.toggleOutputMute()
- return true
- }
-
- function setOutputVolume(value) {
- if (!ready || typeof panel.setOutputVolume !== "function") return false
- panel.setOutputVolume(Math.max(0, Math.min(1, Number(value) || 0)))
- return true
- }
-
- function toggleInputMute() {
- if (!ready || typeof panel.toggleInputMute !== "function") return false
- panel.toggleInputMute()
- return true
- }
-
- function setInputVolume(value) {
- if (!ready || typeof panel.setInputVolume !== "function") return false
- panel.setInputVolume(Math.max(0, Math.min(1, Number(value) || 0)))
- return true
- }
-
- function setDefaultSink(node) {
- if (!ready || !node || typeof panel.setDefaultSink !== "function") return false
- panel.setDefaultSink(node)
- return true
- }
-
- function setDefaultSource(node) {
- if (!ready || !node || typeof panel.setDefaultSource !== "function") return false
- panel.setDefaultSource(node)
- return true
- }
-
- function setStreamVolume(node, value) {
- if (!node || !node.audio) return false
- node.audio.volume = Math.max(0, Math.min(1.5, Number(value) || 0))
- return true
- }
-
- function toggleStreamMute(node) {
- if (!node || !node.audio) return false
- node.audio.muted = !node.audio.muted
- return true
- }
-
- function callLabelHelper(name, node, fallback) {
- if (!ready || typeof panel[name] !== "function") return String(fallback || "")
- return String(panel[name](node) || fallback || "")
- }
-
- function usableDeviceLabel(value) {
- const label = String(value || "").trim()
- return !label || label === "(null)" ? "" : label
- }
-
- function nodeProperties(node) {
- return node && node.ready !== false && node.properties
- ? node.properties : ({})
- }
-
- function nodeDeviceKey(node) {
- const properties = nodeProperties(node)
- const deviceId = usableDeviceLabel(properties["device.id"])
- if (deviceId) return "id:" + deviceId
-
- const deviceName = usableDeviceLabel(properties["device.name"])
- if (deviceName) return "device:" + deviceName.toLowerCase()
-
- const nodeName = usableDeviceLabel(node && node.name)
- const profileSeparator = nodeName.lastIndexOf(".")
- return "node:" + (profileSeparator > 0
- ? nodeName.slice(0, profileSeparator) : nodeName).toLowerCase()
- }
-
- function nodeProfileKey(node) {
- const properties = nodeProperties(node)
- return usableDeviceLabel(
- properties["device.profile.description"]).toLowerCase()
- }
-
- function groupedDeviceNodes(values) {
- const groups = []
- if (!values) return []
-
- for (let i = 0; i < values.length; i++) {
- const node = values[i]
- if (!node) continue
- const key = nodeDeviceKey(node) || "index:" + i
- let group = null
- for (let j = 0; j < groups.length; j++) {
- if (groups[j].key === key) {
- group = groups[j]
- break
- }
- }
- if (!group) {
- group = { key: key, nodes: [] }
- groups.push(group)
- }
- group.nodes.push(node)
- }
-
- const result = []
- for (let i = 0; i < groups.length; i++) {
- groups[i].nodes.sort(function(left, right) {
- const leftProfile = nodeProfileKey(left)
- const rightProfile = nodeProfileKey(right)
- return leftProfile < rightProfile ? -1
- : leftProfile > rightProfile ? 1 : 0
- })
- for (let j = 0; j < groups[i].nodes.length; j++)
- result.push(groups[i].nodes[j])
- }
- return result
- }
-
- function descriptiveNodeLabel(node) {
- if (!node) return ""
- const properties = nodeProperties(node)
- const profile = usableDeviceLabel(properties["device.profile.description"])
- let label = usableDeviceLabel(node.description)
- if (label && profile
- && label.toLowerCase().indexOf(profile.toLowerCase()) < 0)
- return label + " " + profile
- if (label) return label
-
- label = usableDeviceLabel(properties["node.description"])
- if (label && profile
- && label.toLowerCase().indexOf(profile.toLowerCase()) < 0)
- return label + " " + profile
- if (label) return label
-
- const device = usableDeviceLabel(properties["device.description"])
- if (device && profile
- && device.toLowerCase().indexOf(profile.toLowerCase()) < 0)
- return device + " " + profile
- return device || profile
- }
-
- function nodeLabel(node) {
- return descriptiveNodeLabel(node)
- || callLabelHelper("nodeLabel", node, "Audio device")
- }
- function sinkGlyph(node) { return callLabelHelper("sinkGlyph", node, "speaker") }
- function sourceGlyph(node) { return callLabelHelper("sourceGlyph", node, "mic") }
- function streamLabel(node) { return callLabelHelper("streamLabel", node, "Application") }
-
- function suppressBackendKeyboardPanel() {
- if (!panel || !panel.data || panel.data.length === undefined) return false
- let suppressed = false
- for (let index = 0; index < panel.data.length; index++) {
- const candidate = panel.data[index]
- if (!candidate || typeof candidate.beginFocusPrime !== "function"
- || !("anchorItem" in candidate) || !("open" in candidate)
- || !("visible" in candidate) || !("owner" in candidate)
- || candidate.owner !== panel) continue
- candidate.open = false
- candidate.visible = false
- suppressed = true
- }
- return suppressed
- }
-
- function injectPanel() {
- if (!panel) return
- suppressBackendKeyboardPanel()
- if ("bar" in panel) panel.bar = hostProxy
- if ("moduleName" in panel) panel.moduleName = "omarchy.audio"
- if ("settings" in panel) panel.settings = panelSettings
- if ("manageIpc" in panel) panel.manageIpc = false
- if (panel.opened === true && typeof panel.close === "function") panel.close()
- // The official button and popup remain instantiated for their backend
- // state, but Shibumi owns the only visible presentation and click target.
- panel.opacity = 0
- }
-
- function syncPanelSource() {
- panelLoader.sourceComponent = null
- panelLoader.source = ""
- if (panelComponent !== null) {
- panelLoader.sourceComponent = panelComponent
- } else if (String(panelSource)) {
- panelLoader.setSource(panelSource, {
- bar: hostProxy,
- moduleName: "omarchy.audio",
- manageIpc: false,
- settings: panelSettings
- })
- }
- }
-
- onPanelSettingsChanged: injectPanel()
- onBarChanged: injectPanel()
- onPanelComponentChanged: Qt.callLater(syncPanelSource)
- onPanelSourceChanged: Qt.callLater(syncPanelSource)
- Component.onCompleted: Qt.callLater(syncPanelSource)
- Component.onDestruction: {
- const currentPanel = panel
- if (currentPanel && currentPanel.opened === true
- && typeof currentPanel.close === "function") currentPanel.close()
- // Destroy the official panel while its host facade is still valid. Setting
- // panel.bar to null first reevaluates unguarded upstream bindings.
- panelLoader.active = false
- }
-
- QtObject {
- id: hostProxy
-
- readonly property var realBar: root.bar
- readonly property bool vertical: realBar ? realBar.vertical === true : false
- readonly property int barSize: realBar ? Number(realBar.barSize) : 35
- readonly property int sizeHorizontal: realBar && realBar.sizeHorizontal !== undefined
- ? Number(realBar.sizeHorizontal) : barSize
- readonly property string position: realBar ? String(realBar.position || "top") : "top"
- readonly property string fontFamily: realBar ? String(realBar.fontFamily || "monospace") : "monospace"
- readonly property color background: realBar && realBar.background !== undefined
- ? realBar.background : "#111111"
- readonly property color barBackground: background
- readonly property color foreground: realBar ? realBar.foreground : "#ffffff"
- readonly property color barForeground: foreground
- readonly property color urgent: realBar ? realBar.urgent : foreground
- readonly property bool foregroundAnimationEnabled: realBar
- ? realBar.foregroundAnimationEnabled !== false : false
- readonly property var shell: realBar ? realBar.shell : null
- readonly property var activePopout: realBar ? realBar.activePopout : null
- readonly property var clickTargets: realBar ? realBar.clickTargets : []
-
- // The hidden official button must not become a second click target.
- function registerClickTarget(_target) {}
- function unregisterClickTarget(_target) {}
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
-
- function requestPopout(owner) {
- if (realBar && typeof realBar.requestPopout === "function")
- realBar.requestPopout(owner)
- }
-
- function releasePopout(owner) {
- if (realBar && typeof realBar.releasePopout === "function")
- realBar.releasePopout(owner)
- }
-
- function switchPanelFrom(_owner, direction) {
- return realBar && typeof realBar.switchPanelFrom === "function"
- ? realBar.switchPanelFrom(root.ownerWidget, direction) : false
- }
-
- function targetBelongsToWindow(target, window) {
- return realBar && typeof realBar.targetBelongsToWindow === "function"
- ? realBar.targetBelongsToWindow(target, window) : false
- }
- }
-
- Loader {
- id: panelLoader
- anchors.fill: parent
- onLoaded: {
- root.injectPanel()
- Qt.callLater(root.injectPanel)
- }
- }
-}
diff --git a/adapters/BluetoothBackendAdapter.qml b/adapters/BluetoothBackendAdapter.qml
deleted file mode 100644
index c4375bd..0000000
--- a/adapters/BluetoothBackendAdapter.qml
+++ /dev/null
@@ -1,535 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import Quickshell.Bluetooth
-import Quickshell.Services.Pipewire
-import "." as Local
-import "BluetoothModel.js" as Model
-
-// The process-wide Bluetooth state and action owner. This deliberately uses
-// Quickshell's data APIs directly; no foreign UI component is instantiated.
-Item {
- id: root
-
- // Runtime tests inject a side-effect-free backend. Production leaves this
- // null and therefore uses the native BlueZ/PipeWire implementation below.
- property var backendOverride: null
- property var adapterOverride: null
- // Lets the component smoke test drive native device property transitions
- // without touching the host's real Bluetooth devices.
- property var nativeDevicesOverride: null
- property var pipewireNodesOverride: null
- property var commandRunnerOverride: null
- property var audioOutputOverride: null
- property int audioSwitchInterval: 500
- property int audioIntentTimeoutInterval: 60000
- property int discoveryRequestTimeoutInterval: 1500
- property int discoveryDestructionGuardInterval: 30000
- property bool discoveryOwned: false
- property var discoveryOwnerAdapter: null
- property var discoveryRequestedAdapter: null
- property var retiredDiscoveryAdapter: null
- property bool discoveryDesired: false
- property var nativePendingActions: ({})
- property var audioHandoffIntent: null
- property var pendingAudioOutputDevice: null
- property int pendingAudioOutputAttempts: 0
-
- readonly property var backend: backendOverride !== null ? backendOverride : root
- readonly property bool ready: true
- readonly property var adapter: backendOverride !== null
- ? ("adapter" in backendOverride ? backendOverride.adapter : null)
- : (adapterOverride !== null ? adapterOverride : Bluetooth.defaultAdapter)
- readonly property bool adapterAvailable: adapter !== null
- readonly property bool radioEnabled: adapterAvailable
- && adapter.enabled !== undefined && adapter.enabled === true
- readonly property bool discovering: adapterAvailable
- && adapter.discovering !== undefined && adapter.discovering === true
- readonly property var nativeDevices: nativeDevicesOverride !== null
- ? nativeDevicesOverride
- : (backendOverride === null && Bluetooth.devices ? Bluetooth.devices.values : [])
- readonly property var pipewireNodes: pipewireNodesOverride !== null
- ? pipewireNodesOverride
- : (backendOverride === null && Pipewire.nodes ? Pipewire.nodes.values : [])
- readonly property var nativeDeviceGroups: Model.deviceLists(nativeDevices)
- readonly property var connectedDevices: backendOverride !== null
- ? backendList("connectedDevices") : nativeDeviceGroups.connected
- readonly property var knownDevices: backendOverride !== null
- ? backendList("knownDevices") : nativeDeviceGroups.known
- readonly property var discoveredDevices: backendOverride !== null
- ? backendList("discoveredDevices") : nativeDeviceGroups.discovered
- readonly property var pendingActions: backendOverride !== null
- && "pendingActions" in backendOverride
- ? backendOverride.pendingActions : nativePendingActions
-
- visible: false
- width: 0
- height: 0
-
- function backendList(name) {
- if (backendOverride === null || !(name in backendOverride)) return []
- const values = backendOverride[name]
- return Array.isArray(values) ? values : []
- }
-
- function deviceLabel(device) {
- if (backendOverride !== null
- && typeof backendOverride.deviceLabel === "function")
- return String(backendOverride.deviceLabel(device) || "")
- return Model.deviceLabel(device)
- }
-
- function pendingAction(address) {
- const key = String(address || "")
- if (backendOverride !== null
- && typeof backendOverride.pendingAction === "function")
- return String(backendOverride.pendingAction(key) || "")
- return key && nativePendingActions[key] ? nativePendingActions[key] : ""
- }
-
- // discoveryOwned is only set if Shibumi changed false -> true. An already
- // active external scan is observed but never claimed or stopped by Shibumi.
- function startDiscovery() {
- if (!adapterAvailable || !radioEnabled) return false
- // A replacement service must first retire a teardown guard from its own
- // predecessor. If that pending start has already completed, inherit the
- // proven Shibumi request instead of misclassifying it as external and
- // letting the old guard stop it.
- const target = adapter
- const inheritedRequest = Local.BluetoothDiscoveryGuard.disarm(target)
- // Read the native property directly: the derived QML binding may still
- // hold the previous value inside the adapter's discoveringChanged turn.
- if (target && !!target.discovering) {
- if (inheritedRequest) {
- discoveryRequestedAdapter = null
- if (retiredDiscoveryAdapter === target)
- retiredDiscoveryAdapter = null
- discoveryOwned = true
- discoveryOwnerAdapter = target
- if (!retiredDiscoveryAdapter) discoveryRequestTimeout.stop()
- }
- return true
- }
- if (discoveryRequestedAdapter === target) return true
- if (retiredDiscoveryAdapter === target) retiredDiscoveryAdapter = null
- discoveryRequestedAdapter = target
- discoveryRequestTimeout.restart()
- requestAdapterDiscovery(target)
- return true
- }
-
- function requestAdapterDiscovery(target) {
- if (backendOverride !== null
- && typeof backendOverride.requestDiscovery === "function")
- backendOverride.requestDiscovery(target)
- else target.discovering = true
- }
-
- function confirmRequestedDiscovery() {
- const requested = discoveryRequestedAdapter
- if (!requested || !requested.discovering) return
- discoveryRequestedAdapter = null
- if (!discoveryDesired || requested !== adapter) {
- retiredDiscoveryAdapter = requested
- requested.discovering = false
- return
- }
- discoveryOwned = true
- discoveryOwnerAdapter = requested
- if (!retiredDiscoveryAdapter) discoveryRequestTimeout.stop()
- }
-
- function retirePendingDiscovery() {
- const requested = discoveryRequestedAdapter
- if (!requested) return
- discoveryRequestedAdapter = null
- retiredDiscoveryAdapter = requested
- discoveryRequestTimeout.restart()
- if (requested.discovering !== undefined && requested.discovering)
- requested.discovering = false
- }
-
- function expireDiscoveryRequestWindow() {
- confirmRequestedDiscovery()
- if (discoveryRequestedAdapter && !discoveryRequestedAdapter.discovering)
- discoveryRequestedAdapter = null
- const retired = retiredDiscoveryAdapter
- retiredDiscoveryAdapter = null
- if (retired && retired.discovering !== undefined && retired.discovering)
- retired.discovering = false
- }
-
- function stopDiscovery() {
- retirePendingDiscovery()
- const owner = discoveryOwnerAdapter
- if (discoveryOwned && owner && owner.discovering !== undefined
- && owner.discovering)
- owner.discovering = false
- discoveryOwned = false
- discoveryOwnerAdapter = null
- return true
- }
-
- function destroyDiscovery() {
- const requested = discoveryRequestedAdapter
- const retired = retiredDiscoveryAdapter
- if (requested)
- Local.BluetoothDiscoveryGuard.arm(requested, discoveryDestructionGuardInterval)
- if (retired && retired !== requested)
- Local.BluetoothDiscoveryGuard.arm(retired, discoveryDestructionGuardInterval)
- stopDiscovery()
- }
-
- function restartDiscovery() {
- if (!adapterAvailable || !radioEnabled) return false
- // A scan that was already active before Shibumi opened is externally
- // owned. Refresh must observe it without toggling or claiming it.
- if (discovering && !discoveryOwned) return true
- if (discovering) return stopDiscovery()
- return startDiscovery()
- }
-
- function toggleBluetooth() {
- if (!adapterAvailable) return false
- if (backendOverride !== null) {
- if (typeof backendOverride.toggleBluetooth !== "function") return false
- backendOverride.toggleBluetooth()
- return true
- }
- if (radioEnabled) stopDiscovery()
- adapter.enabled = !adapter.enabled
- return true
- }
-
- function setNativePendingAction(address, action) {
- if (!address) return
- nativePendingActions = Model.withPendingAction(
- nativePendingActions, String(address), String(action || ""))
- if (action) pendingTimeout.restart()
- }
-
- function rememberAudioHandoffIntent(device) {
- if (!device || !device.address) return
- // There can only be one default sink. A newer explicit connect request
- // therefore replaces every older, not-yet-consumed handoff intent.
- cancelPendingAudioOutput("")
- audioHandoffIntent = {
- address: String(device.address),
- name: device.name ? String(device.name) : "",
- deviceName: device.deviceName ? String(device.deviceName) : ""
- }
- audioIntentTimeout.restart()
- }
-
- function clearAudioHandoffIntent(address) {
- const key = String(address || "")
- if (!audioHandoffIntent || !key
- || String(audioHandoffIntent.address || "") !== key) return
- audioHandoffIntent = null
- audioIntentTimeout.stop()
- }
-
- function cancelPendingAudioOutput(address) {
- const key = String(address || "")
- if (pendingAudioOutputDevice && (!key
- || String(pendingAudioOutputDevice.address || "") === key)) {
- pendingAudioOutputDevice = null
- pendingAudioOutputAttempts = 0
- audioSwitchTimer.stop()
- }
- }
-
- function cancelAudioHandoff(address) {
- clearAudioHandoffIntent(address)
- cancelPendingAudioOutput(address)
- }
-
- function cancelAllAudioHandoffs() {
- audioHandoffIntent = null
- audioIntentTimeout.stop()
- cancelPendingAudioOutput("")
- }
-
- function deviceCommand(action, address) {
- return ["omarchy-bluetooth-device", String(action), String(address)]
- }
-
- function executeDeviceCommand(command) {
- if (commandRunnerOverride !== null
- && typeof commandRunnerOverride.run === "function") {
- commandRunnerOverride.run(command)
- return
- }
- Quickshell.execDetached(command)
- }
-
- function runNativeDeviceAction(device, action, pending) {
- if (!device || !device.address) return false
- setNativePendingAction(device.address, pending)
- executeDeviceCommand(deviceCommand(action, device.address))
- return true
- }
-
- function connectDevice(device) {
- if (!device || device.connected) return false
- if (backendOverride !== null) {
- if (typeof backendOverride.connectDevice !== "function") return false
- backendOverride.connectDevice(device)
- return true
- }
- const action = device.paired || device.bonded || device.trusted
- ? "connect" : "pair"
- rememberAudioHandoffIntent(device)
- return runNativeDeviceAction(device, action, "connecting")
- }
-
- function disconnectDevice(device) {
- if (!device || !device.address || !device.connected) return false
- if (backendOverride !== null) {
- if (typeof backendOverride.disconnectDevice !== "function") return false
- backendOverride.disconnectDevice(device)
- return true
- }
- cancelAudioHandoff(device.address)
- setNativePendingAction(device.address, "disconnecting")
- if (typeof device.disconnect === "function") device.disconnect()
- executeDeviceCommand(deviceCommand("disconnect", device.address))
- return true
- }
-
- function forgetDevice(device) {
- if (!device || !device.address) return false
- if (backendOverride !== null) {
- if (typeof backendOverride.forgetDevice !== "function") return false
- backendOverride.forgetDevice(device)
- return true
- }
- cancelAudioHandoff(device.address)
- return runNativeDeviceAction(device, "forget", "forgetting")
- }
-
- function audioSinks() {
- const sinks = []
- for (let i = 0; i < pipewireNodes.length; i++) {
- const node = pipewireNodes[i]
- if (node && node.isSink && !node.isStream) sinks.push(node)
- }
- return sinks
- }
-
- function bluetoothAudioSink(device) {
- const sinks = audioSinks()
- for (let i = 0; i < sinks.length; i++)
- if (Model.bluetoothSinkMatchesDevice(sinks[i], device)) return sinks[i]
- return null
- }
-
- function setDefaultAudioSink(sink) {
- if (!sink) return
- if (audioOutputOverride !== null
- && typeof audioOutputOverride.setDefaultSink === "function") {
- audioOutputOverride.setDefaultSink(sink)
- return
- }
- Pipewire.preferredDefaultAudioSink = sink
- if (sink.id === undefined || !sink.name) return
- Quickshell.execDetached([
- "omarchy-audio-output-set-default",
- String(sink.id),
- String(sink.name)
- ])
- }
-
- function scheduleAudioOutputSwitch(device) {
- if (!device || !device.address || !device.connected) return
- pendingAudioOutputDevice = {
- address: device && device.address ? device.address : "",
- name: device && device.name ? device.name : "",
- deviceName: device && device.deviceName ? device.deviceName : ""
- }
- pendingAudioOutputAttempts = 0
- audioSwitchTimer.restart()
- }
-
- function nativeDeviceByAddress(address) {
- const key = String(address || "")
- for (let i = 0; i < nativeDevices.length; i++) {
- const device = nativeDevices[i]
- if (device && String(device.address || "") === key) return device
- }
- return null
- }
-
- function deviceUsesCurrentAdapter(device) {
- return device && (device.adapter === undefined || device.adapter === null
- || device.adapter === adapter)
- }
-
- function validatePendingAudioOutput() {
- if (!pendingAudioOutputDevice) return false
- const device = nativeDeviceByAddress(pendingAudioOutputDevice.address)
- if (!radioEnabled || !device || !device.connected
- || !deviceUsesCurrentAdapter(device)) {
- cancelPendingAudioOutput("")
- return false
- }
- return true
- }
-
- function switchPendingAudioOutput() {
- if (!validatePendingAudioOutput()) return
- const device = nativeDeviceByAddress(pendingAudioOutputDevice.address)
- const sink = bluetoothAudioSink(device)
- if (sink) {
- setDefaultAudioSink(sink)
- pendingAudioOutputDevice = null
- audioSwitchTimer.stop()
- return
- }
- pendingAudioOutputAttempts++
- if (pendingAudioOutputAttempts >= 8) {
- pendingAudioOutputDevice = null
- return
- }
- audioSwitchTimer.restart()
- }
-
- function syncNativeAudioHandoffIntents() {
- if (backendOverride !== null) return
- const intent = audioHandoffIntent
- if (intent) {
- const device = nativeDeviceByAddress(intent.address)
- if (device && device.connected && deviceUsesCurrentAdapter(device)) {
- scheduleAudioOutputSwitch(device)
- audioHandoffIntent = null
- audioIntentTimeout.stop()
- }
- }
- validatePendingAudioOutput()
- }
-
- function syncNativePendingActions() {
- if (backendOverride !== null) return
- const next = Model.cloneMap(nativePendingActions)
- let changed = false
-
- for (const address in next) {
- const action = next[address]
- let found = null
- for (let i = 0; i < nativeDevices.length; i++) {
- const device = nativeDevices[i]
- if (device && device.address === address) {
- found = device
- break
- }
- }
-
- const finishedConnecting = action === "connecting" && found && found.connected
- if (finishedConnecting
- || (action === "disconnecting" && found && !found.connected)
- || (action === "forgetting" && (!found
- || (!found.paired && !found.bonded && !found.trusted)))) {
- delete next[address]
- changed = true
- }
- }
- if (changed) nativePendingActions = next
- }
-
- onNativeDevicesChanged: {
- syncNativePendingActions()
- syncNativeAudioHandoffIntents()
- }
- // ScriptModel.values changes when the collection changes, while a device's
- // connection/pairing flags have their own signals. The derived group signals
- // cover both paths and complete pending actions as soon as state settles.
- onConnectedDevicesChanged: {
- syncNativePendingActions()
- syncNativeAudioHandoffIntents()
- }
- onKnownDevicesChanged: {
- syncNativePendingActions()
- syncNativeAudioHandoffIntents()
- }
- onDiscoveredDevicesChanged: {
- syncNativePendingActions()
- syncNativeAudioHandoffIntents()
- }
- onDiscoveryDesiredChanged: if (!discoveryDesired) stopDiscovery()
- onRadioEnabledChanged: if (!radioEnabled) cancelAllAudioHandoffs()
- onAdapterChanged: {
- // Ownership is tied to the adapter instance on which Shibumi started the
- // scan. Stop that scan before observing a replacement adapter as external.
- retirePendingDiscovery()
- const owner = discoveryOwnerAdapter
- if (discoveryOwned && owner && owner !== adapter
- && owner.discovering !== undefined && owner.discovering)
- owner.discovering = false
- discoveryOwned = false
- discoveryOwnerAdapter = null
- cancelAllAudioHandoffs()
- }
- Component.onDestruction: destroyDiscovery()
-
- Timer {
- id: pendingTimeout
- interval: 20000
- repeat: false
- onTriggered: root.nativePendingActions = ({})
- }
-
- Timer {
- id: audioSwitchTimer
- interval: root.audioSwitchInterval
- repeat: false
- onTriggered: root.switchPendingAudioOutput()
- }
-
- Timer {
- id: audioIntentTimeout
- interval: root.audioIntentTimeoutInterval
- repeat: false
- onTriggered: root.audioHandoffIntent = null
- }
-
- Timer {
- id: discoveryRequestTimeout
- interval: root.discoveryRequestTimeoutInterval
- repeat: false
- onTriggered: root.expireDiscoveryRequestWindow()
- }
-
- Connections {
- target: root.discoveryRequestedAdapter
- ignoreUnknownSignals: true
- function onDiscoveringChanged() { root.confirmRequestedDiscovery() }
- }
-
- Connections {
- target: root.discoveryOwnerAdapter
- ignoreUnknownSignals: true
- function onDiscoveringChanged() {
- const owner = root.discoveryOwnerAdapter
- if (owner && !owner.discovering) {
- root.discoveryOwned = false
- root.discoveryOwnerAdapter = null
- }
- }
- }
-
- Connections {
- target: root.retiredDiscoveryAdapter
- ignoreUnknownSignals: true
- function onDiscoveringChanged() {
- const retired = root.retiredDiscoveryAdapter
- if (!retired) return
- if (retired.discovering) retired.discovering = false
- else {
- root.retiredDiscoveryAdapter = null
- if (!root.discoveryRequestedAdapter)
- discoveryRequestTimeout.stop()
- }
- }
- }
-}
diff --git a/adapters/BluetoothDiscoveryGuard.qml b/adapters/BluetoothDiscoveryGuard.qml
deleted file mode 100644
index 8799e27..0000000
--- a/adapters/BluetoothDiscoveryGuard.qml
+++ /dev/null
@@ -1,166 +0,0 @@
-pragma Singleton
-
-import QtQuick
-
-QtObject {
- id: root
-
- readonly property int initialRetryInterval: 250
- readonly property int maximumRetryInterval: 4000
- readonly property int settledStopInterval: 100
- property var guards: []
-
- function guardIndex(adapter) {
- for (let index = 0; index < guards.length; index++)
- if (guards[index].adapter === adapter) return index
- return -1
- }
-
- function isArmed(adapter) {
- return guardIndex(adapter) >= 0
- }
-
- function guardCount() {
- return guards.length
- }
-
- function removeGuard(entry) {
- if (!entry) return
- const index = guards.indexOf(entry)
- if (index < 0) return
- guards.splice(index, 1)
- const adapter = entry.adapter
- entry.adapter = null
- if (adapter) {
- try {
- adapter.discoveringChanged.disconnect(entry.handler)
- } catch (_error) {
- // The native adapter may disappear with the QML engine. There is then
- // no BlueZ session left for this guard to reconcile.
- }
- }
- const timer = entry.retryTimer
- entry.retryTimer = null
- if (timer) timer.destroy()
- const watcher = entry.adapterWatcher
- entry.adapterWatcher = null
- if (watcher) {
- watcher.armed = false
- watcher.destroy()
- }
- }
-
- function disarm(adapter) {
- const index = guardIndex(adapter)
- if (index < 0) return false
- removeGuard(guards[index])
- return true
- }
-
- function adapterDestroyed(entry) {
- if (!entry || guards.indexOf(entry) < 0) return
- entry.adapter = null
- entry.adapterWatcher = null
- removeGuard(entry)
- }
-
- function schedule(entry, interval) {
- if (!entry || guards.indexOf(entry) < 0) return
- const remaining = entry.expiresAt - Date.now()
- if (remaining <= 0) {
- removeGuard(entry)
- return
- }
- entry.retryTimer.interval = Math.max(1, Math.min(interval, remaining))
- entry.retryTimer.restart()
- }
-
- function reconcile(entry) {
- if (!entry || guards.indexOf(entry) < 0) return
- if (Date.now() >= entry.expiresAt) {
- removeGuard(entry)
- return
- }
- const adapter = entry.adapter
- if (!adapter) {
- removeGuard(entry)
- return
- }
- let discovering = false
- try {
- discovering = !!adapter.discovering
- } catch (_error) {
- entry.adapter = null
- removeGuard(entry)
- return
- }
- if (discovering) {
- entry.stopRequested = true
- entry.stopSettlePending = false
- try {
- adapter.discovering = false
- discovering = !!adapter.discovering
- } catch (_error) {
- entry.adapter = null
- removeGuard(entry)
- return
- }
- const retryInterval = entry.retryInterval
- entry.retryInterval = Math.min(maximumRetryInterval, retryInterval * 2)
- schedule(entry, discovering ? retryInterval : settledStopInterval)
- } else if (entry.stopRequested) {
- // StopDiscovery is asynchronous. Require the stopped state to survive a
- // later event-loop turn before releasing the guard.
- if (entry.stopSettlePending) removeGuard(entry)
- else {
- entry.stopSettlePending = true
- schedule(entry, settledStopInterval)
- }
- } else {
- // No delayed start has arrived yet. The signal handles it immediately;
- // this wakeup exists only to enforce the hard deadline.
- schedule(entry, entry.expiresAt - Date.now())
- }
- }
-
- function arm(adapter, timeoutMs) {
- if (!adapter || adapter.discovering === undefined
- || !adapter.discoveringChanged) return false
- disarm(adapter)
-
- const requestedInterval = Number(timeoutMs)
- const interval = isFinite(requestedInterval) && requestedInterval > 0
- ? requestedInterval : 30000
- const entry = {
- "adapter": adapter,
- "expiresAt": Date.now() + interval,
- "stopRequested": false,
- "stopSettlePending": false,
- "retryInterval": initialRetryInterval,
- "handler": null,
- "destroyedHandler": null,
- "retryTimer": null,
- "adapterWatcher": null
- }
- entry.handler = function() { root.reconcile(entry) }
- entry.destroyedHandler = function() { root.adapterDestroyed(entry) }
- entry.retryTimer = Qt.createQmlObject(
- 'import QtQuick; Timer { running: false; repeat: false }',
- root,
- "ShibumiBluetoothDiscoveryGuardTimer"
- )
- entry.adapterWatcher = Qt.createQmlObject(
- 'import QtQuick; QtObject {'
- + ' property bool armed: true; signal watchedDestroyed();'
- + ' Component.onDestruction: if (armed) watchedDestroyed() }',
- adapter,
- "ShibumiBluetoothDiscoveryAdapterWatcher"
- )
- entry.adapterWatcher.watchedDestroyed.connect(entry.destroyedHandler)
- entry.retryTimer.triggered.connect(function() { root.reconcile(entry) })
- guards.push(entry)
- adapter.discoveringChanged.connect(entry.handler)
- reconcile(entry)
- return true
- }
-}
diff --git a/adapters/BluetoothModel.js b/adapters/BluetoothModel.js
deleted file mode 100644
index 6608ab7..0000000
--- a/adapters/BluetoothModel.js
+++ /dev/null
@@ -1,115 +0,0 @@
-function deviceLabel(device) {
- if (!device) return ""
- return String(device.deviceName || device.name || "").trim()
-}
-
-function toArray(values) {
- if (!values) return []
- if (Array.isArray(values)) return values.slice()
-
- var length = Number(values.length || 0)
- if (!isFinite(length) || length <= 0) return []
-
- var list = []
- for (var i = 0; i < length; i++) list.push(values[i])
- return list
-}
-
-function isUuidLike(value) {
- var text = String(value || "").trim()
- if (text === "") return false
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(text)
- || /^[0-9a-f]{32}$/i.test(text)
- || /^0x[0-9a-f]{4,32}$/i.test(text)
- || /^0000[0-9a-f]{4}-0000-1000-8000-00805f9b34fb$/i.test(text)
-}
-
-function isAddressLike(value) {
- return /^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/i.test(String(value || "").trim())
-}
-
-function hasHumanName(device) {
- var label = deviceLabel(device)
- return label !== "" && !isUuidLike(label) && !isAddressLike(label)
-}
-
-function sortedByLabel(devices) {
- var list = toArray(devices)
- list.sort(function(a, b) { return deviceLabel(a).localeCompare(deviceLabel(b)) })
- return list
-}
-
-function deviceLists(devices) {
- var values = toArray(devices)
- var connected = []
- var known = []
- var discovered = []
-
- for (var i = 0; i < values.length; i++) {
- var device = values[i]
- if (!device || !hasHumanName(device)) continue
- if (device.connected) connected.push(device)
- else if (device.paired || device.bonded || device.trusted) known.push(device)
- else discovered.push(device)
- }
-
- return {
- connected: sortedByLabel(connected),
- known: sortedByLabel(known),
- discovered: sortedByLabel(discovered)
- }
-}
-
-function normalizedAddress(value) {
- return String(value || "").trim().toLowerCase().replace(/[^0-9a-f]/g, "")
-}
-
-function nodeProperties(node) {
- return node && node.ready && node.properties ? node.properties : ({})
-}
-
-function nodeText(node) {
- var properties = nodeProperties(node)
- return [
- node ? node.name : "",
- node ? node.description : "",
- node ? node.nickname : "",
- node ? node.nick : "",
- properties["node.name"],
- properties["node.description"],
- properties["node.nick"],
- properties["device.name"],
- properties["device.description"],
- properties["device.product.name"],
- properties["device.alias"],
- properties["device.string"],
- properties["api.bluez5.address"],
- properties["bluez5.address"],
- properties["media.name"]
- ].join(" ").toLowerCase()
-}
-
-function bluetoothSinkMatchesDevice(node, device) {
- if (!node || !node.isSink || node.isStream || !device) return false
-
- var address = normalizedAddress(device.address)
- var text = nodeText(node)
- if (address !== "" && normalizedAddress(text).indexOf(address) !== -1) return true
-
- var label = deviceLabel(device).toLowerCase()
- return label !== "" && text.indexOf(label) !== -1
-}
-
-function cloneMap(map) {
- var next = ({})
- for (var key in map || {}) next[key] = map[key]
- return next
-}
-
-function withPendingAction(actions, address, action) {
- var next = cloneMap(actions)
- if (!address) return next
- if (action) next[address] = action
- else delete next[address]
- return next
-}
diff --git a/adapters/MonitorPanelBridge.qml b/adapters/MonitorPanelBridge.qml
deleted file mode 100644
index 5bc7f96..0000000
--- a/adapters/MonitorPanelBridge.qml
+++ /dev/null
@@ -1,220 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-
-// Hosts Quattro's monitor component as the single process-wide state/action
-// owner. Shibumi owns only screen-local presentation and forwards mutations.
-Item {
- id: root
-
- required property var bar
- required property Item ownerWidget
- property Component panelComponent: null
- property url panelSource: ""
- property var panelSettings: ({})
-
- readonly property var panel: panelLoader.item
- readonly property bool ready: panel !== null
- readonly property bool opened: ready && panel.opened === true
- readonly property bool brightnessAvailable: ready
- && panel.brightnessAvailable !== undefined
- ? panel.brightnessAvailable === true : false
- readonly property int brightnessPercent: ready
- && panel.brightnessPercent !== undefined
- ? Math.max(0, Math.min(100, Number(panel.brightnessPercent) || 0)) : 0
- readonly property int displayCount: ready && panel.displays !== undefined
- && Array.isArray(panel.displays) ? panel.displays.length : 0
- readonly property string internalMonitor: ready
- && panel.internalMonitor !== undefined ? String(panel.internalMonitor || "") : ""
- readonly property string externalMonitor: ready
- && panel.externalMonitor !== undefined ? String(panel.externalMonitor || "") : ""
- readonly property string focusedMonitor: ready
- && panel.focusedMonitor !== undefined ? String(panel.focusedMonitor || "") : ""
- readonly property bool internalEnabled: ready
- && panel.internalEnabled !== undefined ? panel.internalEnabled === true : false
- readonly property bool mirrorEnabled: ready
- && panel.mirrorEnabled !== undefined ? panel.mirrorEnabled === true : false
- readonly property string monitorScale: ready
- && panel.monitorScale !== undefined ? String(panel.monitorScale || "") : ""
- readonly property var displays: ready && panel.displays !== undefined
- && Array.isArray(panel.displays) ? panel.displays : []
- readonly property int enabledDisplayCount: ready
- && panel.enabledDisplayCount !== undefined
- ? Math.max(0, Number(panel.enabledDisplayCount) || 0) : 0
- readonly property bool textSizeAvailable: ready
- && Array.isArray(panel.textSizeStops) && panel.textSizeStops.length > 0
- && typeof panel.currentTextIndex === "function"
- && typeof panel.displayedTextPx === "function"
- && typeof panel.setTextSize === "function"
- readonly property var textSizeStops: textSizeAvailable
- ? panel.textSizeStops : []
- readonly property int textSizeIndex: textSizeAvailable
- ? Math.max(0, Math.min(textSizeStops.length - 1,
- Number(panel.currentTextIndex()) || 0)) : 0
- readonly property real textSizePx: textSizeAvailable
- ? Number(panel.displayedTextPx()) || textSizeStops[textSizeIndex] : 0
-
- function refresh() {
- if (!ready || typeof panel.refresh !== "function") return false
- panel.refresh()
- return true
- }
-
- function setBrightness(value) {
- if (!ready || !brightnessAvailable
- || typeof panel.setBrightness !== "function") return false
- panel.setBrightness(Math.max(1, Math.min(100, Number(value) || 1)))
- return true
- }
-
- function previewBrightness(value) {
- if (!ready || !brightnessAvailable) return false
- if (typeof panel.previewBrightness === "function")
- panel.previewBrightness(Math.max(1, Math.min(100, Number(value) || 1)))
- else return setBrightness(value)
- return true
- }
-
- function setScale(value) {
- if (!ready || typeof panel.setScale !== "function") return false
- panel.setScale(String(value || ""))
- return true
- }
-
- function setTextSize(value) {
- if (!textSizeAvailable) return false
- const px = Math.round(Number(value) || 0)
- if (textSizeStops.indexOf(px) < 0) return false
- panel.setTextSize(px)
- return true
- }
-
- function adjustTextSize(delta) {
- if (!textSizeAvailable) return false
- const next = Math.max(0, Math.min(textSizeStops.length - 1,
- textSizeIndex + Math.sign(Number(delta) || 0)))
- return setTextSize(textSizeStops[next])
- }
-
- function toggleDisplay(name, enabled) {
- if (!ready || !String(name || "")
- || typeof panel.toggleDisplay !== "function") return false
- panel.toggleDisplay(String(name), enabled === true)
- return true
- }
-
- function normalizeScale(value) {
- if (ready && typeof panel.normalizeScale === "function")
- return String(panel.normalizeScale(value) || "")
- const number = parseFloat(String(value || ""))
- return isFinite(number) ? String(Math.round(number * 100) / 100) : ""
- }
-
- function brightnessName(value) {
- if (ready && typeof panel.brightnessName === "function")
- return String(panel.brightnessName(value) || "")
- const percent = Math.round(Number(value) || 0)
- if (percent >= 80) return "Bright"
- if (percent >= 45) return "Balanced"
- if (percent >= 20) return "Dim"
- return "Low light"
- }
-
- function injectPanel() {
- if (!panel) return
- if ("bar" in panel) panel.bar = hostProxy
- if ("moduleName" in panel) panel.moduleName = "omarchy.monitor"
- if ("settings" in panel) panel.settings = panelSettings
- if ("manageIpc" in panel) panel.manageIpc = false
- if (panel.opened === true && typeof panel.close === "function") panel.close()
- panel.opacity = 0
- }
-
- function syncPanelSource() {
- panelLoader.sourceComponent = null
- panelLoader.source = ""
- if (panelComponent !== null) {
- panelLoader.sourceComponent = panelComponent
- } else if (String(panelSource)) {
- panelLoader.setSource(panelSource, {
- bar: hostProxy,
- moduleName: "omarchy.monitor",
- manageIpc: false,
- settings: panelSettings
- })
- }
- }
-
- onPanelSettingsChanged: injectPanel()
- onBarChanged: injectPanel()
- onPanelComponentChanged: Qt.callLater(syncPanelSource)
- onPanelSourceChanged: Qt.callLater(syncPanelSource)
- Component.onCompleted: Qt.callLater(syncPanelSource)
- Component.onDestruction: {
- if (!panel || !bar) return
- if (panel.opened === true && typeof panel.close === "function") panel.close()
- }
-
- // The official component owns the legacy IPC target. Redirect an IPC open
- // to Shibumi's screen-local presentation and never leave the stock popup
- // mapped behind it.
- Connections {
- target: root.panel
- function onOpenedChanged() {
- if (!root.panel || root.panel.opened !== true) return
- if (root.bar && typeof root.bar.summonBarWidget === "function")
- root.bar.summonBarWidget("omarchy.monitor")
- Qt.callLater(function() {
- if (root.panel && root.panel.opened === true
- && typeof root.panel.close === "function") root.panel.close()
- })
- }
- }
-
- QtObject {
- id: hostProxy
-
- readonly property var realBar: root.bar
- readonly property bool vertical: realBar ? realBar.vertical === true : false
- readonly property int barSize: realBar ? Number(realBar.barSize) : 35
- readonly property int sizeHorizontal: realBar && realBar.sizeHorizontal !== undefined
- ? Number(realBar.sizeHorizontal) : barSize
- readonly property string position: realBar ? String(realBar.position || "top") : "top"
- readonly property string fontFamily: realBar ? String(realBar.fontFamily || "monospace") : "monospace"
- readonly property color background: realBar && realBar.background !== undefined
- ? realBar.background : "#111111"
- readonly property color barBackground: background
- readonly property color foreground: realBar ? realBar.foreground : "#ffffff"
- readonly property color barForeground: foreground
- readonly property color urgent: realBar ? realBar.urgent : foreground
- readonly property bool foregroundAnimationEnabled: realBar
- ? realBar.foregroundAnimationEnabled !== false : false
- readonly property var shell: realBar ? realBar.shell : null
- readonly property var activePopout: realBar ? realBar.activePopout : null
- readonly property var clickTargets: realBar ? realBar.clickTargets : []
-
- function registerClickTarget(_target) {}
- function unregisterClickTarget(_target) {}
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(_owner) {}
- function releasePopout(_owner) {}
- function switchPanelFrom(_owner, direction) {
- return realBar && typeof realBar.switchPanelFrom === "function"
- ? realBar.switchPanelFrom(root.ownerWidget, direction) : false
- }
- function targetBelongsToWindow(target, window) {
- return realBar && typeof realBar.targetBelongsToWindow === "function"
- ? realBar.targetBelongsToWindow(target, window) : false
- }
- }
-
- Loader {
- id: panelLoader
- anchors.fill: parent
- onLoaded: {
- root.injectPanel()
- Qt.callLater(root.injectPanel)
- }
- }
-}
diff --git a/adapters/NetworkPanelBridge.qml b/adapters/NetworkPanelBridge.qml
deleted file mode 100644
index 7e11cf8..0000000
--- a/adapters/NetworkPanelBridge.qml
+++ /dev/null
@@ -1,218 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-
-// Host Quattro's complete network component as a hidden backend. This bridge
-// is instantiated once by NetworkService, never once per output.
-Item {
- id: root
-
- required property var bar
- required property Item ownerWidget
- property var networkService: null
- property Component panelComponent: null
- property url panelSource: ""
- property var panelSettings: ({})
-
- readonly property var panel: panelLoader.item
- readonly property bool ready: panel !== null
- readonly property bool opened: ready && panel.opened === true
- readonly property bool backendAvailable: ready
- && panel.networkManagerAvailable !== undefined
- ? panel.networkManagerAvailable === true : false
- readonly property string kind: ready && panel.kind !== undefined
- ? String(panel.kind || "disconnected") : "disconnected"
- // Quattro exposed `label` in early panel revisions, then moved the live
- // Wi-Fi name to the native connected network object. Prefer the current
- // event-driven source and retain narrow fallbacks for older hosts and the
- // panel-only details sample.
- readonly property string label: !ready ? ""
- : kind === "wifi" && connectedWifiLabel() !== ""
- ? connectedWifiLabel()
- : panel.label !== undefined && String(panel.label || "") !== ""
- ? String(panel.label)
- : kind === "ethernet" && panel.info && panel.info.iface !== undefined
- ? String(panel.info.iface || "") : ""
- readonly property int signalStrength: ready
- && panel.signalStrength !== undefined
- ? Math.max(0, Math.min(100, Number(panel.signalStrength) || 0)) : 0
- readonly property bool scanning: ready && panel.scanning !== undefined
- ? panel.scanning === true : false
- readonly property bool busy: ready && panel.busy !== undefined
- ? panel.busy === true : false
-
- function connectedWifiLabel() {
- if (!ready || kind !== "wifi") return ""
-
- // `wifiNetworks` is the panel's normalized, event-driven public model and
- // carries the same connected row that the official panel renders. Prefer
- // it because some Qt NetworkManager objects expose `name` only after their
- // wrapper has emitted a later property change.
- const rows = panel.wifiNetworks
- if (Array.isArray(rows)) {
- for (let i = 0; i < rows.length; i++) {
- if (rows[i] && rows[i].connected === true
- && String(rows[i].ssid || "") !== "")
- return String(rows[i].ssid)
- }
- }
-
- const current = panel.connectedWifiNetwork
- if (current && current.name !== undefined
- && String(current.name || "") !== "")
- return String(current.name)
- if (panel.info && panel.info.ssid !== undefined)
- return String(panel.info.ssid || "")
- return ""
- }
-
- function open(scanWifi) {
- if (!ready || typeof panel.open !== "function") return false
- panel.open()
- if (scanWifi === true && typeof panel.refresh === "function") panel.refresh(true)
- return true
- }
-
- function close() {
- if (!ready || typeof panel.close !== "function") return false
- panel.close()
- return true
- }
-
- function toggle() {
- if (!ready) return false
- if (opened) return close()
- return open(false)
- }
-
- function suppressBackendKeyboardPanel() {
- if (!panel || !panel.data || panel.data.length === undefined) return false
- let suppressed = false
- for (let index = 0; index < panel.data.length; index++) {
- const candidate = panel.data[index]
- if (!candidate || typeof candidate.beginFocusPrime !== "function"
- || !("anchorItem" in candidate) || !("open" in candidate)
- || !("visible" in candidate) || !("owner" in candidate)
- || candidate.owner !== panel) continue
- candidate.open = false
- candidate.visible = false
- suppressed = true
- }
- return suppressed
- }
-
- function injectPanel() {
- if (!panel) return
- suppressBackendKeyboardPanel()
- if ("bar" in panel) panel.bar = hostProxy
- if ("moduleName" in panel) panel.moduleName = "omarchy.network"
- if ("settings" in panel) panel.settings = panelSettings
- if ("manageIpc" in panel) panel.manageIpc = false
- if (panel.opened === true && typeof panel.close === "function") panel.close()
- panel.opacity = 0
- }
-
- function syncPanelSource() {
- panelLoader.sourceComponent = null
- panelLoader.source = ""
- if (panelComponent !== null) {
- panelLoader.sourceComponent = panelComponent
- } else if (String(panelSource)) {
- panelLoader.setSource(panelSource, {
- bar: hostProxy,
- moduleName: "omarchy.network",
- manageIpc: false,
- settings: panelSettings
- })
- }
- }
-
- onPanelSettingsChanged: injectPanel()
- onBarChanged: injectPanel()
- onPanelComponentChanged: Qt.callLater(syncPanelSource)
- onPanelSourceChanged: Qt.callLater(syncPanelSource)
- Component.onCompleted: Qt.callLater(syncPanelSource)
- Component.onDestruction: {
- if (!panel || !bar) return
- if (panel.opened === true && typeof panel.close === "function") panel.close()
- }
-
- QtObject {
- id: hostProxy
-
- readonly property var realBar: root.bar
- readonly property bool vertical: realBar ? realBar.vertical === true : false
- readonly property int barSize: realBar ? Number(realBar.barSize) : 35
- readonly property int sizeHorizontal: realBar && realBar.sizeHorizontal !== undefined
- ? Number(realBar.sizeHorizontal) : barSize
- readonly property string position: realBar ? String(realBar.position || "top") : "top"
- readonly property string fontFamily: realBar ? String(realBar.fontFamily || "monospace") : "monospace"
- readonly property color foreground: realBar ? realBar.foreground : "#ffffff"
- readonly property color barForeground: foreground
- readonly property color urgent: realBar ? realBar.urgent : foreground
- readonly property bool foregroundAnimationEnabled: realBar
- ? realBar.foregroundAnimationEnabled !== false : false
- readonly property var shell: hostShellProxy
- readonly property var activePopout: realBar ? realBar.activePopout : null
- readonly property var clickTargets: realBar ? realBar.clickTargets : []
-
- function registerClickTarget(_target) {}
- function unregisterClickTarget(_target) {}
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
-
- function run(command) {
- if (realBar && typeof realBar.run === "function") realBar.run(command)
- }
-
- function requestPopout(owner) {
- if (realBar && typeof realBar.requestPopout === "function")
- realBar.requestPopout(owner)
- }
-
- function releasePopout(owner) {
- if (realBar && typeof realBar.releasePopout === "function")
- realBar.releasePopout(owner)
- }
-
- function switchPanelFrom(_owner, direction) {
- return realBar && typeof realBar.switchPanelFrom === "function"
- ? realBar.switchPanelFrom(root.ownerWidget, direction) : false
- }
-
- function targetBelongsToWindow(target, window) {
- return realBar && typeof realBar.targetBelongsToWindow === "function"
- ? realBar.targetBelongsToWindow(target, window) : false
- }
- }
-
- QtObject {
- id: hostShellProxy
-
- readonly property var realShell: root.bar ? root.bar.shell : null
-
- function summon(id, payloadJson) {
- if (String(id || "") === "omarchy.speedtest") {
- return root.networkService
- && typeof root.networkService.runSpeedTest === "function"
- && root.networkService.runSpeedTest() ? "ok" : "unknown"
- }
- return realShell && typeof realShell.summon === "function"
- ? realShell.summon(id, payloadJson) : "unknown"
- }
-
- function hide(id) {
- return realShell && typeof realShell.hide === "function"
- ? realShell.hide(id) : undefined
- }
- }
-
- Loader {
- id: panelLoader
- anchors.fill: parent
- onLoaded: {
- root.injectPanel()
- Qt.callLater(root.injectPanel)
- }
- }
-}
diff --git a/adapters/SystemActions.qml b/adapters/SystemActions.qml
deleted file mode 100644
index 2555d1b..0000000
--- a/adapters/SystemActions.qml
+++ /dev/null
@@ -1,13 +0,0 @@
-import QtQuick
-
-QtObject {
- id: root
-
- property var bar: null
-
- function openSystemMonitor() {
- if (!bar || typeof bar.run !== "function") return false
- bar.run("omarchy-launch-floating-terminal-with-presentation 'btop'")
- return true
- }
-}
diff --git a/adapters/WorkspaceActions.qml b/adapters/WorkspaceActions.qml
deleted file mode 100644
index d7db969..0000000
--- a/adapters/WorkspaceActions.qml
+++ /dev/null
@@ -1,19 +0,0 @@
-import QtQuick
-
-Item {
- id: root
-
- property var bar: null
-
- visible: false
- width: 0
- height: 0
-
- function focusWorkspace(value) {
- const id = Number(value)
- if (!Number.isInteger(id) || id <= 0 || id > 9999) return false
- if (!bar || typeof bar.run !== "function") return false
- bar.run("hyprctl dispatch 'hl.dsp.focus({ workspace = \"" + id + "\" })'")
- return true
- }
-}
diff --git a/adapters/qmldir b/adapters/qmldir
deleted file mode 100644
index 6b87e89..0000000
--- a/adapters/qmldir
+++ /dev/null
@@ -1,7 +0,0 @@
-singleton BluetoothDiscoveryGuard 1.0 BluetoothDiscoveryGuard.qml
-AudioPanelBridge 1.0 AudioPanelBridge.qml
-BluetoothBackendAdapter 1.0 BluetoothBackendAdapter.qml
-MonitorPanelBridge 1.0 MonitorPanelBridge.qml
-NetworkPanelBridge 1.0 NetworkPanelBridge.qml
-SystemActions 1.0 SystemActions.qml
-WorkspaceActions 1.0 WorkspaceActions.qml
diff --git a/assets/arch-header-arch.png b/assets/arch-header-arch.png
deleted file mode 100644
index f87d1df..0000000
Binary files a/assets/arch-header-arch.png and /dev/null differ
diff --git a/assets/arch-header-linux.png b/assets/arch-header-linux.png
deleted file mode 100644
index 813a621..0000000
Binary files a/assets/arch-header-linux.png and /dev/null differ
diff --git a/assets/bob2.png b/assets/bob2.png
deleted file mode 100644
index 83a77ce..0000000
Binary files a/assets/bob2.png and /dev/null differ
diff --git a/assets/bob3.png b/assets/bob3.png
deleted file mode 100644
index ac7afa6..0000000
Binary files a/assets/bob3.png and /dev/null differ
diff --git a/assets/codex.svg b/assets/codex.svg
deleted file mode 100644
index b40dd62..0000000
--- a/assets/codex.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/assets/logo-tint.frag.qsb b/assets/logo-tint.frag.qsb
deleted file mode 100644
index 520a691..0000000
Binary files a/assets/logo-tint.frag.qsb and /dev/null differ
diff --git a/assets/omacom-text.png b/assets/omacom-text.png
deleted file mode 100644
index 48e52a0..0000000
Binary files a/assets/omacom-text.png and /dev/null differ
diff --git a/assets/opencode-mark.svg b/assets/opencode-mark.svg
deleted file mode 100644
index 290cc4e..0000000
--- a/assets/opencode-mark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/contracts/v1-embedded-v2-differences.json b/contracts/v1-embedded-v2-differences.json
index 9d49262..cf6bfeb 100644
--- a/contracts/v1-embedded-v2-differences.json
+++ b/contracts/v1-embedded-v2-differences.json
@@ -47,7 +47,7 @@
"impact": "cava-process-lifecycle",
"decision": "adapt-safer",
"implementation": ["hancore.shibumi.media/Service.qml", "hancore.shibumi.media/BarWidget.qml"],
- "evidence": ["tests/audio-media-plugins-regression.sh", "tests/media-widget-smoke.qml"]
+ "evidence": ["tests/audio-media-plugins-regression.sh", "tests/media-plugin-smoke.qml"]
},
{
"source": "modules/WorkspaceWidget.qml",
diff --git a/contracts/v1-feature-evidence.json b/contracts/v1-feature-evidence.json
index e283d85..362e746 100644
--- a/contracts/v1-feature-evidence.json
+++ b/contracts/v1-feature-evidence.json
@@ -37,7 +37,7 @@
"strategy": "adapt",
"sources": ["IconMap.js"],
"implementation": ["shared/presentation/IconText.qml", "hancore.shibumi.control-center/ControlSettings.qml"],
- "evidence": ["tests/shibumi-presentation-smoke.qml", "tests/control-center-smoke.qml"]
+ "evidence": ["tests/group-renderer-regression.qml", "tests/control-center-smoke.qml"]
},
{
"id": "power-actions",
@@ -86,7 +86,7 @@
"strategy": "port",
"sources": ["modules/BatteryWidget.qml", "panels/BatteryPanel.qml"],
"implementation": ["hancore.shibumi.battery/BarWidget.qml", "hancore.shibumi.battery/BatteryPanel.qml"],
- "evidence": ["tests/power-plugins-regression.sh", "tests/power-widgets-smoke.qml"]
+ "evidence": ["tests/power-plugins-regression.sh", "tests/power-plugins-smoke.qml"]
},
{
"id": "bluetooth",
@@ -107,7 +107,7 @@
"strategy": "port",
"sources": ["modules/ClockWidget.qml", "panels/CalendarPopup.qml"],
"implementation": ["hancore.shibumi.center/ClockWidget.qml", "hancore.shibumi.center/CalendarPanel.qml"],
- "evidence": ["tests/center-plugin-regression.sh", "tests/clock-widget-smoke.qml"]
+ "evidence": ["tests/center-plugin-regression.sh", "tests/center-plugin-smoke.qml"]
},
{
"id": "ai-usage",
@@ -128,7 +128,7 @@
"strategy": "port",
"sources": ["modules/IconText.qml", "modules/UiText.qml"],
"implementation": ["shared/presentation/IconText.qml"],
- "evidence": ["tests/shibumi-presentation-smoke.qml", "tests/style-contract-regression.sh"]
+ "evidence": ["tests/group-renderer-regression.qml", "tests/style-contract-regression.sh"]
},
{
"id": "status-indicators",
@@ -191,7 +191,7 @@
"strategy": "port",
"sources": ["modules/PillShadow.qml"],
"implementation": ["shared/presentation/PillSurface.qml", "styles/shibumi/RunChrome.qml"],
- "evidence": ["tests/shibumi-presentation-smoke.qml", "tests/run-geometry-regression.qml"]
+ "evidence": ["tests/group-renderer-regression.qml", "tests/run-geometry-regression.qml"]
},
{
"id": "power-profile",
@@ -212,7 +212,7 @@
"strategy": "port",
"sources": ["modules/TooltipMixin.qml", "panels/TooltipOverlay.qml"],
"implementation": ["shared/presentation/ShibumiPanelToolTip.qml", "hancore.shibumi.bar/Bar.qml"],
- "evidence": ["tests/panel-surface-smoke.qml", "tests/shibumi-presentation-smoke.qml"]
+ "evidence": ["tests/panel-surface-smoke.qml", "tests/group-renderer-regression.qml"]
},
{
"id": "tray",
diff --git a/contracts/v2-feature-evidence.json b/contracts/v2-feature-evidence.json
index 539d6dc..0ce0d52 100644
--- a/contracts/v2-feature-evidence.json
+++ b/contracts/v2-feature-evidence.json
@@ -3,13 +3,13 @@
"referenceBaseline": "contracts/baselines/quickshell-dots-d0896fc-v2-deec8103.json",
"referenceRootId": "v2",
"features": [
- {"id":"bar-shell.full","implementation":["styles/shibumi/RunChrome.qml","styles/shibumi/BarSurface.qml"],"tests":["tests/shibumi-presentation-smoke.qml","tests/style-contract-regression.sh"]},
- {"id":"bar-shell.fit","implementation":["styles/shibumi/RunChrome.qml","styles/shibumi/BarSurface.qml"],"tests":["tests/shibumi-presentation-smoke.qml","tests/style-contract-regression.sh"]},
- {"id":"bar-shell.dock","implementation":["styles/shibumi/RunChrome.qml","styles/shibumi/BarSurface.qml"],"tests":["tests/shibumi-presentation-smoke.qml","tests/style-contract-regression.sh"]},
- {"id":"bar-shell.notch","implementation":["styles/shibumi/RunChrome.qml","styles/shibumi/BarSurface.qml"],"tests":["tests/shibumi-presentation-smoke.qml","tests/style-contract-regression.sh"]},
+ {"id":"bar-shell.full","implementation":["styles/shibumi/RunChrome.qml","styles/shibumi/BarSurface.qml"],"tests":["tests/group-renderer-regression.qml","tests/style-contract-regression.sh"]},
+ {"id":"bar-shell.fit","implementation":["styles/shibumi/RunChrome.qml","styles/shibumi/BarSurface.qml"],"tests":["tests/group-renderer-regression.qml","tests/style-contract-regression.sh"]},
+ {"id":"bar-shell.dock","implementation":["styles/shibumi/RunChrome.qml","styles/shibumi/BarSurface.qml"],"tests":["tests/group-renderer-regression.qml","tests/style-contract-regression.sh"]},
+ {"id":"bar-shell.notch","implementation":["styles/shibumi/RunChrome.qml","styles/shibumi/BarSurface.qml"],"tests":["tests/group-renderer-regression.qml","tests/style-contract-regression.sh"]},
{"id":"bar.responsive-staging","implementation":["core/ResponsiveLayout.js","styles/shibumi/BarSurface.qml"],"tests":["tests/responsive-layout-regression.qml"]},
{"id":"bar.inline-slot-controls","implementation":["styles/shibumi/GroupSection.qml","core/LayoutController.qml"],"tests":["tests/style-contract-regression.sh","tests/shibumi-config-regression.qml"]},
- {"id":"bar.border-toggle","implementation":["styles/shibumi/VisualTokens.qml","styles/shibumi/RunChrome.qml"],"tests":["tests/shibumi-presentation-smoke.qml","tests/state-service-smoke.qml"]},
+ {"id":"bar.border-toggle","implementation":["styles/shibumi/VisualTokens.qml","styles/shibumi/RunChrome.qml"],"tests":["tests/group-renderer-regression.qml","tests/state-service-smoke.qml"]},
{"id":"panel.border-toggle","implementation":["styles/shibumi/VisualTokens.qml","shared/presentation/ShibumiPanel.qml"],"tests":["tests/panel-surface-smoke.qml","tests/state-service-smoke.qml"]},
{"id":"panel.connected-silhouette","implementation":["styles/shibumi/RunChrome.qml","shared/presentation/ShibumiPanel.qml"],"tests":["tests/panel-surface-smoke.qml","tests/panel-routing-regression.qml"]},
{"id":"panel.pointer","implementation":["shared/presentation/ShibumiPanel.qml"],"tests":["tests/panel-surface-smoke.qml"]},
@@ -19,18 +19,18 @@
{"id":"picker.tanzaku","implementation":["hancore.shibumi.quick-access/TanzakuPickerView.qml"],"tests":["tests/quick-access-plugin-smoke.qml","tests/quick-access-plugin-regression.sh"]},
{"id":"picker.hearthstone","implementation":["hancore.shibumi.quick-access/HearthstonePickerView.qml"],"tests":["tests/quick-access-plugin-smoke.qml","tests/quick-access-plugin-regression.sh"]},
{"id":"picker.carousel","implementation":["hancore.shibumi.quick-access/CarouselPickerView.qml","hancore.shibumi.quick-access/Service.qml"],"tests":["tests/quick-access-plugin-regression.sh","tests/shibumi-config-regression.qml"]},
- {"id":"widget.palette","implementation":["styles/shibumi/VisualTokens.qml","core/GroupSlot.qml"],"tests":["tests/shibumi-presentation-smoke.qml","tests/state-service-smoke.qml"]},
- {"id":"widget.fill-border-modes","implementation":["styles/shibumi/VisualTokens.qml","core/GroupSlot.qml"],"tests":["tests/shibumi-presentation-smoke.qml","tests/group-renderer-regression.qml"]},
- {"id":"widget.separators","implementation":["styles/shibumi/VisualTokens.qml","core/GroupSlot.qml"],"tests":["tests/shibumi-presentation-smoke.qml","tests/group-renderer-regression.qml"]},
- {"id":"widget.compact-modes","implementation":["hancore.shibumi.control-center/BarFunctionsPage.qml","hancore.shibumi.audio/BarWidget.qml"],"tests":["tests/control-center-smoke.qml","tests/audio-widget-smoke.qml"]},
- {"id":"widget.icon-only-modes","implementation":["hancore.shibumi.control-center/BarFunctionsPage.qml","hancore.shibumi.status/BarWidget.qml"],"tests":["tests/control-center-smoke.qml","tests/status-widget-smoke.qml"]},
+ {"id":"widget.palette","implementation":["styles/shibumi/VisualTokens.qml","core/GroupSlot.qml"],"tests":["tests/group-renderer-regression.qml","tests/state-service-smoke.qml"]},
+ {"id":"widget.fill-border-modes","implementation":["styles/shibumi/VisualTokens.qml","core/GroupSlot.qml"],"tests":["tests/group-renderer-regression.qml","tests/style-contract-regression.sh"]},
+ {"id":"widget.separators","implementation":["styles/shibumi/VisualTokens.qml","core/GroupSlot.qml"],"tests":["tests/group-renderer-regression.qml","tests/style-contract-regression.sh"]},
+ {"id":"widget.compact-modes","implementation":["hancore.shibumi.control-center/BarFunctionsPage.qml","hancore.shibumi.audio/BarWidget.qml"],"tests":["tests/control-center-smoke.qml","tests/audio-plugin-smoke.qml"]},
+ {"id":"widget.icon-only-modes","implementation":["hancore.shibumi.control-center/BarFunctionsPage.qml","hancore.shibumi.status/BarWidget.qml"],"tests":["tests/control-center-smoke.qml","tests/status-plugin-smoke.qml"]},
{"id":"temperature.widget","implementation":["hancore.shibumi.temperature/BarWidget.qml"],"tests":["tests/telemetry-plugins-smoke.qml","tests/telemetry-plugins-regression.sh"]},
{"id":"temperature.source-selection","implementation":["hancore.shibumi.telemetry/ThermalTelemetry.qml"],"tests":["tests/telemetry-plugins-smoke.qml","tests/telemetry-plugins-regression.sh"]},
{"id":"thermals.panel","implementation":["hancore.shibumi.temperature/TemperaturePanel.qml"],"tests":["tests/telemetry-plugins-smoke.qml","tests/telemetry-plugins-regression.sh"]},
{"id":"gpu.widget-panel","implementation":["hancore.shibumi.gpu/BarWidget.qml","hancore.shibumi.gpu/GpuPanel.qml"],"tests":["tests/telemetry-plugins-smoke.qml","tests/telemetry-plugins-regression.sh"]},
{"id":"storage.widget-panel","implementation":["hancore.shibumi.storage/BarWidget.qml","hancore.shibumi.storage/StoragePanel.qml","hancore.shibumi.storage/StorageTelemetry.qml"],"tests":["tests/storage-plugin-smoke.qml","tests/storage-plugin-regression.sh"]},
- {"id":"media.full-muse","implementation":["hancore.shibumi.media/BarWidget.qml","hancore.shibumi.media/Service.qml"],"tests":["tests/media-plugin-smoke.qml","tests/media-widget-smoke.qml"]},
- {"id":"media.artwork","implementation":["hancore.shibumi.media/MediaPanel.qml"],"tests":["tests/media-plugin-smoke.qml","tests/media-widget-smoke.qml"]},
+ {"id":"media.full-muse","implementation":["hancore.shibumi.media/BarWidget.qml","hancore.shibumi.media/Service.qml"],"tests":["tests/media-plugin-smoke.qml","tests/audio-media-plugins-regression.sh"]},
+ {"id":"media.artwork","implementation":["hancore.shibumi.media/MediaPanel.qml"],"tests":["tests/media-plugin-smoke.qml","tests/audio-media-plugins-regression.sh"]},
{"id":"tray.drawer","implementation":["hancore.shibumi.status/TrayDrawerPanel.qml"],"tests":["tests/status-plugin-smoke.qml","tests/status-plugin-regression.sh"]},
{"id":"tray.item-menu","implementation":["hancore.shibumi.status/TrayAppMenuPanel.qml"],"tests":["tests/tray-app-menu-smoke.qml","tests/status-plugin-regression.sh"]},
{"id":"launcher.logo-variants","implementation":["hancore.shibumi.state/ShibumiConfig.js","hancore.shibumi.control-center/BarWidget.qml"],"tests":["tests/shibumi-config-regression.qml","tests/control-center-smoke.qml"]},
diff --git a/contracts/v2-source-evidence.json b/contracts/v2-source-evidence.json
index 5e97726..d8cf2b6 100644
--- a/contracts/v2-source-evidence.json
+++ b/contracts/v2-source-evidence.json
@@ -30,7 +30,7 @@
"strategy": "adapt",
"sources": ["IconMap.js"],
"implementation": ["shared/presentation/IconText.qml", "hancore.shibumi.control-center/ControlSettings.qml"],
- "evidence": ["tests/shibumi-presentation-smoke.qml", "tests/control-center-smoke.qml"]
+ "evidence": ["tests/group-renderer-regression.qml", "tests/control-center-smoke.qml"]
},
{
"id": "power-actions",
@@ -44,7 +44,7 @@
"strategy": "port",
"sources": ["modules/AiBarInset.qml", "modules/AiPanelSurface.qml", "modules/ConnectedBarInset.qml", "modules/ConnectedPanelSurface.qml", "modules/PanelPointer.qml"],
"implementation": ["styles/shibumi/RunChrome.qml", "shared/presentation/ShibumiPanel.qml"],
- "evidence": ["tests/shibumi-presentation-smoke.qml", "tests/panel-surface-smoke.qml", "tests/panel-routing-regression.qml"]
+ "evidence": ["tests/group-renderer-regression.qml", "tests/panel-surface-smoke.qml", "tests/panel-routing-regression.qml"]
},
{
"id": "control-center",
@@ -79,7 +79,7 @@
"strategy": "port",
"sources": ["modules/BatteryWidget.qml", "panels/BatteryPanel.qml"],
"implementation": ["hancore.shibumi.battery/BarWidget.qml", "hancore.shibumi.battery/BatteryPanel.qml"],
- "evidence": ["tests/power-plugins-regression.sh", "tests/power-widgets-smoke.qml"]
+ "evidence": ["tests/power-plugins-regression.sh", "tests/power-plugins-smoke.qml"]
},
{
"id": "bluetooth",
@@ -100,7 +100,7 @@
"strategy": "port",
"sources": ["modules/ClockWidget.qml", "panels/CalendarPopup.qml"],
"implementation": ["hancore.shibumi.center/ClockWidget.qml", "hancore.shibumi.center/CalendarPanel.qml"],
- "evidence": ["tests/center-plugin-regression.sh", "tests/clock-widget-smoke.qml"]
+ "evidence": ["tests/center-plugin-regression.sh", "tests/center-plugin-smoke.qml"]
},
{
"id": "ai-usage",
@@ -121,7 +121,7 @@
"strategy": "port",
"sources": ["modules/IconText.qml", "modules/UiText.qml"],
"implementation": ["shared/presentation/IconText.qml"],
- "evidence": ["tests/shibumi-presentation-smoke.qml", "tests/style-contract-regression.sh"]
+ "evidence": ["tests/group-renderer-regression.qml", "tests/style-contract-regression.sh"]
},
{
"id": "status-indicators",
@@ -198,7 +198,7 @@
"strategy": "port",
"sources": ["modules/PillShadow.qml"],
"implementation": ["shared/presentation/PillSurface.qml", "styles/shibumi/RunChrome.qml"],
- "evidence": ["tests/shibumi-presentation-smoke.qml", "tests/run-geometry-regression.qml"]
+ "evidence": ["tests/group-renderer-regression.qml", "tests/run-geometry-regression.qml"]
},
{
"id": "power-profile",
@@ -219,7 +219,7 @@
"strategy": "port",
"sources": ["modules/TooltipMixin.qml", "panels/TooltipOverlay.qml"],
"implementation": ["shared/presentation/ShibumiPanelToolTip.qml", "hancore.shibumi.bar/Bar.qml"],
- "evidence": ["tests/panel-surface-smoke.qml", "tests/shibumi-presentation-smoke.qml"]
+ "evidence": ["tests/panel-surface-smoke.qml", "tests/group-renderer-regression.qml"]
},
{
"id": "tray",
diff --git a/docs/bluetooth-ipc-root-cause-plan.md b/docs/bluetooth-ipc-root-cause-plan.md
index 54e996d..f7c0d92 100644
--- a/docs/bluetooth-ipc-root-cause-plan.md
+++ b/docs/bluetooth-ipc-root-cause-plan.md
@@ -269,24 +269,21 @@ ein Modellmodul begrenzt.
### Phase 4 – Implementierung ausschließlich in Shibumi
Abhängig von Phase 3 werden nur die betroffenen Shibumi-Quellen geändert.
-Wegen der parallelen V1-/Plugin-Struktur sind mindestens diese Spiegel zu
-prüfen und konsistent zu halten:
+Die Implementierung wurde in diesen ausgelieferten Plugin-Quellen geprüft:
- `hancore.shibumi.bluetooth/Service.qml`
- `hancore.shibumi.bluetooth/BluetoothBackendAdapter.qml`
- `hancore.shibumi.bluetooth/BluetoothModel.js`
- `hancore.shibumi.bluetooth/BarWidget.qml`
-- `services/BluetoothService.qml`
-- `adapters/BluetoothBackendAdapter.qml`
-- `adapters/BluetoothModel.js`
-- `widgets/BluetoothWidget.qml`
+
+Die damaligen parallelen Root-Spiegel wurden in Step 0 nach dem Retargeting der
+Assertions entfernt und sind keine aktuelle Autorenquelle mehr.
Außerdem werden die falschen Verträge und Dokumentationsaussagen korrigiert:
- `tests/contract-regression.sh`
- `tests/bluetooth-plugin-regression.sh`
- `tests/bluetooth-plugin-smoke.qml`
-- `tests/bluetooth-widget-smoke.qml`
- Bluetooth-Fixtures unter `tests/fixtures/`
- `docs/phase2-validation.md`
- `docs/phase2-ownership-map.md`
@@ -294,7 +291,8 @@ Außerdem werden die falschen Verträge und Dokumentationsaussagen korrigiert:
- [x] Keine Datei unter `/usr/share/omarchy` verändern.
- [x] Keine Runtime-Warnung unterdrücken.
- [x] Keine reale Bluetooth-Funkmutation in automatischen Produktionstests.
-- [x] Quell- und Plugin-Spiegel über denselben Contract und denselben Smoke prüfen.
+- [x] Ausgelieferte Pluginquellen und isolierte Fixtures über denselben Contract
+ und denselben Smoke prüfen.
### Phase 5 – Testpyramide
diff --git a/docs/phase2-ownership-map.md b/docs/phase2-ownership-map.md
index 5aa7af9..b781d04 100644
--- a/docs/phase2-ownership-map.md
+++ b/docs/phase2-ownership-map.md
@@ -30,20 +30,20 @@ Every feature has exactly one state owner and one action boundary:
| Group | V1 contents | Authoritative Shibumi state/action owner | Shibumi responsibility |
| --- | --- | --- | --- |
| G1 | Shibumi wordmark and Control Center | Independent `hancore.shibumi.control-center` widget/panel over host-owned Shibumi settings | Preserve the V1 wordmark/icon, screen-local Control Center, lifecycle-gated feedback, colors 01-07 plus foreground, and complete bar-settings contract without opening or owning the App Menu; the current eight-choice palette is implemented and accepted on the validation system |
-| G2 | Workspaces and workspace panel | One root-owned `Quickshell.Hyprland` model plus validated Quattro focus adapter | Shibumi modes, three presentation styles and lazy screen-local panel implemented locally; real-Wayland acceptance remains |
+| G2 | Workspaces and workspace panel | One process-wide `hancore.shibumi.workspaces` service over `Quickshell.Hyprland` plus its validated focus adapter | Shibumi modes, three presentation styles and lazy screen-local panel implemented locally; real-Wayland acceptance remains |
| G3 | Tray and notifications | Complete official `omarchy.tray` and `omarchy.notifications` components | One V1 status pill groups both components without copying SystemTray, notification, DND, history, menu, or action ownership; Omarchy update presentation is restored to G8 and no V1 self-updater exists in Shibumi |
| G4 | Memory | Shibumi singleton system telemetry | Compact/full widget and lazy detail panel |
| G5 | CPU | Shibumi singleton system telemetry | Compact/full widget and lazy detail panel |
| G6 | Volume | Official `omarchy.audio` state/actions through a narrow bridge; one open-only local peak monitor | V1 full/compact pill and lazy V1 mixer panel cover descriptively labeled output/input devices, per-app streams, mute, and meter; real Wayland/top-bottom/multi-output acceptance remains |
-| G7 | Claude, Codex, and OpenCode usage | One root-owned Shibumi AI usage service over primitive current `omarchy.agents` schema-v1 records; pinned older hosts exclusively fall back to their official Claude/Codex `omarchy.model-usage` providers, and one read-only OpenCode SQLite provider fills the remaining host gap | One selected-provider pill, V1 usage fill, local lazy panel, provider cycling, refresh, current `omarchy.agents` and legacy `omarchy.model-usage` routing, and no duplicate visible host panel, cache, or screen-local poller ownership; OpenCode is accepted on the validation system while current-host Claude/Codex real-account and multi-output gates remain |
+| G7 | Claude, Codex, and OpenCode usage | One process-wide `hancore.shibumi.ai` service over primitive current `omarchy.agents` schema-v1 records; pinned older hosts exclusively fall back to their official Claude/Codex `omarchy.model-usage` providers, and one read-only OpenCode SQLite provider fills the remaining host gap | One selected-provider pill, V1 usage fill, local lazy panel, provider cycling, refresh, current `omarchy.agents` and legacy `omarchy.model-usage` routing, and no duplicate visible host panel, cache, or screen-local poller ownership; OpenCode is accepted on the validation system while current-host Claude/Codex real-account and multi-output gates remain |
| G8 | Weather, clock, date, status indicators, Omarchy update | Shared Shibumi clock/status adapters plus official Quattro weather-detail, idle, notification, and update owners | Single V1 pill, lazy routed calendar, local weather/status/update facades, child settings, and monitor-local normal/compact/minimal stages are implemented. Top weather, update-available, idle/DND, the complete recording-indicator lifecycle, and a real Voxtype recording/transcription cycle pass on the validation system; bottom and physical multi-output acceptance remain. |
| G9 | MPRIS | Keep-loaded official `omarchy.media` service plus one lazy process-wide Shibumi Cava service | Preserve the default row and current V1 FULL/muse 24-band spectrum, vinyl, transport, click, and wheel outcome; implementation plus real-player/failure/resource/Top-Bottom single-output acceptance pass, while multiple-real-player and physical multi-output gates remain |
-| G10 | Idle inhibitor, media browser, theme/wallpaper picker | Surface-bound Quickshell idle inhibitor, Omarchy theme/background actions, and one root-owned Shibumi picker/media controller | V1 quick-tools pill plus Tanzaku and Hearthstone presentation-only views; Quattro keeps its native carousel; basic focused-output runtime and worker cleanup accepted on the validation system |
-| G11 | Network | One root-owned service around the official `omarchy.network` state/action backend, plus Shibumi-owned inline speed-test and saved-profile workers | V1 `NET`/SSID/signal and compact views plus a lazy local details/DNS/speed-test/available/saved/connect/forget panel; top Wayland mapping and cleanup pass on the validation system, while mutation, bottom, and physical multi-output acceptance remain |
-| G12 | Battery | One root-owned Shibumi power service over Quickshell's UPower singleton; Quattro battery helper only while the detail panel is open | V1 compact/full battery view and lazy battery panel; hidden on batteryless desktops; a real discharging-to-charging transition passes on the validation system |
-| G13 | Brightness | One root-owned service around the complete official `omarchy.monitor` backend | V1 `BRI`/sun/percentage and compact views plus a lazy, screen-local Shibumi brightness/scale/display panel; top/Bottom mapping, reversible laptop brightness mutation, and a real `1.0 -> 1.25 -> 1.0` scale round trip pass on the validation system, while physical display enable/disable and multi-output acceptance remain |
-| G14 | Power profile | Same root-owned Shibumi power service; one Quattro profile-list poller and one validated setter | V1 compact/full profile view and lazy profile panel; remains available without a battery |
-| G15 | Bluetooth | One root-owned Shibumi service and native Quickshell BlueZ/PipeWire adapter; no complete Omarchy Bluetooth component is loaded | V1 full/compact widget and lazy per-output Shibumi device panel; confirmed discovery ownership with bounded reconciliation, latest-only audio handoff intent, and one symmetric six-method legacy IPC target; top mapping and real adapter/radio/discovery lifecycle pass on the validation system, while real device/audio, bottom, and physical multi-output acceptance remain |
+| G10 | Idle inhibitor, media browser, theme/wallpaper picker | Surface-bound Quickshell idle inhibitor, Omarchy theme/background actions, and one process-wide `hancore.shibumi.quick-access` picker/media controller | V1 quick-tools pill plus Tanzaku and Hearthstone presentation-only views; Quattro keeps its native carousel; basic focused-output runtime and worker cleanup accepted on the validation system |
+| G11 | Network | One process-wide `hancore.shibumi.network` service around the official `omarchy.network` state/action backend, plus Shibumi-owned inline speed-test and saved-profile workers | V1 `NET`/SSID/signal and compact views plus a lazy local details/DNS/speed-test/available/saved/connect/forget panel; top Wayland mapping and cleanup pass on the validation system, while mutation, bottom, and physical multi-output acceptance remain |
+| G12 | Battery | One process-wide `hancore.shibumi.power-state` service over Quickshell's UPower singleton; Quattro battery helper only while the detail panel is open | V1 compact/full battery view and lazy battery panel; hidden on batteryless desktops; a real discharging-to-charging transition passes on the validation system |
+| G13 | Brightness | One process-wide `hancore.shibumi.brightness` service around the complete official `omarchy.monitor` backend | V1 `BRI`/sun/percentage and compact views plus a lazy, screen-local Shibumi brightness/scale/display panel; top/Bottom mapping, reversible laptop brightness mutation, and a real `1.0 -> 1.25 -> 1.0` scale round trip pass on the validation system, while physical display enable/disable and multi-output acceptance remain |
+| G14 | Power profile | Same process-wide `hancore.shibumi.power-state` service; one Quattro profile-list poller and one validated setter | V1 compact/full profile view and lazy profile panel; remains available without a battery |
+| G15 | Bluetooth | One process-wide `hancore.shibumi.bluetooth` service and native Quickshell BlueZ/PipeWire adapter; no complete Omarchy Bluetooth component is loaded | V1 full/compact widget and lazy per-output Shibumi device panel; confirmed discovery ownership with bounded reconciliation, latest-only audio handoff intent, and one symmetric six-method legacy IPC target; top mapping and real adapter/radio/discovery lifecycle pass on the validation system, while real device/audio, bottom, and physical multi-output acceptance remain |
## Shared Runtime Modules
@@ -174,11 +174,12 @@ single-output lifecycle, real-player, degraded, Top/Bottom, visual, and
resource slices; this does not waive physical multi-output acceptance.
Network uses the same state/action delegation principle as audio, but the owner
-must be root-scoped: Quattro's component includes a permanent three-second bar
+must be process-wide: Quattro's component includes a permanent three-second bar
status process, so instantiating it inside every output would duplicate work.
-One `NetworkService` hosts that component, and all G11 widgets consume it.
-Screen-local popups delegate visible-network and DNS mutations to the official
-backend. The root service runs `omarchy-network-speedtest` itself for bounded
+The `hancore.shibumi.network` service hosts that component, and all G11 widgets
+consume it. Screen-local popups delegate visible-network and DNS mutations to
+the official backend. The process-wide service runs
+`omarchy-network-speedtest` itself for bounded
download/upload phases and publishes progress only to Shibumi's inline panel;
it never loads Omarchy's standalone speed-test panel. A lifecycle-only `nmcli`
query/action path adds saved profiles that are not currently visible because
diff --git a/docs/plugin-suite-inventory.md b/docs/plugin-suite-inventory.md
index 293d053..b0829e1 100644
--- a/docs/plugin-suite-inventory.md
+++ b/docs/plugin-suite-inventory.md
@@ -374,26 +374,25 @@ These are small host-neutral helpers used by several feature plugins. Reviewed
copies are vendored into each consuming plugin and checked byte-for-byte by the
sync test. They are not runtime plugins and are never imported from `shared/`.
-### Transitional files
+### Resolved transitional files
-```text
-manifest.json combined root manifest; delete after
- independent manifests exist
-widgets/WidgetRegistry.qml internal component switch; replace
- with Omarchy's BarWidgetRegistry
-```
+The combined root manifest and `widgets/WidgetRegistry.qml` were retired after
+the independent manifests and Quattro registry integration became authoritative.
+Step 0 later removed the remaining unguarded historical root implementations.
The root `README.md`, `ARCHITECTURE.md`, `LICENSE`, `config/`, `docs/`, and
repository scripts remain repository-level development and distribution files.
`styles/README.md` moves into the bar plugin's developer documentation; it is
not installed as a runtime dependency.
-## Test Ownership
+## Historical Test Ownership
-Tests move with their owning plugin while repository-wide contract tests stay at
-the root. Paths in the table are relative to `tests/`.
+This table freezes extraction-era test ownership; its filenames are historical
+and are not the current executable inventory. Current plugin regressions,
+machine-readable evidence contracts, and `tests/contract-regression.sh` are
+authoritative. Paths in the table are relative to `tests/`.
-| Owner | Current tests |
+| Owner | Extraction-era tests |
| --- | --- |
| Repository/installer | `contract-regression.sh`, future manifest/dependency/self-containment/install/rollback tests |
| Bar | `group-interaction-regression.qml`, `group-registry-regression.qml`, `group-renderer-regression.qml`, `host-widget-resolver-regression.qml`, `layout-controller-regression.qml`, `layout-model-regression.qml`, `panel-routing-regression.qml`, `qsrise-presentation-smoke.qml`, `reactor-renderer-regression.qml`, `run-geometry-regression.qml`, `style-contract-regression.sh`, `fixtures/ResolverTestWidget.qml` |
diff --git a/docs/v1-presentation-contract.md b/docs/v1-presentation-contract.md
index e18c78a..1e7b544 100644
--- a/docs/v1-presentation-contract.md
+++ b/docs/v1-presentation-contract.md
@@ -150,7 +150,9 @@ bounded retry and cleanup, Top/Bottom rendering, and the focused
single-output resource slice. Multiple-real-player source switching, remaining
media-panel states, and physical multi-output remain acceptance gates.
-`tests/shibumi-presentation-smoke.qml` proves at component-load time that:
+`tests/group-renderer-regression.qml` and
+`tests/style-contract-regression.sh` prove at component-load and contract time
+that:
- the 35/38/32/24 px geometry contract is active;
- CPU and memory follow the Shibumi bar height;
diff --git a/docs/v1-widget-parity-audit.md b/docs/v1-widget-parity-audit.md
index 2be3572..6101bb2 100644
--- a/docs/v1-widget-parity-audit.md
+++ b/docs/v1-widget-parity-audit.md
@@ -34,9 +34,9 @@ Status values:
| G8 | Weather, clock, date, idle, DND, recording, Voxtype, Omarchy update | Local V1 weather, clock/date/calendar, active-only status, and update facades over process-wide services and official owners | Adapted, incomplete | Weather has one process-wide source, honors Quattro's saved location, and lazy-loads the V1 panel without the stock Weather poller. the validation system confirms one shared live Voxtype stream plus a real recording/transcription/idle cycle with text insertion. A real no-audio Quattro recording was detected, timed, stopped by physically clicking the visible G8 indicator, finalized as a valid MP4, and cleaned up while G8 expanded and contracted correctly. Complete mixed-scale and physical multi-output acceptance. Idle, notification, and update owners remain authoritative. |
| G9 | MPRIS controls, marquee, pulse, and player panel, including selectable FULL/muse | Local default row, lazy panel, and current V1 24-band vinyl/waveform/transport row over the official keep-loaded media service and one lazy process-wide spectrum owner | Ported | Real `mpv`, two-player source switching, the actual empty/populated Top/Bottom MediaPanel, available/unavailable/crash states, two-client/one-worker lifecycle, 20 pause/resume cycles, resource characterization, and Top/Bottom bar visuals pass on the validation system. Complete degraded/failure presentation and physical multi-output acceptance. |
| G10 | Idle inhibitor, screenshot/video browser, theme/wallpaper picker | One V1 quick-access pill, a true surface-bound idle inhibitor, and one process-wide picker/media controller with V1-faithful Tanzaku and Hearthstone views; use Quattro's native image picker for its carousel | Ported | Basic open/close, all four modes, style persistence, focused-output routing, worker cleanup, 20-cycle closed-PSS retention, real theme/wallpaper success, controlled theme/wallpaper failure feedback, and screenshot/video copy/open/two-step trash are accepted on the validation system. Tanzaku and the revised Hearthstone wallpaper view are visually accepted; keyboard navigation, warm close cleanup, native carousel coexistence, V1 idle-inhibitor presentation, and a real 4K cold-wallpaper/warm-reopen gate pass. Complete remaining Hearthstone theme/media rendering and physical multi-output acceptance. |
-| G11 | Network state, Wi-Fi/ethernet presentation, throughput, network panel | One root-owned official NetworkManager backend plus lazy Shibumi panels, a Shibumi-owned inline speed-test process, and a lifecycle-only saved-profile adapter | Ported, acceptance incomplete | Top and bottom panel placement, expanded metadata, saved-profile discovery, alias routing, and backend refresh pass on the validation system. The direct inline speed-test path has fixture-backed phase, failure, and crash-cleanup coverage; repeat real output acceptance for this new owner before release. A stale saved-only profile was forgotten through the visible two-step action and stayed absent after reopen/rescan. A real `DHCP -> Cloudflare -> DHCP` DNS round trip passed through the panel and Polkit with the original connection and resolver state restored. Validate a new protected-network connection/passphrase and physical multi-output before release. |
+| G11 | Network state, Wi-Fi/ethernet presentation, throughput, network panel | One process-wide `hancore.shibumi.network` service around the official NetworkManager backend plus lazy Shibumi panels, a Shibumi-owned inline speed-test process, and a lifecycle-only saved-profile adapter | Ported, acceptance incomplete | Top and bottom panel placement, expanded metadata, saved-profile discovery, alias routing, and backend refresh pass on the validation system. The direct inline speed-test path has fixture-backed phase, failure, and crash-cleanup coverage; repeat real output acceptance for this new owner before release. A stale saved-only profile was forgotten through the visible two-step action and stayed absent after reopen/rescan. A real `DHCP -> Cloudflare -> DHCP` DNS round trip passed through the panel and Polkit with the original connection and resolver state restored. Validate a new protected-network connection/passphrase and physical multi-output before release. |
| G12 | Battery widget and battery detail panel | Local view and panel over the shared UPower service | Ported, state accepted | the validation system reports the physical BAT0 as present and healthy. A real discharging-to-charging transition produced matching kernel, UPower, helper, widget, and panel state. Complete physical multi-output acceptance. |
-| G13 | Brightness widget and panel | One official root backend plus V1-style per-output widget and lazy local Shibumi display panel | Ported, acceptance incomplete | Top and Bottom mapping, reversible laptop brightness mutation, and a real `1.0 -> 1.25 -> 1.0` scale round trip pass on the validation system. Validate no-backlight hardware, physical display enable/disable, and physical multi-output ownership. |
+| G13 | Brightness widget and panel | One process-wide `hancore.shibumi.brightness` service around the official backend plus a V1-style per-output widget and lazy local Shibumi display panel | Ported, acceptance incomplete | Top and Bottom mapping, reversible laptop brightness mutation, and a real `1.0 -> 1.25 -> 1.0` scale round trip pass on the validation system. Validate no-backlight hardware, physical display enable/disable, and physical multi-output ownership. |
| G14 | Power-profile widget and panel | Local view and panel over the shared power-profile service | Ported, action accepted | the validation system completed and restored a real performance-to-balanced-to-performance change from `app-graphical.slice`; the product panel now passes Top/Bottom routing and loader diagnostics. Complete physical multi-output acceptance. |
| G15 | Bluetooth widget and panel | One shared Shibumi-native BlueZ/PipeWire adapter with local V1-style widgets and lazy Shibumi device panels | Ported, acceptance incomplete | Top/Bottom mapping, real adapter/radio reactivity, confirmed discovery ownership/reconciliation, multi-panel lease accounting, and the symmetric six-method legacy IPC pass on the validation system. Validate pair/connect/disconnect/forget with real devices, Bluetooth audio, and physical multi-output. |
diff --git a/hancore.shibumi.cpu/GpuTelemetry.qml b/hancore.shibumi.cpu/GpuTelemetry.qml
index b8b5d50..948b799 100644
--- a/hancore.shibumi.cpu/GpuTelemetry.qml
+++ b/hancore.shibumi.cpu/GpuTelemetry.qml
@@ -7,7 +7,7 @@ Scope {
property int consumers: 0
property string helperPath: String(Qt.resolvedUrl(
- "../scripts/shibumi-gpu-probe")).replace("file://", "")
+ "scripts/shibumi-gpu-probe")).replace("file://", "")
property var devices: []
property string backend: ""
property string name: ""
diff --git a/scripts/check-production-boundary b/scripts/check-production-boundary
new file mode 100755
index 0000000..539a767
--- /dev/null
+++ b/scripts/check-production-boundary
@@ -0,0 +1,320 @@
+#!/usr/bin/env python3
+
+from __future__ import annotations
+
+import argparse
+import os
+import posixpath
+import re
+import sys
+from collections import Counter
+from pathlib import Path
+
+
+CODE_SUFFIXES = {"", ".bash", ".js", ".mjs", ".py", ".qml", ".sh"}
+
+# Exact, reviewed compatibility debt. A capability cutover removes entries; it
+# must not broaden them. This is a repository policy gate, not a hostile-code
+# sandbox: deliberate lexical obfuscation is outside its threat model.
+TOKEN_ALLOWANCES = {
+ "hancore.shibumi.ai/Service.qml": Counter({
+ "entryPointUrl": 2,
+ "registeredWidgetSource": 2,
+ "standardWidgetSource": 3,
+ }),
+ "hancore.shibumi.audio/BarWidget.qml": Counter({
+ "entryPointUrl": 2,
+ "registeredComponent": 2,
+ "registeredSource": 2,
+ "registeredWidgetComponent": 2,
+ "registeredWidgetSource": 2,
+ }),
+ "hancore.shibumi.bar/Bar.qml": Counter({
+ "entryPointUrl": 3,
+ "firstPartyServiceFor": 2,
+ "registeredWidgetComponent": 1,
+ "registeredWidgetSource": 1,
+ }),
+ "hancore.shibumi.bar/core/WidgetSlot.qml": Counter({
+ "registeredWidgetComponent": 4,
+ }),
+ "hancore.shibumi.bar/services/HostWidgetResolver.qml": Counter({
+ "Qt.createComponent": 1,
+ "entryPointUrl": 5,
+ }),
+ "hancore.shibumi.brightness/Service.qml": Counter({
+ "entryPointUrl": 2,
+ "registeredComponent": 2,
+ "registeredSource": 2,
+ "registeredWidgetComponent": 2,
+ "registeredWidgetSource": 2,
+ }),
+ "hancore.shibumi.center/BarWidget.qml": Counter({
+ "entryPointUrl": 2,
+ "registeredComponent": 2,
+ "registeredSource": 2,
+ "registeredWidgetComponent": 2,
+ "registeredWidgetSource": 2,
+ }),
+ "hancore.shibumi.center/SystemUpdateWidget.qml": Counter({
+ "firstPartyServiceFor": 2,
+ }),
+ "hancore.shibumi.media/BarWidget.qml": Counter({
+ "firstPartyServiceFor": 2,
+ }),
+ "hancore.shibumi.media/Service.qml": Counter({
+ "firstPartyServiceFor": 2,
+ }),
+ "hancore.shibumi.network/Service.qml": Counter({
+ "entryPointUrl": 2,
+ "registeredComponent": 2,
+ "registeredSource": 2,
+ "registeredWidgetComponent": 2,
+ "registeredWidgetSource": 2,
+ }),
+ "hancore.shibumi.reactor/ReactorService.qml": Counter({
+ "firstPartyService": 2,
+ "firstPartyServiceFor": 2,
+ }),
+ "hancore.shibumi.status/BarWidget.qml": Counter({
+ "entryPointUrl": 2,
+ "firstPartyServiceFor": 5,
+ "registeredComponent": 3,
+ "registeredSource": 3,
+ "registeredWidgetComponent": 2,
+ "registeredWidgetSource": 2,
+ }),
+ "hancore.shibumi.status/Service.qml": Counter({
+ "firstPartyServiceFor": 4,
+ }),
+}
+
+PROVIDER_ALLOWANCES = {
+ "hancore.shibumi.ai/Service.qml": {
+ "omarchy.agents", "omarchy.model-usage",
+ },
+ "hancore.shibumi.audio/BarWidget.qml": {"omarchy.audio"},
+ "hancore.shibumi.brightness/Service.qml": {"omarchy.monitor"},
+ "hancore.shibumi.center/BarWidget.qml": {"omarchy.system-update"},
+ "hancore.shibumi.network/Service.qml": {"omarchy.network"},
+ "hancore.shibumi.status/BarWidget.qml": {"omarchy.tray"},
+}
+
+FIRST_PARTY_ALLOWANCES = {
+ "hancore.shibumi.bar/Bar.qml": {"omarchy.idle"},
+ "hancore.shibumi.center/SystemUpdateWidget.qml": {
+ "omarchy.system-update",
+ },
+ "hancore.shibumi.media/BarWidget.qml": {"omarchy.media"},
+ "hancore.shibumi.media/Service.qml": {"omarchy.media"},
+ "hancore.shibumi.reactor/ReactorService.qml": {"omarchy.media"},
+ "hancore.shibumi.status/BarWidget.qml": {"omarchy.notifications"},
+ "hancore.shibumi.status/Service.qml": {
+ "omarchy.idle", "omarchy.notifications",
+ },
+}
+
+HELPER_ALLOWANCES = {
+ "hancore.shibumi.network/Service.qml": Counter({
+ "omarchy-network-status": 1,
+ }),
+ "hancore.shibumi.power-state/Service.qml": Counter({
+ "omarchy-battery-status": 1,
+ "omarchy-powerprofiles-list": 1,
+ }),
+ "hancore.shibumi.status/Service.qml": Counter({
+ "/tmp/omarchy-screenrecord-filename": 1,
+ "omarchy-voxtype-status": 1,
+ "pgrep": 1,
+ }),
+ "hancore.shibumi.quick-access/scripts/shibumi-picker": Counter({
+ "pgrep": 1,
+ }),
+}
+
+PROTECTED_TOKENS = {
+ token for allowance in TOKEN_ALLOWANCES.values() for token in allowance
+}
+PRIVATE_PATHS = (
+ re.compile(r"/usr/share/omarchy/(?:plugins|shell)(?:/|$)"),
+ re.compile(r"(?:^|[/\"'\s])shell/plugins/", re.MULTILINE),
+ re.compile(r"\.config/omarchy/plugins/omarchy\."),
+)
+PROVIDER_CALL = re.compile(
+ r"\b(?:registeredSource|registeredComponent|registeredWidgetSource|"
+ r"registeredWidgetComponent|standardWidgetSource)\s*\(\s*"
+ r"[\"'](omarchy\.[A-Za-z0-9_.-]+)[\"']"
+)
+FIRST_PARTY_CALL = re.compile(
+ r"\b(?:firstPartyServiceFor|firstPartyService)\s*\(\s*"
+ r"[\"'](omarchy\.[A-Za-z0-9_.-]+)[\"']"
+)
+HELPER_MARKER = re.compile(
+ r"/tmp/omarchy-[A-Za-z0-9_.-]+|"
+ r"\bomarchy-[A-Za-z0-9-]*(?:status|state|list)\b|"
+ r"\b(?:pgrep|pidof)\b"
+)
+RELATIVE_LITERAL = re.compile(r"[\"']([^\"']*\.\.[^\"']*)[\"']")
+
+
+def add(findings: list[str], path: str, kind: str, detail: str) -> None:
+ findings.append(f"{path}: {kind}: {detail}")
+
+
+def production_files(root: Path) -> list[tuple[Path, Path]]:
+ files: list[tuple[Path, Path]] = []
+ for plugin in sorted(root.glob("hancore.shibumi.*")):
+ if plugin.is_symlink():
+ files.append((plugin, plugin))
+ continue
+ if not plugin.is_dir():
+ continue
+ for path in sorted(plugin.rglob("*")):
+ if path.is_symlink() or (
+ path.is_file()
+ and (path.suffix.lower() in CODE_SUFFIXES or os.access(path, os.X_OK))
+ ):
+ files.append((path, plugin))
+ return files
+
+
+def compare_exact(
+ findings: list[str], relative: str, kind: str,
+ observed: Counter[str], allowed: Counter[str],
+) -> None:
+ for token, count in sorted((observed - allowed).items()):
+ add(findings, relative, kind, f"{token} occurs {count} unapproved time(s)")
+ for token, count in sorted((allowed - observed).items()):
+ add(
+ findings,
+ relative,
+ kind,
+ f"approved {token} occurrence disappeared {count} time(s); "
+ "remove or update its narrow allowance",
+ )
+
+
+def check_tree(root: Path) -> list[str]:
+ root = root.resolve()
+ findings: list[str] = []
+ visited: set[str] = set()
+ present_plugins = {
+ path.name for path in root.glob("hancore.shibumi.*") if path.is_dir()
+ }
+
+ for path, plugin in production_files(root):
+ relative = path.relative_to(root).as_posix()
+ visited.add(relative)
+ resolved_plugin = plugin.resolve()
+ if path.is_symlink():
+ kind = "package escape" \
+ if not path.resolve().is_relative_to(resolved_plugin) \
+ else "production symlink"
+ add(findings, relative, kind, "production symlinks are not allowed")
+ continue
+ try:
+ text = path.read_text(encoding="utf-8")
+ except UnicodeDecodeError:
+ add(findings, relative, "undecodable production source", "not UTF-8")
+ continue
+
+ for pattern in PRIVATE_PATHS:
+ if pattern.search(text):
+ add(findings, relative, "private component path", pattern.pattern)
+
+ observed_tokens: Counter[str] = Counter()
+ for token in PROTECTED_TOKENS:
+ count = text.count(token) if "." in token else len(re.findall(
+ rf"(? int:
+ parser = argparse.ArgumentParser(
+ description="Reject undeclared private backends in shipped Shibumi plugins."
+ )
+ parser.add_argument(
+ "--root",
+ type=Path,
+ default=Path(__file__).resolve().parents[1],
+ help="tree containing hancore.shibumi.* plugin directories",
+ )
+ args = parser.parse_args()
+ if not args.root.is_dir():
+ parser.error(f"production root is not a directory: {args.root}")
+ findings = check_tree(args.root)
+ if findings:
+ for finding in findings:
+ print(f"production boundary error: {finding}", file=sys.stderr)
+ return 1
+ print("Shibumi production boundary check passed")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/opencode-usage b/scripts/opencode-usage
deleted file mode 100755
index 8212787..0000000
--- a/scripts/opencode-usage
+++ /dev/null
@@ -1,292 +0,0 @@
-#!/usr/bin/env python3
-"""
-OpenCode local usage for the Shibumi Omarchy plugin.
-
-OpenCode does not expose a Claude/Codex-style 5h/7d quota locally. This script
-reads OpenCode's local SQLite messages and prints the V1-compatible JSON shape
-consumed by the plugin, using configurable soft caps for the visual usage bars.
-It never reads API tokens, writes cache files, or makes network calls.
-"""
-import json
-import os
-import sqlite3
-import time
-from datetime import datetime, timedelta
-from pathlib import Path
-
-DATA_DIR = Path(os.environ.get("OPENCODE_DATA_DIR") or (Path.home() / ".local" / "share" / "opencode"))
-DB_FILE = DATA_DIR / "opencode.db"
-
-DEFAULT_5H_LIMIT = 1_000_000
-DEFAULT_7D_LIMIT = 20_000_000
-
-
-def env_int(name: str, default: int) -> int:
- try:
- value = int(os.environ.get(name, ""))
- return value if value > 0 else default
- except Exception:
- return default
-
-
-LIMIT_5H = env_int("OPENCODE_USAGE_5H_LIMIT", DEFAULT_5H_LIMIT)
-LIMIT_7D = env_int("OPENCODE_USAGE_7D_LIMIT", DEFAULT_7D_LIMIT)
-
-
-def unavailable() -> None:
- print(json.dumps({"ready": False, "status": "unavailable"}))
-
-
-def connect() -> sqlite3.Connection:
- con = sqlite3.connect(f"file:{DB_FILE}?mode=ro", uri=True, timeout=2.0)
- con.execute("pragma busy_timeout=2000")
- return con
-
-
-def as_int(value) -> int:
- try:
- return int(value or 0)
- except Exception:
- return 0
-
-
-def as_float(value) -> float:
- try:
- return float(value or 0.0)
- except Exception:
- return 0.0
-
-
-def model_label(model_id: str, provider_id: str = "") -> str:
- model_id = str(model_id or "")
- provider_id = str(provider_id or "")
- return model_id + (f" ({provider_id})" if provider_id else "")
-
-
-def assistant_messages(
- con: sqlite3.Connection, since_ms: int, before_ms: int
-) -> list[dict]:
- rows = con.execute(
- """
- select session_id, time_created, time_updated, data
- from message
- where time_updated >= ? and time_updated < ?
- order by time_updated asc
- """,
- (since_ms, before_ms),
- ).fetchall()
- out = []
- for session_id, time_created, time_updated, raw in rows:
- try:
- data = json.loads(raw or "{}")
- except Exception:
- continue
- if data.get("role") != "assistant":
- continue
- model_id = data.get("modelID") or ""
- if not model_id:
- continue
- tokens = data.get("tokens") or {}
- cache = tokens.get("cache") or {}
- inp = as_int(tokens.get("input"))
- output = as_int(tokens.get("output"))
- reasoning = as_int(tokens.get("reasoning"))
- cache_read = as_int(cache.get("read"))
- cache_write = as_int(cache.get("write"))
- # cache_read is server-side context replay. Keep it visible in the model
- # breakdown, but exclude it from usage caps so the 5h bar can move.
- total = inp + output + reasoning + cache_write
- out.append({
- "session_id": session_id,
- "model_id": model_id,
- "provider_id": data.get("providerID") or "",
- "input": inp,
- "output": output,
- "reasoning": reasoning,
- "cache_read": cache_read,
- "cache_write": cache_write,
- "total": total,
- "total_with_cache_read": total + cache_read,
- "cost": as_float(data.get("cost")),
- "time": as_int(time_updated or time_created),
- })
- return out
-
-
-def sum_since(messages: list[dict], since_ms: int) -> tuple[int, float, int]:
- usage = 0
- cost = 0.0
- with_cache_read = 0
- for msg in messages:
- if msg["time"] < since_ms:
- continue
- usage += msg["total"]
- with_cache_read += msg["total_with_cache_read"]
- cost += msg["cost"]
- return usage, cost, with_cache_read
-
-
-def newest_model(messages: list[dict], today_ms: int, tomorrow_ms: int) -> str:
- msgs = [
- msg for msg in messages
- if today_ms <= msg["time"] < tomorrow_ms and msg["total"] > 0
- ]
- if not msgs:
- return ""
- msg = max(msgs, key=lambda item: item["time"])
- return model_label(msg["model_id"], msg["provider_id"])
-
-
-def fmt_tokens(value: int) -> str:
- if value >= 1_000_000:
- return f"{value / 1_000_000:.1f}M"
- if value >= 1_000:
- return f"{value / 1_000:.1f}K"
- return str(value)
-
-
-def model_rows(
- messages: list[dict], today_ms: int, tomorrow_ms: int, limit: int = 6
-) -> list[dict]:
- by_model = {}
- for msg in messages:
- if not today_ms <= msg["time"] < tomorrow_ms or msg["total"] <= 0:
- continue
- key = (msg["provider_id"], msg["model_id"])
- cur = by_model.setdefault(key, {
- "name": model_label(msg["model_id"], msg["provider_id"]),
- "session_ids": set(),
- "input": 0,
- "output": 0,
- "reasoning": 0,
- "cache_read": 0,
- "cache_write": 0,
- "cost": 0.0,
- "last_seen": 0,
- "today": 0,
- "total": 0,
- "total_with_cache_read": 0,
- })
- cur["session_ids"].add(msg["session_id"])
- cur["input"] += msg["input"]
- cur["output"] += msg["output"]
- cur["reasoning"] += msg["reasoning"]
- cur["cache_read"] += msg["cache_read"]
- cur["cache_write"] += msg["cache_write"]
- cur["cost"] += msg["cost"]
- cur["last_seen"] = max(cur["last_seen"], msg["time"])
- cur["total"] += msg["total"]
- cur["total_with_cache_read"] += msg["total_with_cache_read"]
- cur["today"] += msg["total"]
-
- rows = sorted(by_model.values(), key=lambda item: item["last_seen"], reverse=True)[:limit]
- totals = []
- for row in rows:
- total = int(row["total"])
- cost = float(row["cost"])
- totals.append({
- "name": row["name"],
- "sessions": len(row["session_ids"]),
- "total": total,
- "totalLabel": fmt_tokens(total),
- "inputLabel": fmt_tokens(int(row["input"])),
- "outputLabel": fmt_tokens(int(row["output"])),
- "reasoningLabel": fmt_tokens(int(row["reasoning"])),
- "cacheReadLabel": fmt_tokens(int(row["cache_read"])),
- "cacheWriteLabel": fmt_tokens(int(row["cache_write"])),
- # Every model component in this row already has today's scope.
- # Keep the legacy field inert rather than repeating the total.
- "todayLabel": "0",
- "costLabel": f"${cost:.2f}" if cost > 0 else "",
- "lastSeen": int(row["last_seen"]),
- "totalWithCacheRead": int(row["total_with_cache_read"]),
- "totalWithCacheReadLabel": fmt_tokens(int(row["total_with_cache_read"])),
- })
- max_total = max((row["total"] for row in totals), default=0)
- for row in totals:
- row["pct"] = int(round(row["total"] * 100 / max_total)) if max_total else 0
- return totals
-
-
-def pct(tokens: int, limit: int) -> float:
- if limit <= 0:
- return 0.0
- return min(1.0, max(0.0, tokens / float(limit)))
-
-
-def local_day_bounds(now_value: datetime | None = None) -> tuple[int, int, str]:
- current = now_value or datetime.now()
- start_today = current.replace(hour=0, minute=0, second=0, microsecond=0)
- start_tomorrow = start_today + timedelta(days=1)
- return (
- int(start_today.timestamp() * 1000),
- int(start_tomorrow.timestamp() * 1000),
- start_today.date().isoformat(),
- )
-
-
-def main() -> None:
- if not DB_FILE.exists():
- unavailable()
- return
-
- now = time.time()
- five_h_ms = int((now - 5 * 3600) * 1000)
- seven_d_ms = int((now - 7 * 24 * 3600) * 1000)
- hour_ms = int((now - 3600) * 1000)
- today_ms, tomorrow_ms, local_day = local_day_bounds()
- current_ms_exclusive = int(now * 1000) + 1
-
- try:
- con = connect()
- with con:
- # Explicitly pin every aggregate to one SQLite read snapshot. A
- # concurrent OpenCode write cannot split latestModel, model rows,
- # and token totals across different database states.
- con.execute("begin")
- messages = assistant_messages(
- con, min(seven_d_ms, today_ms),
- min(tomorrow_ms, current_ms_exclusive),
- )
- tok_5h, cost_5h, tok_5h_with_cache = sum_since(messages, five_h_ms)
- tok_7d, cost_7d, tok_7d_with_cache = sum_since(messages, seven_d_ms)
- tok_1h, _, tok_1h_with_cache = sum_since(messages, hour_ms)
- tok_today, cost_today, tok_today_with_cache = sum_since(messages, today_ms)
- model = newest_model(messages, today_ms, tomorrow_ms)
- models = model_rows(messages, today_ms, tomorrow_ms)
- except sqlite3.Error:
- unavailable()
- return
-
- out = {
- "5h-utilization": f"{pct(tok_5h, LIMIT_5H):.4f}",
- "5h-reset": "0",
- "7d-utilization": f"{pct(tok_7d, LIMIT_7D):.4f}",
- "7d-reset": "0",
- "updated_at": datetime.now().strftime("%H:%M:%S"),
- "_day": local_day,
- "_source": "sqlite",
- "_plan": "local messages",
- "_model": model,
- "_models": models,
- "_tokens_used": tok_5h,
- "_tokens_used_with_cache_read": tok_5h_with_cache,
- "_window_limit": LIMIT_5H,
- "_rate_per_hour": tok_1h,
- "_rate_per_hour_with_cache_read": tok_1h_with_cache,
- "_today_tokens": tok_today,
- "_today_tokens_with_cache_read": tok_today_with_cache,
- "_7d_tokens": tok_7d,
- "_7d_tokens_with_cache_read": tok_7d_with_cache,
- "_7d_limit": LIMIT_7D,
- "_cost_5h": cost_5h,
- "_cost_7d": cost_7d,
- "_cost_today": cost_today,
- "status": "allowed_warning" if tok_5h >= LIMIT_5H * 0.9 else "allowed",
- }
- out["ready"] = True
- print(json.dumps(out))
-
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/shibumi-gpu-probe b/scripts/shibumi-gpu-probe
deleted file mode 100755
index c093ddd..0000000
--- a/scripts/shibumi-gpu-probe
+++ /dev/null
@@ -1,170 +0,0 @@
-#!/usr/bin/env bash
-
-set -uo pipefail
-shopt -s extglob nullglob
-
-sys_root=${SHIBUMI_GPU_SYS_ROOT:-/sys}
-found=0
-legacy_emitted=0
-declare -A seen_devices=()
-
-clean_field() {
- local value=${1:-}
- value=${value//$'\n'/ }
- value=${value//$'\r'/ }
- value=${value//|/ }
- value=${value##+([[:space:]])}
- value=${value%%+([[:space:]])}
- printf '%s' "$value"
-}
-
-integer_field() {
- local value=${1:-}
- if [[ $value =~ ^[[:space:]]*([0-9]+)[[:space:]]*$ ]]; then
- printf '%s' "${BASH_REMATCH[1]}"
- return 0
- fi
- return 1
-}
-
-normalize_pci_slot() {
- local value
- value=$(clean_field "${1:-}")
- value=${value,,}
- if [[ $value =~ ^[[:xdigit:]]{8}: ]]; then
- value=${value:4}
- fi
- [[ $value =~ ^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]$ ]] \
- || return 1
- printf '%s' "$value"
-}
-
-emit_device() {
- local id backend util temp used total name driver version node
- id=$(clean_field "${1:-}")
- backend=$(clean_field "${2:-}")
- util=$(integer_field "${3:-}") || util=0
- temp=$(integer_field "${4:-}") || temp=0
- used=$(integer_field "${5:-}") || used=0
- total=$(integer_field "${6:-}") || total=0
- name=$(clean_field "${7:-}")
- driver=$(clean_field "${8:-}")
- version=$(clean_field "${9:-}")
- node=$(clean_field "${10:-}")
- [[ -n $id && -n $backend ]] || return 1
- (( util > 100 )) && util=100
- [[ -n $name ]] || name="$driver GPU"
- # Keep the first two records compatible with a pre-upgrade telemetry parser
- # while a transactional update replaces the helper before loaded QML.
- if (( legacy_emitted == 0 )); then
- printf '%s|%s|%s|%s|%s\n' "$backend" "$util" "$temp" "$used" "$total"
- printf 'meta|%s|%s|%s\n' "$name" "$driver" "$version"
- legacy_emitted=1
- fi
- printf 'device|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s\n' \
- "$id" "$backend" "$util" "$temp" "$used" "$total" \
- "$name" "$driver" "$version" "$node"
- seen_devices["$id"]=1
- found=$((found + 1))
-}
-
-probe_nvidia() {
- [[ ${SHIBUMI_GPU_DISABLE_NVIDIA:-0} != 1 ]] || return 0
-
- local nvidia_bin output summary slot name version util temp used total id
- nvidia_bin=$(command -v nvidia-smi 2>/dev/null) || return 0
- output=$("$nvidia_bin" \
- --query-gpu=pci.bus_id,name,driver_version,utilization.gpu,temperature.gpu,memory.used,memory.total \
- --format=csv,noheader,nounits 2>/dev/null) || return 0
-
- while IFS= read -r summary; do
- [[ -n $summary ]] || continue
- IFS=, read -r slot name version util temp used total <<<"$summary"
- slot=$(normalize_pci_slot "$slot" 2>/dev/null || true)
- # A runtime index is not stable enough for persisted selection. Let the
- # DRM pass recover this device by its PCI slot when NVIDIA omits one.
- [[ -n $slot ]] || continue
- id="pci:$slot"
- [[ -z ${seen_devices[$id]+x} ]] || continue
- util=$(integer_field "$util") || continue
- temp=$(integer_field "$temp") || continue
- used=$(integer_field "$used") || continue
- total=$(integer_field "$total") || continue
- emit_device "$id" nvidia "$util" "$temp" "$used" "$total" \
- "$name" nvidia "$version" ""
- done <<<"$output"
-}
-
-probe_drm() {
- local device card_node resolved slot id driver_path driver util temp raw sensor
- local total_bytes used_bytes total_mib used_mib model model_line lspci_bin
- local version
-
- for device in "$sys_root"/class/drm/card+([0-9])/device; do
- [[ -d $device ]] || continue
- card_node=${device%/device}
- card_node=${card_node##*/}
- resolved=$(readlink -f "$device" 2>/dev/null) || continue
- slot=${resolved##*/}
- slot=$(normalize_pci_slot "$slot" 2>/dev/null || true)
- [[ -n $slot ]] || continue
- id="pci:$slot"
- [[ -z ${seen_devices[$id]+x} ]] || continue
- driver_path=$(readlink -f "$device/driver" 2>/dev/null) || continue
- driver=${driver_path##*/}
-
- util=0
- if [[ -r $device/gpu_busy_percent ]]; then
- IFS= read -r raw <"$device/gpu_busy_percent"
- util=$(integer_field "$raw") || util=0
- fi
- (( util > 100 )) && util=100
-
- temp=0
- for sensor in "$device"/hwmon/hwmon*/temp1_input; do
- [[ -r $sensor ]] || continue
- IFS= read -r raw <"$sensor"
- raw=$(integer_field "$raw") || continue
- temp=$((raw / 1000))
- break
- done
-
- used_mib=0
- total_mib=0
- if [[ -r $device/mem_info_vram_used ]]; then
- IFS= read -r used_bytes <"$device/mem_info_vram_used"
- used_bytes=$(integer_field "$used_bytes") || used_bytes=0
- used_mib=$((used_bytes / 1048576))
- fi
- if [[ -r $device/mem_info_vram_total ]]; then
- IFS= read -r total_bytes <"$device/mem_info_vram_total"
- total_bytes=$(integer_field "$total_bytes") || total_bytes=0
- total_mib=$((total_bytes / 1048576))
- fi
-
- model=""
- lspci_bin=$(command -v lspci 2>/dev/null || true)
- if [[ -n $lspci_bin ]]; then
- IFS= read -r model_line < <("$lspci_bin" -D -s "$slot" 2>/dev/null)
- model_line=${model_line#* }
- [[ $model_line == *": "* ]] && model=${model_line#*: }
- fi
- model=$(clean_field "$model")
- [[ -n $model ]] || model="$driver GPU"
-
- version=""
- [[ -r $sys_root/module/$driver/version ]] \
- && IFS= read -r version <"$sys_root/module/$driver/version"
- version=$(clean_field "$version")
-
- emit_device "$id" sysfs "$util" "$temp" "$used_mib" "$total_mib" \
- "$model" "$driver" "$version" "$card_node"
- done
-}
-
-probe_nvidia
-probe_drm
-if (( found == 0 )); then
- printf 'none|0|0|0|0\n'
-fi
-printf 'status|ok\n'
diff --git a/scripts/sync-shared.sh b/scripts/sync-shared.sh
index e0ebce3..03a5cfd 100755
--- a/scripts/sync-shared.sh
+++ b/scripts/sync-shared.sh
@@ -24,7 +24,6 @@ mappings=(
"shared/telemetry/SystemTelemetry.qml:hancore.shibumi.telemetry/SystemTelemetry.qml"
"shared/telemetry/ThermalTelemetry.qml:services/ThermalTelemetry.qml"
"shared/telemetry/ThermalTelemetry.qml:hancore.shibumi.telemetry/ThermalTelemetry.qml"
- "shared/telemetry/GpuTelemetry.qml:services/GpuTelemetry.qml"
"shared/telemetry/GpuTelemetry.qml:hancore.shibumi.cpu/GpuTelemetry.qml"
"shared/power-state/Service.qml:services/PowerService.qml"
"shared/power-state/Service.qml:hancore.shibumi.power-state/Service.qml"
@@ -124,7 +123,6 @@ mappings=(
)
executable_mappings=(
- "shared/telemetry/shibumi-gpu-probe:scripts/shibumi-gpu-probe"
"shared/telemetry/shibumi-gpu-probe:hancore.shibumi.cpu/scripts/shibumi-gpu-probe"
"shared/quick-access/shibumi-picker:scripts/shibumi-picker"
"shared/quick-access/shibumi-picker:hancore.shibumi.quick-access/scripts/shibumi-picker"
diff --git a/services/AiUsageService.qml b/services/AiUsageService.qml
deleted file mode 100644
index b8634ee..0000000
--- a/services/AiUsageService.qml
+++ /dev/null
@@ -1,270 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell.Io
-
-// One process-wide provider owner. Official Claude/Codex scanners are loaded
-// only when their local data exists; OpenCode uses the plugin-local read-only
-// SQLite adapter. Per-output widgets render this shared state only.
-Item {
- id: root
-
- required property var bar
-
- property bool detectionReady: false
- property bool claudeAvailable: false
- property bool codexAvailable: false
- property bool openCodeAvailable: false
- property int providerRevision: 0
-
- readonly property string modelUsageSource: bar
- && typeof bar.registeredWidgetSource === "function"
- ? String(bar.registeredWidgetSource("omarchy.model-usage") || "") : ""
- readonly property url claudeSource: providerUrl("Claude.qml")
- readonly property url codexSource: providerUrl("Codex.qml")
- readonly property var settings: bar
- && typeof bar.widgetSettings === "function"
- ? bar.widgetSettings("G7", "hancore.shibumi.ai") : ({})
- readonly property string requestedTool: String(
- settings.aiTool || settings.tool || "claude").toLowerCase()
- readonly property var providers: {
- void(providerRevision)
- const result = []
- if (claudeLoader.item) result.push(claudeLoader.item)
- if (codexLoader.item) result.push(codexLoader.item)
- if (openCodeProvider.enabled) result.push(openCodeProvider)
- return result
- }
- readonly property string selectedTool: resolveTool(requestedTool)
- readonly property var selectedProvider: providerFor(selectedTool)
- readonly property bool refreshing: providers.some(function(provider) {
- return provider && provider.refreshing === true
- })
-
- function providerUrl(fileName) {
- const source = String(modelUsageSource || "")
- const slash = source.lastIndexOf("/")
- if (slash < 0) return ""
- return source.slice(0, slash + 1) + "providers/" + fileName
- }
-
- function providerFor(id) {
- const target = String(id || "")
- for (let index = 0; index < providers.length; index++) {
- if (String(providers[index].providerId || "") === target)
- return providers[index]
- }
- return null
- }
-
- function resolveTool(preferred) {
- const requested = String(preferred || "")
- if (providerFor(requested)) return requested
- const order = ["claude", "codex", "opencode"]
- for (let index = 0; index < order.length; index++) {
- if (providerFor(order[index])) return order[index]
- }
- return ""
- }
-
- function selectTool(id) {
- const target = String(id || "")
- if (!providerFor(target) || !bar
- || typeof bar.setWidgetSetting !== "function") return false
- return bar.setWidgetSetting("G7", "hancore.shibumi.ai", "aiTool", target)
- }
-
- function cycleTool(direction) {
- if (providers.length < 2) return false
- let index = providers.indexOf(selectedProvider)
- if (index < 0) index = 0
- const step = Number(direction) < 0 ? -1 : 1
- const next = providers[(index + step + providers.length) % providers.length]
- return selectTool(next.providerId)
- }
-
- function refreshAll(force) {
- for (let index = 0; index < providers.length; index++) {
- const provider = providers[index]
- if (provider && typeof provider.refresh === "function") provider.refresh(force === true)
- }
- }
-
- function usagePercent(provider) {
- if (!provider) return -1
- const primary = displayPercent(provider, provider.rateLimitPercent)
- const secondary = displayPercent(provider, provider.secondaryRateLimitPercent)
- if (isFinite(primary) && primary >= 0) return Math.round(primary)
- if (isFinite(secondary) && secondary >= 0) return Math.round(secondary)
- return -1
- }
-
- function displayPercent(provider, value) {
- const number = Number(value)
- if (!isFinite(number) || number < 0) return -1
- const providerId = provider ? String(provider.providerId || "") : ""
- // The official Claude and Codex providers expose utilization as a 0..1
- // fraction. OpenCode is normalized to 0..100 by its local adapter.
- if ((providerId === "claude" || providerId === "codex") && number <= 1)
- return Math.min(100, number * 100)
- return Math.min(100, number)
- }
-
- function displayTierLabel(value) {
- const label = String(value || "")
- if (label.toLowerCase() === "prolite") return "Pro Lite"
- return label
- }
-
- function resetText(provider, timestamp) {
- if (!provider || !timestamp || typeof provider.formatResetTime !== "function") return ""
- return String(provider.formatResetTime(timestamp) || "")
- }
-
- function providerReportsFiveHour(provider) {
- if (!provider) return false
- const labels = [provider.rateLimitLabel, provider.secondaryRateLimitLabel]
- for (let index = 0; index < labels.length; index++) {
- const label = String(labels[index] || "").toLowerCase()
- if (label.indexOf("5h") >= 0
- || (label.indexOf("5") >= 0 && label.indexOf("hour") >= 0))
- return true
- }
- return false
- }
-
- function tooltipText() {
- if (providers.length === 0) return "No AI usage providers detected"
- const lines = []
- for (let index = 0; index < providers.length; index++) {
- const provider = providers[index]
- if (lines.length) lines.push("")
- let heading = String(provider.providerName || provider.providerId || "AI")
- if (displayTierLabel(provider.tierLabel))
- heading += " (" + displayTierLabel(provider.tierLabel) + ")"
- lines.push(heading)
- if (Number(provider.rateLimitPercent) >= 0) {
- const reset = resetText(provider, provider.rateLimitResetAt)
- lines.push(String(provider.rateLimitLabel || "Primary") + ": "
- + Math.round(displayPercent(provider, provider.rateLimitPercent)) + "%"
- + (reset ? " · resets in " + reset : ""))
- }
- if (Number(provider.secondaryRateLimitPercent) >= 0) {
- const reset = resetText(provider, provider.secondaryRateLimitResetAt)
- lines.push(String(provider.secondaryRateLimitLabel || "Secondary") + ": "
- + Math.round(displayPercent(provider,
- provider.secondaryRateLimitPercent)) + "%"
- + (reset ? " · resets in " + reset : ""))
- }
- if (String(provider.providerId || "") === "codex"
- && !providerReportsFiveHour(provider))
- lines.push("5h: not reported by Codex RPC")
- if (String(provider.usageStatusText || "")
- && String(provider.providerId || "") === "codex")
- lines.push("General limit: " + String(provider.usageStatusText))
- const providerId = String(provider.providerId || "")
- if (providerId === "opencode") {
- if (Number(provider.windowTokens) > 0)
- lines.push("5h tokens: " + formatTokens(provider.windowTokens))
- if (Number(provider.hourlyTokens) > 0)
- lines.push("1h rate: " + formatTokens(provider.hourlyTokens) + "/h")
- } else if (Number(provider.windowTokens) > 0) {
- let activity = formatTokens(provider.windowTokens) + " tokens"
- if (Number(provider.hourlyTokens) > 0)
- activity += " · " + formatTokens(provider.hourlyTokens) + "/h"
- lines.push(activity)
- } else if (Number(provider.hourlyTokens) > 0) {
- lines.push("Rate: " + formatTokens(provider.hourlyTokens) + "/h")
- }
- if (provider.todayTotalTokens !== undefined
- && Number(provider.todayTotalTokens) > 0)
- lines.push("Today: " + formatTokens(provider.todayTotalTokens) + " tokens")
- if (String(provider.latestModel || ""))
- lines.push((providerId === "opencode" ? "Latest today: " : "")
- + String(provider.latestModel))
- }
- return lines.join("\n")
- }
-
- function formatTokens(value) {
- const count = Math.max(0, Number(value) || 0)
- if (count >= 1000000) return (count / 1000000).toFixed(2) + "M"
- if (count >= 1000) return (count / 1000).toFixed(1) + "K"
- return String(Math.round(count))
- }
-
- function applyDetection(raw) {
- try {
- const result = JSON.parse(String(raw || "{}"))
- claudeAvailable = result.claude === true
- codexAvailable = result.codex === true
- openCodeAvailable = result.opencode === true
- detectionReady = true
- providerRevision++
- } catch (_error) {
- detectionReady = true
- }
- }
-
- Process {
- id: detectionProc
- command: ["bash", "-c", [
- "home=${HOME:?}",
- "claude=false",
- "codex=false",
- "opencode=false",
- "[[ -s \"$home/.claude/.credentials.json\" || -s \"$home/.claude/stats-cache.json\" || -s \"$home/.claude/history.jsonl\" ]] && claude=true",
- "[[ -d \"$home/.codex/sessions\" || -s \"$home/.codex/auth.json\" ]] && codex=true",
- "[[ -s \"$home/.local/share/opencode/opencode.db\" ]] && opencode=true",
- "printf '{\"claude\":%s,\"codex\":%s,\"opencode\":%s}\\n' \"$claude\" \"$codex\" \"$opencode\""
- ].join("; ")]
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: root.applyDetection(text)
- }
- }
-
- Timer {
- interval: 60 * 1000
- running: true
- repeat: true
- triggeredOnStart: true
- onTriggered: if (!detectionProc.running) detectionProc.running = true
- }
-
- Loader {
- id: claudeLoader
- active: root.detectionReady && root.claudeAvailable && String(root.claudeSource)
- source: active ? root.claudeSource : ""
- onLoaded: {
- if (item) {
- item.providerSettings = root.settings.providers
- && root.settings.providers.claude ? root.settings.providers.claude : ({})
- item.enabled = true
- }
- root.providerRevision++
- }
- onItemChanged: root.providerRevision++
- }
-
- Loader {
- id: codexLoader
- active: root.detectionReady && root.codexAvailable && String(root.codexSource)
- source: active ? root.codexSource : ""
- onLoaded: {
- if (item) {
- item.providerSettings = root.settings.providers
- && root.settings.providers.codex ? root.settings.providers.codex : ({})
- item.enabled = true
- }
- root.providerRevision++
- }
- onItemChanged: root.providerRevision++
- }
-
- OpenCodeProvider {
- id: openCodeProvider
- enabled: root.detectionReady && root.openCodeAvailable
- onReadyChanged: root.providerRevision++
- }
-}
diff --git a/services/BluetoothService.qml b/services/BluetoothService.qml
deleted file mode 100644
index 47ab1bd..0000000
--- a/services/BluetoothService.qml
+++ /dev/null
@@ -1,111 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell.Io
-import "../adapters" as Adapters
-
-// One process-wide owner for Quattro's Bluetooth and Bluetooth-audio state.
-// Screen-local widgets and panels consume this facade without constructing
-// additional BlueZ or PipeWire models.
-Item {
- id: root
-
- required property var bar
- property var backendOverride: null
- property var sessionOwners: []
- property int discoveryRetryInterval: 1000
- property int discoveryRequestTimeoutInterval: 1500
-
- readonly property var backend: adapter.backend
- readonly property bool ready: adapter.ready
- readonly property bool adapterAvailable: adapter.adapterAvailable
- readonly property bool radioEnabled: adapter.radioEnabled
- readonly property bool discovering: adapter.discovering
- readonly property var connectedDevices: adapter.connectedDevices
- readonly property var knownDevices: adapter.knownDevices
- readonly property var discoveredDevices: adapter.discoveredDevices
- readonly property var pendingActions: adapter.pendingActions
- readonly property int connectedCount: connectedDevices.length
- readonly property int sessionCount: sessionOwners.length
-
- visible: false
- width: 0
- height: 0
-
- function beginSession(owner) {
- if (!owner) return false
- if (sessionOwners.indexOf(owner) < 0)
- sessionOwners = sessionOwners.concat([owner])
- if (sessionOwners.length === 1 && radioEnabled) adapter.startDiscovery()
- return true
- }
-
- function endSession(owner) {
- sessionOwners = sessionOwners.filter(item => item !== owner)
- if (sessionOwners.length === 0) adapter.stopDiscovery()
- }
-
- function restartDiscovery() {
- return sessionOwners.length > 0 && adapter.restartDiscovery()
- }
-
- function openPanel() {
- return bar && typeof bar.summonBarWidget === "function"
- ? bar.summonBarWidget("omarchy.bluetooth") : false
- }
-
- function closePanel() {
- return bar && typeof bar.hideBarWidget === "function"
- ? bar.hideBarWidget("omarchy.bluetooth") : false
- }
-
- function togglePanel() {
- return bar && typeof bar.isBarWidgetOpen === "function"
- && bar.isBarWidgetOpen("omarchy.bluetooth")
- ? closePanel() : openPanel()
- }
-
- function toggleBluetooth() {
- return adapter.toggleBluetooth()
- }
- function connectDevice(device) { return adapter.connectDevice(device) }
- function disconnectDevice(device) { return adapter.disconnectDevice(device) }
- function forgetDevice(device) { return adapter.forgetDevice(device) }
- function pendingAction(address) { return adapter.pendingAction(address) }
- function deviceLabel(device) { return adapter.deviceLabel(device) }
-
- onRadioEnabledChanged: {
- if (!radioEnabled) adapter.stopDiscovery()
- }
-
- Component.onDestruction: adapter.stopDiscovery()
-
- // Preserve Quattro's public contract with one process-wide Shibumi owner.
- IpcHandler {
- target: "omarchy.bluetooth"
-
- function open(): void { root.openPanel() }
- function show(): void { root.openPanel() }
- function toggle(): void { root.togglePanel() }
- function close(): void { root.closePanel() }
- function hide(): void { root.closePanel() }
- function toggleBluetooth(): void { root.toggleBluetooth() }
- }
-
- Adapters.BluetoothBackendAdapter {
- id: adapter
- backendOverride: root.backendOverride
- discoveryDesired: root.sessionCount > 0
- discoveryRequestTimeoutInterval: root.discoveryRequestTimeoutInterval
- }
-
- Timer {
- id: discoveryRetry
- interval: root.discoveryRetryInterval
- repeat: true
- triggeredOnStart: true
- running: root.sessionCount > 0 && root.adapterAvailable
- && root.radioEnabled && !root.discovering
- onTriggered: adapter.startDiscovery()
- }
-}
diff --git a/services/ClockService.qml b/services/ClockService.qml
deleted file mode 100644
index 96477bb..0000000
--- a/services/ClockService.qml
+++ /dev/null
@@ -1,11 +0,0 @@
-import QtQuick
-import Quickshell
-
-Scope {
- readonly property date date: systemClock.date
-
- SystemClock {
- id: systemClock
- precision: SystemClock.Minutes
- }
-}
diff --git a/services/GpuTelemetry.qml b/services/GpuTelemetry.qml
deleted file mode 100644
index b8b5d50..0000000
--- a/services/GpuTelemetry.qml
+++ /dev/null
@@ -1,196 +0,0 @@
-import QtQuick
-import Quickshell
-import Quickshell.Io
-
-Scope {
- id: root
-
- property int consumers: 0
- property string helperPath: String(Qt.resolvedUrl(
- "../scripts/shibumi-gpu-probe")).replace("file://", "")
- property var devices: []
- property string backend: ""
- property string name: ""
- property string driverName: ""
- property string driverVersion: ""
- property int utilization: 0
- property int temperatureC: 0
- property int memoryUsedMiB: 0
- property int memoryTotalMiB: 0
- property bool probeFailed: false
- readonly property bool available: backend !== ""
- readonly property int deviceCount: Array.isArray(devices) ? devices.length : 0
- readonly property var defaultDevice: deviceCount > 0 ? devices[0] : null
- readonly property int intervalMs: 1500
- readonly property int probeTimeoutSeconds: 2
-
- function acquire() {
- consumers++
- if (consumers === 1) refresh()
- }
-
- function release() {
- consumers = Math.max(0, consumers - 1)
- if (consumers === 0) probe.running = false
- }
-
- function refresh() {
- if (consumers <= 0 || probe.running) return
- probe.running = true
- }
-
- function boundedPercent(value) {
- return Math.max(0, Math.min(100, parseInt(value) || 0))
- }
-
- function nonNegativeInteger(value) {
- return Math.max(0, parseInt(value) || 0)
- }
-
- function deviceForId(deviceId) {
- const target = String(deviceId || "").trim()
- if (target === "" || target === "auto") return defaultDevice
- const rows = Array.isArray(devices) ? devices : []
- for (let index = 0; index < rows.length; index++) {
- if (String(rows[index].id || "") === target) return rows[index]
- }
- return null
- }
-
- function applySummary(device) {
- const value = device || null
- backend = value ? String(value.backend || "") : ""
- name = value ? String(value.name || "") : ""
- driverName = value ? String(value.driverName || "") : ""
- driverVersion = value ? String(value.driverVersion || "") : ""
- utilization = value ? boundedPercent(value.utilization) : 0
- temperatureC = value ? nonNegativeInteger(value.temperatureC) : 0
- memoryUsedMiB = value ? nonNegativeInteger(value.memoryUsedMiB) : 0
- memoryTotalMiB = value ? nonNegativeInteger(value.memoryTotalMiB) : 0
- }
-
- function publishDevices(rows) {
- devices = rows
- applySummary(rows.length > 0 ? rows[0] : null)
- }
-
- function parse(output) {
- const lines = String(output || "").trim().split("\n").filter(
- function(value) { return String(value || "").trim() !== "" })
- const completed = lines.some(function(value) {
- const parts = String(value || "").split("|")
- return parts[0] === "status" && parts[1] === "ok"
- })
- if (lines.length === 0 || !completed) {
- probeFailed = true
- return
- }
-
- const rows = []
- let noneReported = false
- let deviceRecordSeen = false
- for (let index = 0; index < lines.length; index++) {
- const fields = lines[index].split("|")
- if (fields[0] === "none") {
- noneReported = true
- continue
- }
- if (fields[0] !== "device") continue
- deviceRecordSeen = true
- if (fields.length < 11) continue
- const deviceId = String(fields[1] || "").trim()
- const deviceBackend = String(fields[2] || "").trim()
- if (deviceId === "" || deviceBackend === "") continue
- let duplicate = false
- for (let existing = 0; existing < rows.length; existing++) {
- if (rows[existing].id === deviceId) {
- duplicate = true
- break
- }
- }
- if (duplicate) continue
- rows.push({
- id: deviceId,
- backend: deviceBackend,
- utilization: boundedPercent(fields[3]),
- temperatureC: nonNegativeInteger(fields[4]),
- memoryUsedMiB: nonNegativeInteger(fields[5]),
- memoryTotalMiB: nonNegativeInteger(fields[6]),
- name: String(fields[7] || "").trim(),
- driverName: String(fields[8] || "").trim(),
- driverVersion: String(fields[9] || "").trim(),
- drmNode: String(fields[10] || "").trim(),
- available: true
- })
- }
-
- if (rows.length > 0) {
- publishDevices(rows)
- probeFailed = false
- return
- }
- if (deviceRecordSeen) {
- probeFailed = true
- return
- }
- if (noneReported) {
- publishDevices([])
- probeFailed = false
- return
- }
-
- // Accept the original single-device protocol for compatibility with
- // staged older helpers during transactional upgrades.
- const fields = lines[0].split("|")
- if (fields.length < 5 || fields[0] === "status") {
- probeFailed = true
- return
- }
- let legacyName = ""
- let legacyDriver = ""
- let legacyVersion = ""
- for (let index = 1; index < lines.length; index++) {
- const parts = lines[index].split("|")
- if (parts[0] !== "meta" || parts.length < 4) continue
- legacyName = String(parts[1] || "").trim()
- legacyDriver = String(parts[2] || "").trim()
- legacyVersion = String(parts[3] || "").trim()
- }
- const legacyBackend = String(fields[0] || "").trim()
- if (legacyBackend === "") {
- probeFailed = true
- return
- }
- publishDevices([{
- id: "legacy:" + legacyBackend,
- backend: legacyBackend,
- utilization: boundedPercent(fields[1]),
- temperatureC: nonNegativeInteger(fields[2]),
- memoryUsedMiB: nonNegativeInteger(fields[3]),
- memoryTotalMiB: nonNegativeInteger(fields[4]),
- name: legacyName,
- driverName: legacyDriver,
- driverVersion: legacyVersion,
- drmNode: "",
- available: true
- }])
- probeFailed = false
- }
-
- Process {
- id: probe
- command: ["timeout", "--signal=TERM",
- String(root.probeTimeoutSeconds), root.helperPath]
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: root.parse(text)
- }
- }
-
- Timer {
- interval: root.intervalMs
- running: root.consumers > 0
- repeat: true
- onTriggered: root.refresh()
- }
-}
diff --git a/services/MonitorService.qml b/services/MonitorService.qml
deleted file mode 100644
index 1bf193d..0000000
--- a/services/MonitorService.qml
+++ /dev/null
@@ -1,65 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import "../adapters" as Adapters
-
-// One process-wide owner for Quattro's monitor state and actions. The official
-// component keeps hardware, scale, display, OSD, and IPC ownership; every
-// output consumes this service through its own lazy Shibumi panel.
-Item {
- id: root
-
- required property var bar
- readonly property url panelSource: bar
- ? bar.registeredWidgetSource("omarchy.monitor") : ""
- property Component panelComponent: String(panelSource) ? null
- : bar ? bar.registeredWidgetComponent("omarchy.monitor") : null
-
- readonly property var backend: bridge.panel
- readonly property bool ready: bridge.ready
- readonly property bool brightnessAvailable: bridge.brightnessAvailable
- readonly property int brightnessPercent: bridge.brightnessPercent
- readonly property string internalMonitor: bridge.internalMonitor
- readonly property string externalMonitor: bridge.externalMonitor
- readonly property string focusedMonitor: bridge.focusedMonitor
- readonly property bool internalEnabled: bridge.internalEnabled
- readonly property bool mirrorEnabled: bridge.mirrorEnabled
- readonly property string monitorScale: bridge.monitorScale
- readonly property var displays: bridge.displays
- readonly property int enabledDisplayCount: bridge.enabledDisplayCount
- readonly property var scaleValues: ["1", "1.25", "1.6", "2", "3", "4"]
- readonly property bool textSizeAvailable: bridge.textSizeAvailable
- readonly property var textSizeStops: bridge.textSizeStops
- readonly property int textSizeIndex: bridge.textSizeIndex
- readonly property real textSizePx: bridge.textSizePx
-
- visible: false
- width: 0
- height: 0
-
- function officialSettings() {
- return bar && typeof bar.widgetSettings === "function"
- ? bar.widgetSettings("G13", "omarchy.monitor") : ({})
- }
-
- function refresh() { return bridge.refresh() }
- function setBrightness(value) { return bridge.setBrightness(value) }
- function previewBrightness(value) { return bridge.previewBrightness(value) }
- function setScale(value) { return bridge.setScale(value) }
- function setTextSize(value) { return bridge.setTextSize(value) }
- function adjustTextSize(delta) { return bridge.adjustTextSize(delta) }
- function toggleDisplay(name, enabled) {
- return bridge.toggleDisplay(name, enabled)
- }
- function normalizeScale(value) { return bridge.normalizeScale(value) }
- function brightnessName(value) { return bridge.brightnessName(value) }
-
- Adapters.MonitorPanelBridge {
- id: bridge
- bar: root.bar
- ownerWidget: root.bar
- panelComponent: root.panelComponent
- panelSource: root.panelSource
- panelSettings: root.officialSettings()
- }
-}
diff --git a/services/NetworkService.qml b/services/NetworkService.qml
deleted file mode 100644
index d4354a3..0000000
--- a/services/NetworkService.qml
+++ /dev/null
@@ -1,798 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell.Io
-import Quickshell.Networking
-import "../adapters" as Adapters
-
-// One process-wide NetworkManager owner for every Shibumi output. Quattro's
-// official network component remains authoritative for live status, details,
-// DNS, and visible-network actions. Shibumi owns the session-scoped scanner
-// lease required by its hidden backend, its inline speed-test process, and the
-// saved-profile view missing from the host API.
-Item {
- id: root
-
- required property var bar
- readonly property url panelSource: bar
- ? bar.registeredWidgetSource("omarchy.network") : ""
- property Component panelComponent: String(panelSource) ? null
- : bar ? bar.registeredWidgetComponent("omarchy.network") : null
- readonly property var backend: bridge.panel
- readonly property url speedTestRunnerSource:
- Qt.resolvedUrl("../hancore.shibumi.network/InlineSpeedTestRunner.py")
- property string speedTestExecutable: "omarchy-network-speedtest"
- property int speedTestPhaseDuration: 5000
- readonly property bool speedTestReady: String(speedTestRunnerSource) !== ""
- && String(speedTestExecutable || "") !== ""
- property bool speedTestRunning: false
- readonly property bool speedTestHasRun: speedTestDownloadMbps !== ""
- || speedTestUploadMbps !== ""
- property string speedTestPhase: ""
- property string speedTestDownloadMbps: ""
- property string speedTestUploadMbps: ""
- property string speedTestError: ""
- property bool speedTestExpectedStop: false
- property bool speedTestPendingRun: false
- property bool speedTestProcessStarted: false
- property string speedTestStderr: ""
- readonly property bool ready: backend !== null
- readonly property bool backendAvailable: ready && bridge.backendAvailable
- readonly property string kind: bridge.kind
- readonly property string label: bridge.label
- readonly property int signalStrength: bridge.signalStrength
- property bool scanPending: false
- property var scannerDevice: null
- readonly property bool scanning: bridge.scanning || scanPending
- readonly property bool busy: bridge.busy || profileAction.running
- readonly property bool wifiEnabled: Networking.wifiEnabled
- readonly property bool wifiAvailable: backend
- ? backend.wifiStationAvailable === true : false
- readonly property var info: backend && backend.info ? backend.info : ({})
- readonly property real downloadRate: backend ? Number(backend.downloadRate || 0) : 0
- readonly property real uploadRate: backend ? Number(backend.uploadRate || 0) : 0
- readonly property real routerPingLatency: backend
- ? Number(backend.routerPingLatency === undefined ? -1 : backend.routerPingLatency) : -1
- readonly property real internetPingLatency: backend
- ? Number(backend.internetPingLatency === undefined ? -1 : backend.internetPingLatency) : -1
- readonly property int internetPingPacketLoss: backend
- ? Number(backend.internetPingPacketLoss || 0) : 0
- readonly property string dnsProvider: backend
- ? String(backend.dnsProvider || "DHCP") : "DHCP"
- readonly property var dnsProviders: backend && backend.dnsProviders
- ? backend.dnsProviders : ["DHCP", "Cloudflare", "Google", "Custom"]
- readonly property string actionSsid: backend ? String(backend.actionSsid || "") : ""
- readonly property string actionKind: backend ? String(backend.actionKind || "") : ""
- readonly property string failureSsid: backend ? String(backend.failureSsid || "") : ""
- readonly property string failureReason: backend ? String(backend.failureReason || "") : ""
- readonly property var visibleNetworks: backend && backend.wifiNetworks
- ? backend.wifiNetworks : []
- property var savedProfiles: []
- property bool profilesLoaded: false
- property string profileError: ""
- property var sessionOwners: []
- readonly property int sessionCount: sessionOwners.length
- readonly property var networks: mergedNetworks(visibleNetworks, savedProfiles)
-
- function activeWifiDevice() {
- return ready && backend && backend.wifiDevice
- ? backend.wifiDevice : null
- }
-
- function setSessionScannerEnabled(enabled) {
- const nextDevice = sessionCount > 0 ? activeWifiDevice() : null
- if (scannerDevice && scannerDevice !== nextDevice)
- scannerDevice.scannerEnabled = false
- scannerDevice = nextDevice
- if (!scannerDevice) return false
- scannerDevice.scannerEnabled = enabled === true
- return true
- }
-
- function releaseWifiScanner() {
- scanRestart.stop()
- scanDone.stop()
- scanPending = false
- const currentDevice = activeWifiDevice()
- if (scannerDevice) scannerDevice.scannerEnabled = false
- if (currentDevice && currentDevice !== scannerDevice)
- currentDevice.scannerEnabled = false
- scannerDevice = null
- }
-
- function requestWifiScan() {
- scanRestart.stop()
- scanDone.stop()
- if (sessionCount <= 0 || !activeWifiDevice()) {
- releaseWifiScanner()
- return false
- }
- scanPending = true
- setSessionScannerEnabled(false)
- scanRestart.restart()
- return true
- }
-
- function completeWifiScan() {
- scanPending = false
- if (backend && typeof backend.syncWifiNetworks === "function")
- backend.syncWifiNetworks()
- }
-
- visible: false
- width: 0
- height: 0
-
- onReadyChanged: {
- if (!ready || sessionCount === 0) releaseWifiScanner()
- else requestWifiScan()
- }
-
- Connections {
- target: root.backend
- function onWifiDeviceChanged() {
- if (root.sessionCount > 0) root.requestWifiScan()
- else root.releaseWifiScanner()
- }
- }
-
- function officialSettings() {
- return bar && typeof bar.widgetSettings === "function"
- ? bar.widgetSettings("G11", "omarchy.network") : ({})
- }
-
- function beginSession(owner) {
- if (!owner || sessionOwners.indexOf(owner) >= 0) return
- const next = sessionOwners.slice()
- next.push(owner)
- sessionOwners = next
- if (sessionCount === 1) {
- refresh(true)
- }
- }
-
- function endSession(owner) {
- if (!owner) return
- const nextOwners = sessionOwners.filter(candidate => candidate !== owner)
- if (nextOwners.length === 0) stopSpeedTest()
- sessionOwners = nextOwners
- if (sessionCount !== 0) return
- detailsPoll.stop()
- detailsProc.running = false
- profileList.running = false
- releaseWifiScanner()
- }
-
- function refresh(scanWifi) {
- if (!ready || typeof backend.refresh !== "function") return false
- const shouldScanWifi = scanWifi === true && activeWifiDevice() !== null
- // The hidden backend may gate its own scanner on `opened`, while older
- // hosts enable it synchronously even for refresh(false). A requested scan
- // therefore defers every backend refresh until our scanner lease starts.
- if (shouldScanWifi) {
- requestWifiScan()
- if (sessionCount > 0 && !profileList.running) refreshProfiles()
- return true
- }
- if (scanPending) return true
- backend.refresh(false)
- if (sessionCount > 0) setSessionScannerEnabled(true)
- else releaseWifiScanner()
- return true
- }
-
- function toggleWifi() {
- if (!backendAvailable || !wifiAvailable) return false
- Networking.wifiEnabled = !Networking.wifiEnabled
- if (Networking.wifiEnabled) Qt.callLater(function() { root.refresh(true) })
- return true
- }
-
- function securityKind(security) {
- switch (security) {
- case WifiSecurityType.Open: return "open"
- case WifiSecurityType.WpaPsk:
- case WifiSecurityType.Wpa2Psk:
- case WifiSecurityType.Sae: return "psk"
- case WifiSecurityType.Owe: return "owe"
- case WifiSecurityType.StaticWep:
- case WifiSecurityType.DynamicWep: return "wep"
- case WifiSecurityType.WpaEap:
- case WifiSecurityType.Wpa2Eap: return "enterprise"
- case WifiSecurityType.Wpa3SuiteB192:
- case WifiSecurityType.Leap: return "unsupported"
- default: return "unknown"
- }
- }
-
- function securityLabel(security) {
- switch (security) {
- case WifiSecurityType.Open: return "Open"
- case WifiSecurityType.WpaPsk: return "WPA Personal"
- case WifiSecurityType.Wpa2Psk: return "WPA2 Personal"
- case WifiSecurityType.Sae: return "WPA3 Personal"
- case WifiSecurityType.Owe: return "Enhanced Open (OWE)"
- case WifiSecurityType.StaticWep:
- case WifiSecurityType.DynamicWep: return "WEP"
- case WifiSecurityType.WpaEap: return "WPA Enterprise"
- case WifiSecurityType.Wpa2Eap: return "WPA2 Enterprise"
- case WifiSecurityType.Wpa3SuiteB192: return "WPA3 Enterprise"
- case WifiSecurityType.Leap: return "LEAP"
- default: return "Unknown"
- }
- }
-
- function profileSecurityLabel(keyManagement, authAlgorithm) {
- switch (String(keyManagement || "").toLowerCase()) {
- case "wpa-psk": return "WPA Personal profile"
- case "sae": return "WPA3 Personal profile"
- case "owe": return "Enhanced Open profile"
- case "wpa-eap": return "WPA Enterprise profile"
- case "wpa-eap-suite-b-192": return "WPA3 Suite B profile"
- case "ieee8021x": return String(authAlgorithm || "").toLowerCase()
- === "leap" ? "LEAP profile" : "802.1X profile"
- case "open": return "Open profile"
- case "none": return "WEP profile"
- default: return "Saved Wi-Fi profile"
- }
- }
-
- function profileMatchesSecurity(profile, security) {
- const keyManagement = String(profile && profile.keyManagement || "")
- .toLowerCase()
- switch (keyManagement) {
- case "wpa-psk":
- return security === WifiSecurityType.WpaPsk
- || security === WifiSecurityType.Wpa2Psk
- case "sae": return security === WifiSecurityType.Sae
- case "owe": return security === WifiSecurityType.Owe
- case "wpa-eap":
- return security === WifiSecurityType.WpaEap
- || security === WifiSecurityType.Wpa2Eap
- case "wpa-eap-suite-b-192":
- return security === WifiSecurityType.Wpa3SuiteB192
- case "ieee8021x":
- return String(profile.authAlgorithm || "").toLowerCase() === "leap"
- ? security === WifiSecurityType.Leap
- : security === WifiSecurityType.DynamicWep
- case "open": return security === WifiSecurityType.Open
- case "none": return security === WifiSecurityType.StaticWep
- default: return false
- }
- }
-
- function profileVisibleMatchCount(profile, visible) {
- if (!profile || !Array.isArray(visible)) return 0
- let count = 0
- for (let i = 0; i < visible.length; i++) {
- const candidate = visible[i]
- if (candidate && candidate.ssid === profile.ssid
- && profileMatchesSecurity(profile, candidate.security)) count++
- }
- return count
- }
-
- function mergedNetworks(visible, profiles) {
- const rows = []
- const visibleRows = Array.isArray(visible) ? visible : []
- const savedRows = Array.isArray(profiles) ? profiles : []
- const representedProfileUuids = ({})
-
- for (let i = 0; i < visibleRows.length; i++) {
- const source = visibleRows[i]
- if (!source) continue
- const matchingProfiles = []
- for (let p = 0; p < savedRows.length; p++) {
- const candidate = savedRows[p]
- if (candidate && candidate.ssid === source.ssid
- && profileMatchesSecurity(candidate, source.security))
- matchingProfiles.push(candidate)
- }
- const profile = matchingProfiles.length === 1
- && profileVisibleMatchCount(matchingProfiles[0], visibleRows) === 1
- ? matchingProfiles[0] : null
- if (profile && String(profile.uuid || ""))
- representedProfileUuids[String(profile.uuid)] = true
- rows.push({
- connected: source.connected === true,
- known: source.known === true || profile !== null,
- ssid: String(source.ssid || ""),
- signal: Math.max(0, Math.min(100, Number(source.signal || 0))),
- security: source.security,
- securityKind: securityKind(source.security),
- securityLabel: securityLabel(source.security),
- visible: true,
- profileUuid: profile ? String(profile.uuid || "") : "",
- lastSuccessful: profile ? Number(profile.lastSuccessful || 0) : 0,
- entryKey: profile ? "profile:" + profile.uuid
- : "network:" + String(source.ssid || "") + ":" + securityKind(source.security)
- })
- }
-
- for (let p = 0; p < savedRows.length; p++) {
- const profile = savedRows[p]
- if (!profile || representedProfileUuids[String(profile.uuid || "")])
- continue
- rows.push({
- network: null,
- connected: false,
- known: true,
- ssid: String(profile.ssid || ""),
- signal: 0,
- security: null,
- securityKind: "saved",
- securityLabel: profileSecurityLabel(profile.keyManagement,
- profile.authAlgorithm),
- visible: false,
- profileUuid: String(profile.uuid || ""),
- lastSuccessful: Number(profile.lastSuccessful || 0),
- entryKey: "profile:" + String(profile.uuid || "")
- })
- }
-
- rows.sort(function(left, right) {
- if (left.connected !== right.connected) return left.connected ? -1 : 1
- if (left.known !== right.known) return left.known ? -1 : 1
- if (left.visible !== right.visible) return left.visible ? -1 : 1
- return right.signal - left.signal
- })
- return rows
- }
-
- function visibleIdentityMatchCount(entry) {
- if (!entry || entry.visible === false) return 0
- const ssid = String(entry.ssid || "")
- if (!ssid) return 0
- const expectedKind = String(entry.securityKind
- || securityKind(entry.security))
- const hasExactSecurity = entry.security !== undefined
- && entry.security !== null
- const rows = Array.isArray(visibleNetworks) ? visibleNetworks : []
- let count = 0
- for (let i = 0; i < rows.length; i++) {
- const candidate = rows[i]
- if (!candidate || String(candidate.ssid || "") !== ssid) continue
- if (hasExactSecurity && candidate.security !== entry.security) continue
- if (!hasExactSecurity
- && securityKind(candidate.security) !== expectedKind) continue
- count++
- }
- return count
- }
-
- function hasUniqueVisibleIdentity(entry) {
- return visibleIdentityMatchCount(entry) === 1
- }
-
- function currentVisibleEntry(entry) {
- if (!entry || entry.visible === false) return null
- const ssid = String(entry.ssid || "")
- const hasExactSecurity = entry.security !== undefined
- && entry.security !== null
- const expectedKind = String(entry.securityKind
- || securityKind(entry.security))
- const rows = mergedNetworks(visibleNetworks, savedProfiles)
- let result = null
- let count = 0
- for (let i = 0; i < rows.length; i++) {
- const candidate = rows[i]
- if (!candidate || candidate.visible === false
- || String(candidate.ssid || "") !== ssid) continue
- if (hasExactSecurity && candidate.security !== entry.security) continue
- if (!hasExactSecurity && candidate.securityKind !== expectedKind) continue
- result = candidate
- count++
- }
- return count === 1 ? result : null
- }
-
- function savedProfileMatchCount(entry) {
- if (!entry || !Array.isArray(savedProfiles)) return 0
- let count = 0
- for (let i = 0; i < savedProfiles.length; i++) {
- const profile = savedProfiles[i]
- if (profile && profile.ssid === entry.ssid
- && profileMatchesSecurity(profile, entry.security)) count++
- }
- return count
- }
-
- function visibleSsidMatchCount(entry) {
- const ssid = String(entry && entry.ssid || "")
- if (!ssid || !Array.isArray(visibleNetworks)) return 0
- let count = 0
- for (let i = 0; i < visibleNetworks.length; i++) {
- const candidate = visibleNetworks[i]
- if (candidate && String(candidate.ssid || "") === ssid) count++
- }
- return count
- }
-
- function hasUnambiguousVisibleSsid(entry) {
- return hasUniqueVisibleIdentity(entry)
- && visibleSsidMatchCount(entry) === 1
- }
-
- function connect(entry) {
- if (!entry || busy) return false
- profileError = ""
- if (entry.profileUuid && entry.visible === false)
- return runProfileAction("connect", entry.profileUuid)
- if (!ready || !hasUniqueVisibleIdentity(entry)) return false
- if (entry.connected) return disconnect(entry)
- if (entry.profileUuid)
- return runProfileAction("connect", entry.profileUuid)
- if (hasUnambiguousVisibleSsid(entry)
- && (entry.known || entry.securityKind === "open")
- && typeof backend.connectKnown === "function") {
- backend.connectKnown(String(entry.ssid || ""))
- return true
- }
- return false
- }
-
- function connectWithPassphrase(entry, passphrase) {
- const current = currentVisibleEntry(entry)
- if (!entry || entry.securityKind !== "psk"
- || !String(passphrase || "") || !ready || busy
- || !hasUnambiguousVisibleSsid(entry) || !current
- || current.connected === true || current.known === true
- || savedProfileMatchCount(entry) > 0
- || typeof backend.connectWithPassphrase !== "function") return false
- profileError = ""
- backend.connectWithPassphrase(String(entry.ssid || ""), String(passphrase))
- return true
- }
-
- function connectEnterprise(entry, identity, passphrase) {
- const current = currentVisibleEntry(entry)
- if (!entry || entry.securityKind !== "enterprise"
- || !String(identity || "") || !String(passphrase || "")
- || !ready || busy || !hasUnambiguousVisibleSsid(entry) || !current
- || current.connected === true || current.known === true
- || savedProfileMatchCount(entry) > 0
- || typeof backend.connectEnterprise !== "function") return false
- profileError = ""
- backend.connectEnterprise(String(entry.ssid || ""),
- String(identity), String(passphrase))
- return true
- }
-
- function disconnect(entry) {
- if (!entry || entry.connected !== true || !ready || busy
- || !hasUnambiguousVisibleSsid(entry)
- || typeof backend.disconnectRow !== "function") return false
- backend.disconnectRow(String(entry.ssid || ""))
- return true
- }
-
- function forget(entry) {
- if (!entry || !entry.known || busy) return false
- profileError = ""
- if (entry.profileUuid && entry.visible === false)
- return runProfileAction("forget", entry.profileUuid)
- if (!ready || !hasUniqueVisibleIdentity(entry)) return false
- if (entry.profileUuid)
- return runProfileAction("forget", entry.profileUuid)
- if (!hasUnambiguousVisibleSsid(entry)
- || typeof backend.forget !== "function") return false
- backend.forget(entry)
- return true
- }
-
- function setDns(provider) {
- if (!ready || typeof backend.setDns !== "function") return false
- backend.setDns(provider)
- return true
- }
-
- function speedTestCommandFor(phaseValue) {
- return [String(speedTestRunnerSource),
- String(speedTestExecutable || ""), String(phaseValue || "")]
- }
-
- function updateSpeedTestLine(line) {
- const raw = String(line || "").trim()
- if (!/^(?:\d+(?:\.\d*)?|\.\d+)$/.test(raw)) return
- const value = Number(raw)
- if (!isFinite(value) || value < 0) return
- const normalized = String(value)
- if (speedTestPhase === "down") speedTestDownloadMbps = normalized
- else if (speedTestPhase === "up") speedTestUploadMbps = normalized
- }
-
- function startSpeedTestPhase(phaseValue) {
- speedTestExpectedStop = false
- speedTestProcessStarted = false
- speedTestPhase = phaseValue
- speedTestStderr = ""
- speedTestProc.command = speedTestCommandFor(phaseValue)
- speedTestProc.running = true
- speedTestPhaseTimer.restart()
- }
-
- function speedTestPhaseHasSample() {
- return speedTestPhase === "down"
- ? speedTestDownloadMbps !== "" : speedTestUploadMbps !== ""
- }
-
- function failSpeedTest(message) {
- speedTestPhaseTimer.stop()
- speedTestError = String(message || "Speed test failed")
- speedTestPhase = ""
- speedTestRunning = false
- speedTestExpectedStop = false
- speedTestProcessStarted = false
- }
-
- function finishSpeedTestPhase() {
- if (!speedTestRunning) return
- if (speedTestPhase === "down") {
- startSpeedTestPhase("up")
- return
- }
- speedTestPhase = ""
- speedTestRunning = false
- speedTestExpectedStop = false
- }
-
- function stopSpeedTestPhase() {
- speedTestPhaseTimer.stop()
- if (!speedTestRunning) return
- if (speedTestProc.running) {
- speedTestExpectedStop = true
- speedTestProc.running = false
- return
- }
- finishSpeedTestPhase()
- }
-
- function handleSpeedTestExit(exitCode) {
- speedTestPhaseTimer.stop()
- const expected = speedTestExpectedStop
- speedTestExpectedStop = false
- speedTestProcessStarted = false
- if (speedTestPendingRun) {
- speedTestPendingRun = false
- speedTestRunning = false
- if (sessionCount > 0) Qt.callLater(function() {
- if (root.sessionCount > 0) root.runSpeedTest()
- })
- return
- }
- if (!speedTestRunning) return
- if (!expected && exitCode !== 0) {
- failSpeedTest(speedTestStderr || "Speed test failed")
- return
- }
- if (!speedTestPhaseHasSample()) {
- failSpeedTest(speedTestStderr || "Speed test produced no "
- + speedTestPhase + "load data")
- return
- }
- finishSpeedTestPhase()
- }
-
- function stopSpeedTest() {
- const active = speedTestRunning || speedTestProc.running
- speedTestPhaseTimer.stop()
- speedTestPendingRun = false
- speedTestPhase = ""
- speedTestRunning = false
- if (speedTestProc.running) {
- speedTestExpectedStop = true
- speedTestProc.running = false
- } else {
- speedTestExpectedStop = false
- speedTestProcessStarted = false
- }
- return active
- }
-
- function runSpeedTest() {
- if (!ready || !speedTestReady || speedTestRunning) return false
- speedTestError = ""
- speedTestDownloadMbps = ""
- speedTestUploadMbps = ""
- if (speedTestProc.running) {
- if (sessionCount <= 0) return false
- speedTestPendingRun = true
- speedTestRunning = true
- speedTestPhase = ""
- return true
- }
- speedTestPendingRun = false
- speedTestRunning = true
- startSpeedTestPhase("down")
- return true
- }
-
- function formatRate(value) {
- return ready && typeof backend.formatRate === "function"
- ? backend.formatRate(value) : "0 B/s"
- }
-
- function formatPing(value) {
- return ready && typeof backend.formatPingLatency === "function"
- ? backend.formatPingLatency(value) : "--"
- }
-
- function formatSpeed(value) {
- const speed = Number(value)
- if (!(speed > 0)) return "—"
- return (speed >= 100 ? speed.toFixed(0) : speed.toFixed(1)) + " Mbps"
- }
-
- function refreshProfiles() {
- if (sessionCount <= 0 || profileList.running) return false
- profilesLoaded = false
- profileList.running = true
- return true
- }
-
- function runProfileAction(action, uuid) {
- const id = String(uuid || "")
- if (!id || profileAction.running) return false
- profileAction.action = action
- profileAction.command = action === "connect"
- ? ["nmcli", "--wait", "20", "connection", "up", "uuid", id]
- : ["nmcli", "--wait", "20", "connection", "delete", "uuid", id]
- profileAction.running = true
- return true
- }
-
- Adapters.NetworkPanelBridge {
- id: bridge
- bar: root.bar
- ownerWidget: root.bar
- networkService: root
- panelComponent: root.panelComponent
- panelSource: root.panelSource
- panelSettings: root.officialSettings()
- }
-
- Timer {
- id: scanRestart
- interval: 100
- repeat: false
- onTriggered: {
- if (root.sessionCount > 0
- && root.setSessionScannerEnabled(true)) {
- if (root.backend && typeof root.backend.refresh === "function")
- root.backend.refresh(false)
- scanDone.restart()
- } else {
- root.scanPending = false
- }
- }
- }
-
- Timer {
- id: scanDone
- interval: 1500
- repeat: false
- onTriggered: root.completeWifiScan()
- }
-
- Timer {
- id: speedTestPhaseTimer
- interval: root.speedTestPhaseDuration
- repeat: false
- onTriggered: root.stopSpeedTestPhase()
- }
-
- Process {
- id: speedTestProc
- onStarted: root.speedTestProcessStarted = true
- onRunningChanged: {
- if (!running && root.speedTestRunning
- && !root.speedTestProcessStarted)
- root.failSpeedTest("Unable to start network speed test")
- }
- stdout: SplitParser {
- onRead: function(line) { root.updateSpeedTestLine(line) }
- }
- stderr: StdioCollector {
- waitForEnd: true
- onStreamFinished: {
- root.speedTestStderr = String(text || "").trim()
- if (root.speedTestError !== "" && root.speedTestStderr !== "")
- root.speedTestError = root.speedTestStderr
- }
- }
- onExited: function(exitCode, _exitStatus) {
- root.handleSpeedTestExit(exitCode)
- }
- }
-
- Timer {
- id: detailsPoll
- interval: 1500
- repeat: true
- running: root.sessionCount > 0 && root.ready
- onTriggered: {
- if (!detailsProc.running) detailsProc.running = true
- }
- }
-
- Process {
- id: detailsProc
- command: ["omarchy-network-status", "--verbose"]
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: {
- if (root.backend && typeof root.backend.updateDetails === "function")
- root.backend.updateDetails(text)
- }
- }
- }
-
- Process {
- id: profileList
- command: ["bash", "-c",
- "connections=$(nmcli -t -f UUID,TYPE connection show) || exit 1; "
- + "printf '__READY__\\n'; "
- + "while IFS=: read -r uuid type; do "
- + "case \"$type\" in 802-11-wireless|wifi) ;; *) continue ;; esac; "
- + "mapfile -t details < <(nmcli --escape no -g 802-11-wireless.ssid,connection.timestamp,802-11-wireless-security.key-mgmt,802-11-wireless-security.auth-alg connection show uuid \"$uuid\"); "
- + "ssid=${details[0]-}; timestamp=${details[1]:-0}; key_mgmt=${details[2]-}; auth_alg=${details[3]-}; "
- + "[ -n \"$ssid\" ] || continue; "
- + "case \"$timestamp\" in ''|*[!0-9]*) timestamp=0 ;; esac; "
- + "[ -n \"$key_mgmt\" ] || key_mgmt=open; [ -n \"$auth_alg\" ] || auth_alg=unknown; "
- + "printf '%s\\t%s\\t%s\\t%s\\t%s\\n' \"$uuid\" \"$ssid\" \"$timestamp\" \"$key_mgmt\" \"$auth_alg\"; "
- + "done <<< \"$connections\""]
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: {
- const lines = String(text || "").trim().split("\n")
- const ready = lines.length > 0 && lines[0] === "__READY__"
- root.profilesLoaded = ready
- if (!ready) {
- root.profileError = "Saved networks unavailable"
- return
- }
- const profiles = []
- for (let i = 1; i < lines.length; i++) {
- if (!lines[i]) continue
- const fields = lines[i].split("\t")
- if (fields.length < 5) continue
- profiles.push({
- uuid: fields[0],
- ssid: fields.slice(1, fields.length - 3).join("\t"),
- lastSuccessful: parseInt(fields[fields.length - 3]) || 0,
- keyManagement: fields[fields.length - 2],
- authAlgorithm: fields[fields.length - 1]
- })
- }
- root.profileError = ""
- root.savedProfiles = profiles
- }
- }
- }
-
- Process {
- id: profileAction
- property string action: ""
- stderr: StdioCollector {
- id: profileActionError
- waitForEnd: true
- }
- onExited: function(exitCode, _exitStatus) {
- root.profileError = exitCode === 0 ? ""
- : String(profileActionError.text || "Network action failed").trim()
- action = ""
- if (root.sessionCount > 0) {
- root.refresh(false)
- root.refreshProfiles()
- }
- }
- }
-
- Component.onDestruction: {
- sessionOwners = []
- releaseWifiScanner()
- stopSpeedTest()
- detailsProc.running = false
- profileList.running = false
- }
-}
diff --git a/services/OpenCodeProvider.qml b/services/OpenCodeProvider.qml
deleted file mode 100644
index 4fce3f8..0000000
--- a/services/OpenCodeProvider.qml
+++ /dev/null
@@ -1,201 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell.Io
-
-Item {
- id: root
- visible: false
-
- property string providerId: "opencode"
- property string providerName: "OpenCode"
- property bool enabled: false
- property var scannerCommandOverride: []
- property int scanGeneration: 0
- property bool ready: false
- property bool refreshing: false
- property bool midnightRefreshPending: false
- property double lastRefreshedAtMs: 0
-
- property real rateLimitPercent: -1
- property string rateLimitLabel: "5h soft cap"
- property string rateLimitResetAt: ""
- property real secondaryRateLimitPercent: -1
- property string secondaryRateLimitLabel: "7d soft cap"
- property string secondaryRateLimitResetAt: ""
-
- property real todayTotalTokens: 0
- property real windowTokens: 0
- property real hourlyTokens: 0
- property var models: []
- property string latestModel: ""
- property string dataDay: ""
- property string tierLabel: "Local messages"
- property string usageStatusText: ""
- property string authHelpText: "OpenCode usage is read from its local database."
- property bool hasLocalStats: true
-
- readonly property string scannerPath: String(
- Qt.resolvedUrl("../scripts/opencode-usage")).replace("file://", "")
- readonly property bool scannerRunning: usageScanner.running
-
- function clampPercent(value) {
- const number = Number(value)
- if (!isFinite(number)) return -1
- return Math.max(0, Math.min(100, number * 100))
- }
-
- function localDay() {
- return Qt.formatDateTime(new Date(), "yyyy-MM-dd")
- }
-
- function millisecondsUntilNextLocalMidnight() {
- const now = new Date()
- const next = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)
- return Math.max(1, next.getTime() - now.getTime())
- }
-
- function clearCurrentDayData() {
- todayTotalTokens = 0
- latestModel = ""
- models = []
- dataDay = ""
- }
-
- function clearScannerData() {
- ready = false
- rateLimitPercent = -1
- secondaryRateLimitPercent = -1
- windowTokens = 0
- hourlyTokens = 0
- clearCurrentDayData()
- }
-
- function scheduleMidnightInvalidation() {
- if (!enabled) {
- midnightTimer.stop()
- return
- }
- midnightTimer.interval = millisecondsUntilNextLocalMidnight()
- midnightTimer.restart()
- }
-
- function handleLocalMidnight() {
- clearCurrentDayData()
- if (usageScanner.running) midnightRefreshPending = true
- else refresh(true)
- scheduleMidnightInvalidation()
- }
-
- function refresh(_force) {
- if (!enabled || usageScanner.running) return
- refreshing = true
- usageScanner.running = true
- scanGeneration++
- }
-
- function stopScanner() {
- if (usageScanner.running) usageScanner.running = false
- refreshing = false
- }
-
- function finishScanner() {
- refreshing = false
- lastRefreshedAtMs = Date.now()
- if (midnightRefreshPending) {
- midnightRefreshPending = false
- Qt.callLater(function() { root.refresh(true) })
- }
- }
-
- function parseScannerOutput(output) {
- const raw = String(output || "").trim()
- if (!raw) {
- clearScannerData()
- usageStatusText = "OpenCode data unavailable"
- return
- }
- try {
- const data = JSON.parse(raw.split("\n").pop())
- ready = data.ready === true
- if (!ready) {
- clearScannerData()
- usageStatusText = "OpenCode data unavailable"
- return
- }
- rateLimitPercent = clampPercent(data["5h-utilization"])
- secondaryRateLimitPercent = clampPercent(data["7d-utilization"])
- windowTokens = Math.max(0, Number(data._tokens_used) || 0)
- hourlyTokens = Math.max(0, Number(data._rate_per_hour) || 0)
- const scanDay = String(data._day || "")
- if (scanDay === localDay()) {
- todayTotalTokens = Math.max(0, Number(data._today_tokens) || 0)
- latestModel = String(data._model || "")
- models = Array.isArray(data._models) ? data._models : []
- dataDay = scanDay
- } else {
- clearCurrentDayData()
- }
- tierLabel = String(data._plan || "Local messages")
- usageStatusText = String(data.status || "allowed") === "allowed_warning"
- ? "Approaching local soft cap" : "Local activity"
- } catch (error) {
- clearScannerData()
- usageStatusText = "OpenCode scan failed"
- authHelpText = String(error)
- }
- }
-
- function formatResetTime(_timestamp) { return "" }
-
- Process {
- id: usageScanner
- command: Array.isArray(root.scannerCommandOverride)
- && root.scannerCommandOverride.length > 0
- ? root.scannerCommandOverride : ["python3", root.scannerPath]
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: root.parseScannerOutput(text)
- }
- stderr: StdioCollector {
- waitForEnd: true
- onStreamFinished: if (String(text || "").trim())
- console.warn("shibumi/opencode", String(text).trim())
- }
- onExited: root.finishScanner()
- }
-
- Timer {
- id: refreshTimer
- interval: 5 * 60 * 1000
- running: root.enabled
- repeat: true
- triggeredOnStart: true
- onTriggered: root.refresh(false)
- }
-
- // UI-only day data expires exactly at the next local midnight. This timer
- // exists only while OpenCode is enabled and is rescheduled after each fire;
- // it owns no worker and the shared five-minute scanner remains unchanged.
- Timer {
- id: midnightTimer
- repeat: false
- onTriggered: root.handleLocalMidnight()
- }
-
- onEnabledChanged: {
- if (!enabled) {
- midnightRefreshPending = false
- stopScanner()
- midnightTimer.stop()
- } else {
- scheduleMidnightInvalidation()
- }
- }
- Component.onCompleted: scheduleMidnightInvalidation()
- Component.onDestruction: {
- midnightRefreshPending = false
- midnightTimer.stop()
- stopScanner()
- }
-}
diff --git a/services/PickerService.qml b/services/PickerService.qml
deleted file mode 100644
index 62971ef..0000000
--- a/services/PickerService.qml
+++ /dev/null
@@ -1,495 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import Quickshell.Hyprland
-import Quickshell.Io
-import qs.Commons as Commons
-import "PickerModel.js" as PickerModel
-
-Item {
- id: root
-
- property var bar: null
- readonly property string home: Quickshell.env("HOME")
- readonly property string scriptPath: String(
- Qt.resolvedUrl("../scripts/shibumi-picker")).replace("file://", "")
- readonly property bool available: bar !== null && bar.hostReady
- && bar.omarchyPath !== ""
-
- property bool opened: false
- property string mode: "wallpaper"
- property string pickerStyle: "tanzaku"
- property var activeScreen: null
- property string activeScreenName: activeScreen ? String(activeScreen.name || "") : ""
- property var entries: []
- readonly property var filteredEntries: PickerModel.filtered(entries, filterText)
- property int selectedIndex: 0
- readonly property var selectedEntry: filteredEntries.length > 0
- ? filteredEntries[PickerModel.clampIndex(selectedIndex, filteredEntries.length)] : null
- property string filterText: ""
- property bool loading: false
- property bool scanComplete: false
- property bool confirmDelete: false
- property string currentSelection: ""
- property string statusText: ""
- property int requestSerial: 0
- property bool cachedRowsApplied: false
- property string selectedThemeAuthor: ""
- property string selectedThemeRepo: ""
- property var selectedThemePalette: []
-
- readonly property bool imageMode: mode === "theme" || mode === "wallpaper"
- readonly property bool mediaMode: mode === "screenshots" || mode === "videos"
- readonly property bool videoMode: mode === "videos"
- readonly property string title: mode === "theme" ? "Themes"
- : mode === "wallpaper" ? "Wallpapers"
- : mode === "videos" ? "Videos" : "Screenshots"
- readonly property string emptyText: loading ? "Loading " + title.toLowerCase() + "..."
- : "No " + title.toLowerCase() + " found"
- readonly property string currentThemeNamePath:
- home + "/.local/state/omarchy/current/theme.name"
-
- function normalizeStyle(value) {
- const candidate = String(value || "")
- return ["tanzaku", "hearthstone", "carousel"].indexOf(candidate) >= 0
- ? candidate : "tanzaku"
- }
-
- function resolveTargetScreen(preferred) {
- if (preferred && String(preferred.name || "") !== "") return preferred
- const focusedName = Hyprland.focusedMonitor
- ? String(Hyprland.focusedMonitor.name || "") : ""
- return bar && typeof bar.screenForName === "function"
- ? bar.screenForName(focusedName) : null
- }
-
- function cycleStyle(direction) {
- const styles = ["tanzaku", "hearthstone", "carousel"]
- const current = Math.max(0, styles.indexOf(normalizeStyle(pickerStyle)))
- const step = Number(direction) < 0 ? -1 : 1
- const next = styles[(current + step + styles.length) % styles.length]
- if (!bar || typeof bar.mutateShibumiConfig !== "function") return false
- return bar.mutateShibumiConfig(function(config) {
- if (!config.picker || typeof config.picker !== "object") config.picker = {}
- config.picker.style = next
- })
- }
-
- function openMode(nextMode, screen) {
- const candidate = String(nextMode || "")
- if (["theme", "wallpaper", "screenshots", "videos"].indexOf(candidate) < 0)
- return false
- requestSerial++
- stopForegroundWorkers()
- mode = candidate
- activeScreen = resolveTargetScreen(screen)
- filterText = ""
- selectedIndex = 0
- confirmDelete = false
- currentSelection = ""
- statusText = ""
- clearThemeMetadata()
- entries = []
- cachedRowsApplied = false
- scanComplete = false
- loading = true
- opened = true
- if (mode === "theme" || mode === "wallpaper") {
- currentProc.activeSerial = requestSerial
- currentProc.command = mode === "theme"
- ? ["cat", currentThemeNamePath]
- : ["readlink", "-f", home + "/.local/state/omarchy/current/background"]
- currentProc.running = true
- } else {
- beginLoads(requestSerial)
- }
- return true
- }
-
- function toggleMode(nextMode, screen) {
- if (opened && mode === nextMode) {
- close()
- return true
- }
- return openMode(nextMode, screen)
- }
-
- function close() {
- if (!opened) return
- requestSerial++
- opened = false
- confirmDelete = false
- filterText = ""
- stopForegroundWorkers()
- loading = false
- }
-
- function stopForegroundWorkers() {
- currentProc.running = false
- cacheProc.running = false
- scanProc.running = false
- priorityWarmProc.running = false
- warmProc.running = false
- warmDelay.stop()
- clearThemeMetadata()
- }
-
- function beginLoads(serial) {
- if (!opened || serial !== requestSerial) return
- cacheProc.activeSerial = serial
- cacheProc.command = [scriptPath, "cached", mode]
- cacheProc.running = true
- scanProc.activeSerial = serial
- scanProc.command = [scriptPath, "scan", mode, bar.omarchyPath]
- scanProc.running = true
- }
-
- function applyRows(text, fromCache, serial) {
- if (!opened || serial !== requestSerial) return
- const parsed = PickerModel.parseRows(text)
- if (fromCache && (cachedRowsApplied || parsed.length === 0)) return
- if (fromCache && entries.length > 0) return
- if (!fromCache) scanComplete = true
- if (parsed.length === 0 && fromCache) return
- entries = parsed
- cachedRowsApplied = fromCache
- selectedIndex = selectedIndexForCurrent(parsed)
- loading = fromCache ? true : false
- Qt.callLater(function() {
- if (!root.opened || serial !== root.requestSerial) return
- root.warmVisible()
- warmDelay.restart()
- })
- }
-
- function selectedIndexForCurrent(nextEntries) {
- if (mode === "theme") {
- for (let i = 0; i < nextEntries.length; i++) {
- if (String(nextEntries[i].label || "") === currentSelection) return i
- }
- return 0
- }
- return PickerModel.indexForSource(nextEntries, currentSelection)
- }
-
- function moveSelection(delta) {
- selectedIndex = PickerModel.clampIndex(selectedIndex + Number(delta),
- filteredEntries.length)
- confirmDelete = false
- warmVisible()
- }
-
- function clearThemeMetadata() {
- themeMetaDelay.stop()
- themeMetaProc.running = false
- selectedThemeAuthor = ""
- selectedThemeRepo = ""
- selectedThemePalette = []
- }
-
- function requestThemeMetadata() {
- clearThemeMetadata()
- if (mode === "theme" && selectedEntry && selectedEntry.directory)
- themeMetaDelay.restart()
- }
-
- function openSelectedThemeRepo() {
- if (!selectedThemeRepo) return false
- let url = String(selectedThemeRepo)
- .replace(/^git@github\.com:/, "https://github.com/")
- .replace(/\.git$/, "")
- if (!/^https?:\/\//.test(url)) return false
- Quickshell.execDetached(["xdg-open", url])
- return true
- }
-
- function selectIndex(index) {
- selectedIndex = PickerModel.clampIndex(index, filteredEntries.length)
- confirmDelete = false
- warmVisible()
- }
-
- function updateFilter(text) {
- filterText = String(text || "")
- selectedIndex = 0
- confirmDelete = false
- warmVisible()
- }
-
- function thumbnailUrl(entry) {
- if (!entry || !entry.thumbnailReady || !entry.thumbnailPath) return ""
- return Commons.Util.fileUrl(entry.thumbnailPath)
- }
-
- function sourcePaths(visibleOnly) {
- const result = []
- const seen = {}
- if (visibleOnly) {
- for (let distance = 0; distance <= 5; distance++) {
- const indexes = distance === 0 ? [selectedIndex]
- : [selectedIndex - distance, selectedIndex + distance]
- for (let j = 0; j < indexes.length; j++) {
- const entry = filteredEntries[indexes[j]]
- if (!entry || !entry.sourcePath || entry.thumbnailReady
- || seen[entry.sourcePath]) continue
- seen[entry.sourcePath] = true
- result.push(entry.sourcePath)
- }
- }
- return result
- }
- for (let i = 0; i < entries.length; i++) {
- const entry = entries[i]
- if (!entry || !entry.sourcePath || entry.thumbnailReady
- || seen[entry.sourcePath]) continue
- seen[entry.sourcePath] = true
- result.push(entry.sourcePath)
- }
- return result
- }
-
- function startWarm(process, sources, niceLevel) {
- if (!opened || !Array.isArray(sources) || sources.length === 0) return
- process.activeSerial = requestSerial
- process.command = [scriptPath, "warm", mode, String(niceLevel)].concat(sources)
- process.running = true
- }
-
- function warmVisible() {
- startWarm(priorityWarmProc, sourcePaths(true), 10)
- }
-
- function warmAll() {
- startWarm(warmProc, sourcePaths(false), 19)
- }
-
- function noteThumbnailReady(path, serial) {
- if (!opened || serial !== requestSerial) return
- entries = PickerModel.replaceThumbnailReady(entries, String(path || "").trim())
- }
-
- function activateSelected() {
- const entry = selectedEntry
- if (!entry || !entry.sourcePath) return false
- if (mode === "theme") {
- actionProc.command = ["env", "OMARCHY_PATH=" + bar.omarchyPath,
- "omarchy-theme-set", entry.label]
- } else if (mode === "wallpaper") {
- actionProc.command = ["omarchy-theme-bg-set", entry.sourcePath]
- } else {
- Quickshell.execDetached(["xdg-open", entry.sourcePath])
- close()
- return true
- }
- actionProc.running = true
- close()
- return true
- }
-
- function copySelected() {
- const entry = selectedEntry
- if (!mediaMode || !entry || !entry.sourcePath) return false
- copyProc.command = videoMode
- ? ["bash", "-c", "printf '%s' \"$1\" | wl-copy", "shibumi-copy", entry.sourcePath]
- : ["bash", "-c", [
- "mime=$(file -Lb --mime-type -- \"$1\") || exit 1;",
- "[[ $mime == image/* ]] || exit 1;",
- "wl-copy --type \"$mime\" < \"$1\""
- ].join(" "), "shibumi-copy", entry.sourcePath]
- statusText = "Copying..."
- copyProc.running = true
- return true
- }
-
- function requestDeleteSelected() {
- const entry = selectedEntry
- if (!mediaMode || !entry || !entry.sourcePath) return false
- if (!confirmDelete) {
- confirmDelete = true
- deleteConfirmTimeout.restart()
- return true
- }
- deleteConfirmTimeout.stop()
- confirmDelete = false
- deleteProc.command = ["bash", "-c",
- "gio trash -- \"$1\" 2>/dev/null || trash-put -- \"$1\" 2>/dev/null",
- "shibumi-trash", entry.sourcePath]
- deleteProc.running = true
- return true
- }
-
- onSelectedIndexChanged: confirmDelete = false
- onSelectedEntryChanged: requestThemeMetadata()
- onAvailableChanged: if (available) initialPrewarmDelay.restart()
- Component.onDestruction: {
- stopForegroundWorkers()
- wallpaperPrewarmProc.running = false
- themePrewarmProc.running = false
- initialPrewarmDelay.stop()
- wallpaperPrewarmDelay.stop()
- }
-
- Process {
- id: currentProc
- property int activeSerial: 0
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: {
- if (currentProc.activeSerial !== root.requestSerial || !root.opened) return
- root.currentSelection = String(text || "").trim()
- root.beginLoads(currentProc.activeSerial)
- }
- }
- onExited: {
- if (activeSerial === root.requestSerial && root.opened && !cacheProc.running
- && !scanProc.running) root.beginLoads(activeSerial)
- }
- }
-
- Process {
- id: cacheProc
- property int activeSerial: 0
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: root.applyRows(text, true, cacheProc.activeSerial)
- }
- }
-
- Process {
- id: scanProc
- property int activeSerial: 0
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: root.applyRows(text, false, scanProc.activeSerial)
- }
- onExited: function(code) {
- if (activeSerial !== root.requestSerial || !root.opened) return
- root.loading = false
- if (code !== 0) root.statusText = "Scan failed"
- }
- }
-
- Process {
- id: priorityWarmProc
- property int activeSerial: 0
- stdout: SplitParser {
- onRead: line => root.noteThumbnailReady(line, priorityWarmProc.activeSerial)
- }
- }
-
- Process {
- id: warmProc
- property int activeSerial: 0
- stdout: SplitParser {
- onRead: line => root.noteThumbnailReady(line, warmProc.activeSerial)
- }
- }
-
- Process { id: actionProc }
- Process {
- id: copyProc
- onExited: function(code) {
- root.statusText = code === 0 ? "Copied" : "Copy failed"
- }
- }
-
- Process {
- id: themeMetaProc
- property string requestedDirectory: ""
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: {
- if (!root.selectedEntry
- || String(root.selectedEntry.directory || "") !== themeMetaProc.requestedDirectory)
- return
- const parts = String(text || "").replace(/\n+$/, "").split("\t")
- root.selectedThemeAuthor = parts[0] || ""
- root.selectedThemeRepo = parts[1] || ""
- root.selectedThemePalette = parts[2] ? parts[2].split(",") : []
- }
- }
- }
-
- Process {
- id: deleteProc
- onExited: function(code) {
- if (code !== 0) {
- root.statusText = "Delete failed; file kept"
- return
- }
- root.statusText = "Moved to trash"
- if (root.opened) root.beginLoads(root.requestSerial)
- }
- }
-
- Process {
- id: wallpaperPrewarmProc
- }
-
- Process {
- id: themePrewarmProc
- }
-
- Timer {
- id: warmDelay
- interval: 450
- onTriggered: root.warmAll()
- }
-
- Timer {
- id: deleteConfirmTimeout
- interval: 3500
- onTriggered: root.confirmDelete = false
- }
-
- Timer {
- id: themeMetaDelay
- interval: 70
- onTriggered: {
- if (!root.selectedEntry || !root.selectedEntry.directory) return
- const directory = String(root.selectedEntry.directory)
- themeMetaProc.requestedDirectory = directory
- themeMetaProc.command = ["bash", "-c", [
- "d=$1; repo=''; author='';",
- "if [[ -f $d/.git/config ]]; then",
- "repo=$(sed -nE 's#^[[:space:]]*url = (.*)$#\\1#p' \"$d/.git/config\" | head -1);",
- "author=$(printf '%s' \"$repo\" | sed -nE 's#.*github\\.com[:/]+([^/]+)/.*#\\1#p'); fi;",
- "palette=$(awk -F'\"' '$2 ~ /^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/ { if (out != \"\") out=out \",\"; out=out $2; if (++n == 6) exit } END { print out }' \"$d/colors.toml\" 2>/dev/null);",
- "printf '%s\\t%s\\t%s\\n' \"$author\" \"$repo\" \"$palette\""
- ].join(" "), "shibumi-theme-meta", directory]
- themeMetaProc.running = true
- }
- }
-
- Timer {
- id: wallpaperPrewarmDelay
- interval: 120
- onTriggered: {
- if (!root.available) return
- wallpaperPrewarmProc.running = false
- wallpaperPrewarmProc.command = [root.scriptPath, "prewarm", "wallpaper",
- root.bar.omarchyPath]
- wallpaperPrewarmProc.running = true
- }
- }
-
- Timer {
- id: initialPrewarmDelay
- interval: 900
- onTriggered: {
- if (!root.available) return
- themePrewarmProc.command = [root.scriptPath, "prewarm", "theme",
- root.bar.omarchyPath]
- themePrewarmProc.running = true
- wallpaperPrewarmDelay.restart()
- }
- }
-
- FileView {
- path: root.available ? root.currentThemeNamePath : ""
- watchChanges: true
- printErrors: false
- onFileChanged: wallpaperPrewarmDelay.restart()
- }
-}
diff --git a/services/QuoteService.qml b/services/QuoteService.qml
deleted file mode 100644
index 266dd04..0000000
--- a/services/QuoteService.qml
+++ /dev/null
@@ -1,105 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import Quickshell.Io
-import "QuoteDefaults.js" as QuoteDefaults
-import "ReactorModel.js" as ReactorModel
-
-Item {
- id: root
-
- required property var bar
- readonly property bool active: bar && bar.reactorMode === 8
- readonly property string quotesPath: Quickshell.env("HOME")
- + "/.config/shibumi/quotes.txt"
- property var quotes: QuoteDefaults.values()
- property int currentIndex: -1
- property int serial: 0
- property int eventCount: 0
- property bool armed: false
-
- signal eventRaised(var event)
- signal cleared()
-
- visible: false
- width: 0
- height: 0
-
- function publishNext() {
- if (!active || quotes.length === 0) return false
- currentIndex = (currentIndex + 1) % quotes.length
- const choices = []
- for (let offset = 0; offset < quotes.length && choices.length < 64; offset++) {
- const entry = quotes[(currentIndex + offset) % quotes.length]
- const quote = ReactorModel.sanitize(entry.quote, 160)
- const author = ReactorModel.sanitize(entry.author, 36)
- if (quote) choices.push({ quote: quote, author: author })
- }
- if (choices.length === 0) return false
- const selected = choices[0]
- eventCount++
- eventRaised({
- serial: ++serial,
- timestamp: Date.now(),
- kind: "text",
- direction: 1,
- left: selected.quote,
- right: selected.author ? "-" + selected.author : "",
- choices: choices,
- profile: "quote",
- screen: "",
- count: 1,
- gain: 1
- })
- return true
- }
-
- function reloadQuotes() {
- let parsed = []
- try { parsed = ReactorModel.parseQuotes(quotesReader.text()) }
- catch (_error) {}
- if (parsed.length === 0) return false
- quotes = parsed
- currentIndex = -1
- if (armed) {
- cleared()
- cycle.interval = 250
- cycle.restart()
- }
- return true
- }
-
- function runTest(kindValue, _argumentValue) {
- const kind = String(kindValue || "").toLowerCase()
- if (kind === "clear") {
- cleared()
- return true
- }
- if (kind === "quote" || kind === "next" || kind === "text")
- return publishNext()
- return false
- }
-
- FileView {
- id: quotesReader
- path: root.quotesPath
- watchChanges: true
- printErrors: false
- onFileChanged: reload()
- onLoaded: root.reloadQuotes()
- }
-
- Timer {
- id: cycle
- interval: 450
- onTriggered: {
- root.armed = true
- root.publishNext()
- interval = 16000
- restart()
- }
- }
-
- Component.onCompleted: cycle.start()
-}
diff --git a/services/ReactorService.qml b/services/ReactorService.qml
deleted file mode 100644
index 21b032b..0000000
--- a/services/ReactorService.qml
+++ /dev/null
@@ -1,576 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import Quickshell.Hyprland
-import Quickshell.Io
-import "ReactorModel.js" as ReactorModel
-
-Item {
- id: root
-
- required property var bar
- readonly property bool active: bar && bar.reactorMode === 7
- readonly property string home: Quickshell.env("HOME")
- readonly property var workspaceService: bar ? bar.workspaceService : null
- readonly property var statusService: bar ? bar.statusService : null
- readonly property var powerService: bar ? bar.powerService : null
- readonly property var aiUsageService: bar ? bar.aiUsageService : null
- readonly property var notificationService: statusService
- ? statusService.notificationService : null
- readonly property var mediaService: bar && bar.shell
- && typeof bar.shell.firstPartyServiceFor === "function"
- ? bar.shell.firstPartyServiceFor("omarchy.media") : null
- readonly property var networkWidget: moduleItem("hancore.shibumi.network")
- readonly property var audioWidget: moduleItem("hancore.shibumi.audio")
- readonly property var centerWidget: moduleItem("hancore.shibumi.center")
- readonly property var updateWidget: centerWidget ? centerWidget.updateWidget : null
- readonly property string networkMode: networkWidget
- ? String(networkWidget.mode || "none") : ""
- readonly property bool outputMuted: audioWidget
- ? audioWidget.muted === true : false
- readonly property bool updateAvailable: updateWidget
- ? updateWidget.updateAvailable === true : false
- readonly property int notificationCount: notificationService
- && notificationService.pendingModel
- ? Number(notificationService.pendingModel.count) || 0 : 0
- readonly property string mediaTitle: mediaService
- ? String(mediaService.title || "") : ""
- readonly property string mediaArtist: mediaService
- ? String(mediaService.artist || "") : ""
- readonly property string mediaAlbum: mediaService
- ? String(mediaService.album || "") : ""
- readonly property bool batteryLow: powerService
- && powerService.hasBattery && powerService.discharging
- && powerService.percent <= 20
-
- property bool armed: false
- property int serial: 0
- property var eventHistory: []
- property double lastExternalTimestamp: 0
- property string lastThemeName: ""
- property string lastNetworkMode: ""
- property bool muteBaselined: false
- property bool dndBaselined: false
- property bool updateBaselined: false
- property int lastNotificationCount: -1
- property string lastTrackSignature: ""
- property var recentOpen: ({})
- property string activeAddress: ""
- property string pendingUrgentAddress: ""
- property string pendingUrgentClass: ""
- property string urgentAddress: ""
- property string urgentClass: ""
- property int urgentRepeats: 0
- property var warningNext: ({ offline: 0, battery: 0, urgent: 0 })
- property var quotaState: ({})
- property var warningQueue: []
-
- signal eventRaised(var event)
- signal cleared()
-
- visible: false
- width: 0
- height: 0
-
- function moduleItem(moduleName) {
- const slots = bar && Array.isArray(bar.moduleSlots) ? bar.moduleSlots : []
- for (let index = 0; index < slots.length; index++) {
- const slot = slots[index]
- if (slot && String(slot.moduleName || "") === moduleName && slot.activeItem)
- return slot.activeItem
- }
- return null
- }
-
- function publish(kind, options) {
- if (!active) return false
- const source = options || ({})
- const event = {
- serial: ++serial,
- timestamp: Date.now(),
- kind: String(kind || "text"),
- direction: Number(source.direction) < 0 ? -1 : 1,
- left: ReactorModel.sanitize(source.left, 64),
- right: ReactorModel.sanitize(source.right, 28),
- profile: ReactorModel.profile(source.profile),
- screen: String(source.screen || ""),
- count: Math.max(1, Math.min(36, Number(source.count) || 1)),
- gain: Math.max(0.1, Math.min(1, Number(source.gain) || 1))
- }
- if (event.kind === "text" && !event.left && !event.right) return false
- eventHistory = ReactorModel.boundedAppend(eventHistory, event, 8)
- eventRaised(event)
- return true
- }
-
- function pushText(left, right, profile, force, screen) {
- if (!active || (!force && statusService
- && statusService.notificationsSilenced)) return false
- return publish("text", {
- left: left,
- right: right,
- profile: profile || "long",
- screen: screen || ""
- })
- }
-
- function pushPulse(kind, direction, screen, options) {
- const source = options || ({})
- return publish(kind, {
- direction: direction,
- screen: screen || "",
- count: source.count,
- gain: source.gain,
- profile: "short"
- })
- }
-
- function clear() {
- eventHistory = []
- warningQueue = []
- warningDrain.stop()
- clearUrgent()
- serial++
- cleared()
- }
-
- function workspaceChanged() {
- if (!armed || !workspaceService) return
- const id = Number(workspaceService.focusedId) || 0
- if (id <= 0) return
- const state = workspaceService.workspaceState(id)
- pushText("WS " + id, ReactorModel.workspaceLabel(state.windowCount),
- "short", true, focusedScreenName())
- }
-
- function focusedScreenName() {
- const monitor = Hyprland.focusedMonitor
- return monitor ? String(monitor.name || "") : ""
- }
-
- function classForAddress(address) {
- const target = String(address || "")
- if (!target) return ""
- try {
- const toplevels = Hyprland.toplevels.values || []
- for (let index = 0; index < toplevels.length; index++) {
- const object = toplevels[index] ? toplevels[index].lastIpcObject : null
- const candidate = ReactorModel.parseAddress(object ? object.address : "")
- if (candidate === target)
- return String(object.class || object.initialClass || "")
- }
- } catch (_error) {}
- return ""
- }
-
- function rememberOpen(address) {
- if (!address) return
- const next = recentOpen
- const now = Date.now()
- next[address] = now
- for (const key in next) {
- if (now - next[key] > 5000) delete next[key]
- }
- recentOpen = next
- }
-
- function commitUrgent() {
- const address = pendingUrgentAddress
- let appClass = pendingUrgentClass
- pendingUrgentAddress = ""
- pendingUrgentClass = ""
- if (!armed || !active || !address || address === activeAddress) return
- const openedAt = recentOpen[address] || 0
- if (openedAt > 0 && Date.now() - openedAt < 2500) {
- const next = recentOpen
- delete next[address]
- recentOpen = next
- return
- }
- appClass = appClass || classForAddress(address)
- if (!appClass) return
- urgentAddress = address
- urgentClass = ReactorModel.sanitize(appClass, 28)
- urgentRepeats = 0
- const nextWarning = warningNext
- nextWarning.urgent = 0
- warningNext = nextWarning
- checkWarnings()
- }
-
- function clearUrgent() {
- urgentAddress = ""
- urgentClass = ""
- urgentRepeats = 0
- const next = warningNext
- next.urgent = 0
- warningNext = next
- }
-
- function handleHyprlandEvent(event) {
- if (!active || !event) return
- const name = String(event.name || "")
- const address = ReactorModel.parseAddress(event.data)
- if (name === "openwindow") {
- rememberOpen(address)
- pushPulse("win", 1, focusedScreenName())
- } else if (name === "closewindow") {
- pushPulse("win", -1, "")
- const next = recentOpen
- if (address && next[address] !== undefined) delete next[address]
- recentOpen = next
- if (address && address === urgentAddress) clearUrgent()
- if (address && address === pendingUrgentAddress) {
- pendingUrgentAddress = ""
- pendingUrgentClass = ""
- urgentDelay.stop()
- }
- } else if (name === "fullscreen") {
- pushText("FULLSCREEN", String(event.data).trim() === "1" ? "ON" : "OFF",
- "short", false, focusedScreenName())
- } else if (name === "urgent" && address) {
- pendingUrgentAddress = address
- pendingUrgentClass = classForAddress(address)
- urgentDelay.restart()
- } else if (name === "activewindowv2") {
- activeAddress = address
- if (address && address === urgentAddress) clearUrgent()
- if (address && address === pendingUrgentAddress) {
- pendingUrgentAddress = ""
- pendingUrgentClass = ""
- urgentDelay.stop()
- }
- }
- }
-
- function themeChanged() {
- themeNameReader.reload()
- themeDelay.restart()
- }
-
- function handleThemeText() {
- const name = String(themeNameReader.text() || "").trim()
- if (!lastThemeName) {
- lastThemeName = name
- return
- }
- if (!armed || !name || name === lastThemeName) return
- lastThemeName = name
- pushText(name, "THEME LOADED", "long", false, "")
- }
-
- function baselineExternal() {
- if (lastExternalTimestamp > 0) return
- let parsed = null
- try { parsed = ReactorModel.parseExternal(externalEventReader.text(), 0, Date.now()) }
- catch (_error) {}
- lastExternalTimestamp = parsed ? Math.min(parsed.timestamp, Date.now()) : Date.now()
- }
-
- function handleExternal() {
- if (lastExternalTimestamp <= 0) {
- baselineExternal()
- return
- }
- let parsed = null
- try {
- parsed = ReactorModel.parseExternal(externalEventReader.text(),
- lastExternalTimestamp, Date.now())
- } catch (_error) {}
- if (!parsed) return
- lastExternalTimestamp = parsed.timestamp
- if (parsed.left === "AI") return
- pushText(parsed.left, parsed.right, "long", false, "")
- }
-
- function announceNotification() {
- if (!armed || !notificationService || notificationCount <= lastNotificationCount
- || (statusService && statusService.notificationsSilenced)) {
- lastNotificationCount = notificationCount
- return
- }
- lastNotificationCount = notificationCount
- const model = notificationService.pendingModel
- if (!model || model.count < 1) return
- const entry = model.get(0)
- if (!entry) return
- const summary = String(entry.summary || "")
- const body = String(entry.body || "")
- const message = summary && body && body !== summary
- ? summary + " - " + body : summary || body
- pushText(message, entry.app || "NOTIFY", "long", false, "")
- }
-
- function scheduleTrack() {
- if (armed) trackDelay.restart()
- }
-
- function announceTrack() {
- if (!armed || !mediaTitle) return
- const signature = mediaTitle + "|" + mediaArtist + "|" + mediaAlbum
- if (signature === lastTrackSignature) return
- if (pushText(mediaTitle,
- mediaArtist + (mediaAlbum ? " - " + mediaAlbum : ""),
- "long", false, "")) lastTrackSignature = signature
- }
-
- function handleNetwork() {
- const current = networkMode
- if (!lastNetworkMode) {
- lastNetworkMode = current
- return
- }
- if (!armed || !current || current === lastNetworkMode) return
- if (current === "none") checkWarnings()
- else if (lastNetworkMode === "none")
- pushText("ONLINE", "NETWORK", "short", false, "")
- lastNetworkMode = current
- }
-
- function handleMute() {
- if (!muteBaselined) {
- muteBaselined = true
- return
- }
- if (armed) pushText("VOLUME", outputMuted ? "MUTED" : "UNMUTED",
- "short", false, "")
- }
-
- function handleDnd() {
- if (!dndBaselined) {
- dndBaselined = true
- return
- }
- if (armed) pushText(statusService.notificationsSilenced ? "DND ON" : "DND OFF",
- "", "short", true, "")
- }
-
- function handleUpdate() {
- if (!updateBaselined) {
- updateBaselined = true
- return
- }
- if (armed && updateAvailable)
- pushText("UPDATE READY", "OMARCHY", "long", false, "")
- }
-
- function queueWarning(left, right) {
- if (!armed || !active) return false
- for (let index = 0; index < warningQueue.length; index++) {
- if (warningQueue[index].left === left && warningQueue[index].right === right)
- return false
- }
- const next = warningQueue.slice(-4)
- next.push({ left: left, right: right })
- warningQueue = next
- if (!warningDrain.running) warningDrain.restart()
- return true
- }
-
- function checkQuotaWarnings(now) {
- if (!aiUsageService) return
- const providers = aiUsageService.providers || []
- const state = quotaState
- for (let index = 0; index < providers.length; index++) {
- const provider = providers[index]
- const id = String(provider.providerId || "ai")
- const percent = aiUsageService.usagePercent(provider)
- const previous = state[id] || ({ hot: false, next: 0 })
- if (percent < 75) previous.hot = false
- if (percent >= 80) {
- previous.hot = true
- if (now >= previous.next && queueWarning(
- String(provider.providerName || id) + " " + percent, "AI QUOTA!"))
- previous.next = now + 300000
- } else if (!previous.hot) previous.next = 0
- state[id] = previous
- }
- quotaState = state
- }
-
- function checkWarnings() {
- if (!armed || !active) return
- const now = Date.now()
- const next = warningNext
- if (networkMode === "none") {
- if (now >= next.offline && queueWarning("OFFLINE!", "NETWORK"))
- next.offline = now + 120000
- } else next.offline = 0
- if (batteryLow) {
- if (now >= next.battery && queueWarning(
- "BATTERY " + powerService.percent, "PLUG IN!"))
- next.battery = now + 120000
- } else next.battery = 0
- if (urgentAddress && urgentClass && now >= next.urgent && urgentRepeats < 3
- && queueWarning(urgentClass, "WANTS YOU!")) {
- next.urgent = now + 60000 * Math.pow(3, urgentRepeats)
- urgentRepeats++
- }
- checkQuotaWarnings(now)
- warningNext = next
- }
-
- function runTest(kindValue, argumentValue) {
- if (!active) return false
- const kind = String(kindValue || "").toLowerCase()
- const argument = String(argumentValue || "")
- const parts = argument.split("|")
- if (kind === "text" || kind === "text-long")
- return pushText(parts[0] || "THIS IS A LONG REACTOR TEST MESSAGE",
- parts.slice(1).join("|") || "SOURCE", "long", true, "")
- if (kind === "text-short")
- return pushText(parts[0] || "REACTOR", parts.slice(1).join("|") || "TEST",
- "short", true, "")
- if (kind === "warn" || kind === "text-warn")
- return pushText(parts[0] || "OFFLINE!", parts.slice(1).join("|") || "NETWORK",
- "warn", true, "")
- if (kind === "win" || kind === "win-open")
- return pushPulse("win", argument === "-1" || argument === "close" ? -1 : 1,
- focusedScreenName())
- if (kind === "win-close") return pushPulse("win", -1, focusedScreenName())
- if (kind === "monsweep" || kind === "sweep")
- return pushPulse("monsweep", argument === "-1" ? -1 : 1,
- focusedScreenName(), { count: 36, gain: 0.82 })
- if (kind === "workspace" || kind === "ws") {
- const workspace = Math.max(1, Number(argument) || 1)
- return pushText("WS " + workspace, "TEST APPS", "short", true, "")
- }
- if (kind === "clear") {
- clear()
- return true
- }
- return pushText("REACTOR TEST", kind || "EVENT", "short", true, "")
- }
-
- onNetworkModeChanged: handleNetwork()
- onOutputMutedChanged: handleMute()
- onUpdateAvailableChanged: handleUpdate()
- onNotificationCountChanged: announceNotification()
- onMediaTitleChanged: scheduleTrack()
- onMediaArtistChanged: scheduleTrack()
- onMediaAlbumChanged: scheduleTrack()
- onBatteryLowChanged: if (batteryLow) checkWarnings()
-
- Connections {
- target: Hyprland
- function onFocusedMonitorChanged() {
- if (root.armed) root.pushPulse("monsweep", 1, root.focusedScreenName(),
- { count: 36, gain: 0.82 })
- }
- function onRawEvent(event) { root.handleHyprlandEvent(event) }
- }
-
- Connections {
- target: root.workspaceService
- function onFocusedIdChanged() { root.workspaceChanged() }
- }
-
- Connections {
- target: root.statusService
- function onNotificationsSilencedChanged() { root.handleDnd() }
- function onVoxtypeStateChanged() {
- if (root.armed && root.statusService.voxtypeState === "recording")
- root.pushText("VOXTYPE", "REC", "short", false, "")
- }
- }
-
- FileView {
- id: themeNameReader
- path: root.home + "/.local/state/omarchy/current/theme.name"
- watchChanges: true
- printErrors: false
- onFileChanged: root.themeChanged()
- onLoaded: root.handleThemeText()
- }
-
- Timer {
- id: themeDelay
- interval: 180
- onTriggered: root.handleThemeText()
- }
-
- FileView {
- id: externalEventReader
- path: root.home + "/.cache/qs-reactor-event"
- watchChanges: true
- printErrors: false
- onFileChanged: reload()
- onLoaded: root.handleExternal()
- onLoadFailed: root.baselineExternal()
- }
-
- Timer {
- id: urgentDelay
- interval: 700
- onTriggered: root.commitUrgent()
- }
-
- Timer {
- id: trackDelay
- interval: 800
- onTriggered: root.announceTrack()
- }
-
- Timer {
- id: warningDrain
- interval: 1
- onTriggered: {
- if (root.warningQueue.length === 0) return
- const next = root.warningQueue.slice()
- const warning = next.shift()
- root.warningQueue = next
- root.pushText(warning.left, warning.right, "warn", true, "")
- if (next.length > 0) {
- interval = 4200
- warningDrain.restart()
- } else interval = 1
- }
- }
-
- Timer {
- interval: 30000
- repeat: true
- running: root.armed
- onTriggered: root.checkWarnings()
- }
-
- Process {
- id: pacmanTail
- property int changedPackages: 0
- running: root.active
- command: ["bash", "-c", "exec tail -n 0 -F /var/log/pacman.log 2>/dev/null"]
- stdout: SplitParser {
- onRead: function(line) {
- if (line.indexOf("transaction started") >= 0) pacmanTail.changedPackages = 0
- else if (line.indexOf("] upgraded ") >= 0
- || line.indexOf("] installed ") >= 0
- || line.indexOf("] removed ") >= 0) pacmanTail.changedPackages++
- else if (line.indexOf("transaction completed") >= 0
- && pacmanTail.changedPackages > 0) {
- const count = pacmanTail.changedPackages
- root.pushText(count + (count === 1 ? " PACKAGE" : " PACKAGES")
- + " CHANGED", "PACMAN", "long", false, "")
- pacmanTail.changedPackages = 0
- }
- }
- }
- }
-
- Timer {
- interval: 3000
- running: true
- onTriggered: {
- root.lastNetworkMode = root.networkMode
- root.lastNotificationCount = root.notificationCount
- root.lastTrackSignature = root.mediaTitle + "|" + root.mediaArtist
- + "|" + root.mediaAlbum
- root.muteBaselined = true
- root.dndBaselined = true
- root.updateBaselined = true
- root.armed = true
- root.checkWarnings()
- root.pushText("REACTOR", "ARMED", "short", true, "")
- }
- }
-}
diff --git a/services/StatusService.qml b/services/StatusService.qml
deleted file mode 100644
index 1b88ec1..0000000
--- a/services/StatusService.qml
+++ /dev/null
@@ -1,192 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import Quickshell.Io
-
-// One process-wide adapter over Quattro-owned status backends. Bar views never
-// instantiate their own pollers, so multiple monitors consume the same state.
-Item {
- id: root
-
- required property var bar
-
- readonly property var idleService: bar && bar.shell
- && typeof bar.shell.firstPartyServiceFor === "function"
- ? bar.shell.firstPartyServiceFor("omarchy.idle") : null
- readonly property var notificationService: bar && bar.shell
- && typeof bar.shell.firstPartyServiceFor === "function"
- ? bar.shell.firstPartyServiceFor("omarchy.notifications") : null
- readonly property bool stayAwake: idleService
- ? idleService.stayAwake === true : false
- readonly property bool notificationsSilenced: notificationService
- ? notificationService.doNotDisturb === true : false
-
- property string recordingPid: ""
- property int recordingElapsed: 0
- property int recordingBaseElapsed: 0
- property double recordingBaseMs: 0
- property bool recordingRefreshPending: false
- readonly property bool recording: recordingPid !== ""
-
- property string voxtypeState: "idle"
- property string voxtypeHint: ""
- property bool voxtypeAvailable: false
- readonly property bool voxtypeActive: voxtypeState === "recording"
- || voxtypeState === "transcribing"
-
- function toggleStayAwake() {
- if (!idleService || typeof idleService.setIdleEnabled !== "function") return false
- idleService.setIdleEnabled(stayAwake)
- return true
- }
-
- function toggleNotifications() {
- if (!notificationService
- || typeof notificationService.setDoNotDisturb !== "function") return false
- notificationService.setDoNotDisturb(!notificationsSilenced)
- return true
- }
-
- function stopRecording() {
- if (!recording || !bar) return false
- bar.run("omarchy-capture-screenrecording --stop-recording")
- recordingRetry.restart()
- return true
- }
-
- function openVoxtypeModel() {
- if (!bar) return false
- bar.run("omarchy-voxtype-model")
- return true
- }
-
- function openVoxtypeConfig() {
- if (!bar) return false
- bar.run("omarchy-voxtype-config")
- return true
- }
-
- function refreshRecording() {
- if (recordingProbe.running) {
- recordingRefreshPending = true
- return
- }
- recordingProbe.running = true
- }
-
- function setRecordingPid(value) {
- const next = String(value || "").trim()
- if (next === recordingPid) return
- recordingPid = next
- recordingElapsed = 0
- recordingBaseElapsed = 0
- recordingBaseMs = next ? Date.now() : 0
- if (!next) return
- recordingElapsedProbe.command = ["ps", "-o", "etimes=", "-p", next]
- recordingElapsedProbe.running = true
- }
-
- function updateRecordingElapsed() {
- if (!recording || recordingBaseMs <= 0) return
- recordingElapsed = recordingBaseElapsed
- + Math.floor((Date.now() - recordingBaseMs) / 1000)
- }
-
- function updateVoxtype(raw) {
- try {
- const data = JSON.parse(String(raw || "{}"))
- const next = String(data.alt || data.class || "idle")
- voxtypeState = next === "recording" || next === "transcribing"
- ? next : "idle"
- voxtypeHint = String(data.tooltip || "").split("\n")[0]
- voxtypeAvailable = true
- } catch (error) {
- voxtypeState = "idle"
- voxtypeHint = ""
- }
- }
-
- FileView {
- id: recordingState
- path: "/tmp/omarchy-screenrecord-filename"
- watchChanges: true
- printErrors: false
- onFileChanged: {
- reload()
- root.refreshRecording()
- }
- onLoaded: root.refreshRecording()
- onLoadFailed: root.refreshRecording()
- }
-
- Process {
- id: recordingProbe
- command: ["pgrep", "-o", "-f", "^gpu-screen-recorder"]
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: root.setRecordingPid(text)
- }
- onExited: {
- if (!root.recordingRefreshPending) return
- root.recordingRefreshPending = false
- Qt.callLater(root.refreshRecording)
- }
- }
-
- Process {
- id: recordingElapsedProbe
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: {
- const seconds = Math.max(0, parseInt(String(text || "").trim()) || 0)
- root.recordingBaseElapsed = seconds
- root.recordingElapsed = seconds
- root.recordingBaseMs = Date.now()
- }
- }
- }
-
- Timer {
- interval: 45000
- running: true
- repeat: true
- triggeredOnStart: true
- onTriggered: root.refreshRecording()
- }
-
- Timer {
- id: recordingRetry
- interval: 900
- onTriggered: root.refreshRecording()
- }
-
- Timer {
- interval: 1000
- running: root.recording
- repeat: true
- triggeredOnStart: true
- onTriggered: root.updateRecordingElapsed()
- }
-
- Process {
- id: voxtypeStatus
- command: ["bash", "-lc", "exec omarchy-voxtype-status"]
- running: true
- stdout: SplitParser {
- onRead: function(data) { root.updateVoxtype(data) }
- }
- onExited: {
- root.voxtypeAvailable = false
- root.voxtypeState = "idle"
- root.voxtypeHint = ""
- }
- }
-
- Timer {
- interval: 60000
- running: !voxtypeStatus.running
- repeat: true
- onTriggered: voxtypeStatus.running = true
- }
-}
diff --git a/services/WeatherService.qml b/services/WeatherService.qml
deleted file mode 100644
index 6f78a51..0000000
--- a/services/WeatherService.qml
+++ /dev/null
@@ -1,221 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import Quickshell.Io
-
-Item {
- id: root
-
- property string icon: "·"
- property string place: ""
- property string tempC: ""
- property string tempF: ""
- property string feelsC: ""
- property string feelsF: ""
- property string description: ""
- property string country: ""
- property string humidity: ""
- property string windKmh: ""
- property string windMph: ""
- property var forecastDays: []
- property var configuredLocation: ({
- name: "",
- latitude: null,
- longitude: null
- })
- property bool loaded: false
- property bool unavailable: false
- property bool refreshPending: false
- readonly property bool refreshing: weatherProc.running
- readonly property string locationQuery: {
- const latitude = parseFloat(String(configuredLocation.latitude))
- const longitude = parseFloat(String(configuredLocation.longitude))
- if (!isNaN(latitude) && !isNaN(longitude))
- return latitude + "," + longitude
- const name = String(configuredLocation.name || "").trim()
- return name ? encodeURIComponent(name) : ""
- }
- readonly property string requestUrl: "https://wttr.in/"
- + locationQuery + "?format=j1"
-
- function parseLocation(raw) {
- try {
- const value = JSON.parse(String(raw || ""))
- const latitude = parseFloat(value && value.latitude)
- const longitude = parseFloat(value && value.longitude)
- configuredLocation = {
- name: value && typeof value.name === "string"
- ? value.name.trim() : "",
- latitude: !isNaN(latitude) ? latitude : null,
- longitude: !isNaN(longitude) ? longitude : null
- }
- } catch (_error) {
- configuredLocation = { name: "", latitude: null, longitude: null }
- }
- }
-
- function chanceOfRain(day) {
- const hourly = day && day.hourly ? day.hourly : []
- let maximum = 0
- for (let index = 0; index < hourly.length; index++) {
- const chance = parseFloat(hourly[index].chanceofrain)
- if (!isNaN(chance) && chance > maximum) maximum = chance
- }
- return maximum
- }
-
- function forecastCode(day) {
- const hourly = day && day.hourly ? day.hourly : []
- const representative = hourly.length > 4
- ? hourly[4] : hourly.length > 0 ? hourly[0] : null
- return representative ? String(representative.weatherCode || "") : ""
- }
-
- function minutesForClock(value) {
- const match = String(value || "").match(/^(\d{1,2}):(\d{2})\s*([AP]M)$/i)
- if (!match) return -1
- let hours = parseInt(match[1]) || 0
- const minutes = parseInt(match[2]) || 0
- const suffix = match[3].toUpperCase()
- if (suffix === "PM" && hours !== 12) hours += 12
- if (suffix === "AM" && hours === 12) hours = 0
- return hours * 60 + minutes
- }
-
- function isNight(sunrise, sunset) {
- const rise = minutesForClock(sunrise)
- const set = minutesForClock(sunset)
- if (rise < 0 || set < 0) return false
- const now = new Date()
- const minutes = now.getHours() * 60 + now.getMinutes()
- return minutes < rise || minutes >= set
- }
-
- function glyphForCode(code, night) {
- const value = parseInt(code) || 0
- if (value === 113) return night ? "\ue32b" : "\ue30d"
- if (value === 116) return night ? "\ue32e" : "\ue302"
- if (value === 119 || value === 122) return "\ue33d"
- if (value === 143 || value === 248 || value === 260) return "\ue313"
- if ([176, 263, 266, 293, 296, 353].indexOf(value) >= 0)
- return night ? "\ue333" : "\ue308"
- if ([179, 227, 230, 323, 326, 368].indexOf(value) >= 0)
- return night ? "\ue327" : "\ue30a"
- if ([182, 185, 281, 284, 311, 314, 317, 320, 350, 362, 365, 374, 377]
- .indexOf(value) >= 0) return "\ue3ad"
- if ([200, 386, 389, 392, 395].indexOf(value) >= 0) return "\ue31d"
- if ([299, 302, 305, 308, 356, 359].indexOf(value) >= 0) return "\ue318"
- if ([329, 332, 335, 338, 371].indexOf(value) >= 0) return "\ue31a"
- return "\ue33d"
- }
-
- function refresh(force) {
- if (!enabled) return
- if (weatherProc.running) {
- if (force === true) refreshPending = true
- return
- }
- weatherProc.running = true
- }
-
- function parseReport(raw) {
- const text = String(raw || "").trim()
- if (!text) {
- unavailable = true
- return
- }
-
- try {
- const report = JSON.parse(text)
- const current = report.current_condition && report.current_condition[0]
- ? report.current_condition[0] : null
- const area = report.nearest_area && report.nearest_area[0]
- ? report.nearest_area[0] : null
- const astronomy = report.weather && report.weather[0]
- && report.weather[0].astronomy && report.weather[0].astronomy[0]
- ? report.weather[0].astronomy[0] : null
- if (!current) {
- unavailable = true
- return
- }
-
- icon = glyphForCode(current.weatherCode,
- isNight(astronomy ? astronomy.sunrise : "", astronomy ? astronomy.sunset : ""))
- tempC = String(current.temp_C || "")
- tempF = String(current.temp_F || "")
- feelsC = String(current.FeelsLikeC || "")
- feelsF = String(current.FeelsLikeF || "")
- description = current.weatherDesc && current.weatherDesc[0]
- ? String(current.weatherDesc[0].value || "") : ""
- place = area && area.areaName && area.areaName[0]
- ? String(area.areaName[0].value || "") : ""
- country = area && area.country && area.country[0]
- ? String(area.country[0].value || "") : ""
- humidity = String(current.humidity || "")
- windKmh = String(current.windspeedKmph || "")
- windMph = String(current.windspeedMiles || "")
-
- const days = []
- const reportDays = report.weather || []
- for (let index = 0; index < reportDays.length && index < 3; index++) {
- const day = reportDays[index]
- days.push({
- date: String(day.date || ""),
- minC: String(day.mintempC || ""),
- maxC: String(day.maxtempC || ""),
- minF: String(day.mintempF || ""),
- maxF: String(day.maxtempF || ""),
- code: forecastCode(day),
- rain: chanceOfRain(day)
- })
- }
- forecastDays = days
- loaded = true
- unavailable = false
- } catch (_error) {
- unavailable = true
- }
- }
-
- Process {
- id: weatherProc
- command: ["curl", "-fsS", "--max-time", "5", root.requestUrl]
- stdout: StdioCollector {
- waitForEnd: true
- onStreamFinished: root.parseReport(text)
- }
- onRunningChanged: {
- if (running || !root.refreshPending) return
- root.refreshPending = false
- Qt.callLater(function() { root.refresh(false) })
- }
- }
-
- FileView {
- id: locationFile
- path: Quickshell.env("HOME")
- + "/.local/state/omarchy/settings/weather.json"
- watchChanges: true
- printErrors: false
- onFileChanged: reload()
- onLoaded: root.parseLocation(text())
- onLoadFailed: root.parseLocation("")
- }
-
- Timer {
- interval: 1500
- running: root.enabled
- onTriggered: locationFile.reload()
- }
-
- onLocationQueryChanged: refresh(true)
-
- Timer {
- interval: 15 * 60 * 1000
- running: root.enabled
- repeat: true
- triggeredOnStart: true
- onTriggered: root.refresh(false)
- }
-}
diff --git a/services/WorkspaceModel.js b/services/WorkspaceModel.js
deleted file mode 100644
index 9508310..0000000
--- a/services/WorkspaceModel.js
+++ /dev/null
@@ -1,91 +0,0 @@
-.pragma library
-
-function positiveId(value) {
- var id = Number(value)
- return Number.isInteger(id) && id > 0 && id <= 9999 ? id : 0
-}
-
-function toplevelCount(workspace) {
- if (!workspace || !workspace.toplevels) return 0
- var values = workspace.toplevels.values
- return values && typeof values.length === "number"
- ? Math.max(0, values.length) : 0
-}
-
-function snapshot(source, focusedValue) {
- var values = source && typeof source.length === "number" ? source : []
- var focusedId = positiveId(focusedValue)
- var byId = {}
- for (var i = 0; i < values.length; i++) {
- var id = positiveId(values[i] ? values[i].id : 0)
- if (!id) continue
- var count = toplevelCount(values[i])
- if (!byId[id] || count > byId[id].windowCount)
- byId[id] = { id: id, windowCount: count }
- }
- if (focusedId && !byId[focusedId])
- byId[focusedId] = { id: focusedId, windowCount: 0 }
-
- var ids = Object.keys(byId).map(Number)
- ids.sort(function(left, right) { return left - right })
- var result = []
- for (var j = 0; j < ids.length; j++) {
- var entry = byId[ids[j]]
- result.push({
- id: entry.id,
- windowCount: entry.windowCount,
- focused: entry.id === focusedId,
- occupied: entry.windowCount > 0
- })
- }
- return result
-}
-
-function visibleIds(modeValue, entries, focusedValue) {
- var mode = ["10", "5", "active"].indexOf(String(modeValue || "")) !== -1
- ? String(modeValue) : "10"
- var focusedId = positiveId(focusedValue)
- var source = entries && typeof entries.length === "number" ? entries : []
- var ids = []
-
- if (mode === "active") {
- for (var i = 0; i < source.length; i++) {
- var activeId = positiveId(source[i] ? source[i].id : 0)
- if (activeId && ids.indexOf(activeId) === -1) ids.push(activeId)
- }
- if (focusedId && ids.indexOf(focusedId) === -1) ids.push(focusedId)
- ids.sort(function(left, right) { return left - right })
- return ids
- }
-
- var limit = mode === "5" ? 5 : 10
- for (var id = 1; id <= limit; id++) ids.push(id)
- // Persist modes guarantee a minimum range; they are not a visibility cap.
- // Keep every real positive Hyprland workspace reachable even when it sits
- // beyond that range, including occupied workspaces on another monitor.
- for (var entryIndex = 0; entryIndex < source.length; entryIndex++) {
- var existingId = positiveId(source[entryIndex] ? source[entryIndex].id : 0)
- if (existingId && ids.indexOf(existingId) === -1) ids.push(existingId)
- }
- if (focusedId && ids.indexOf(focusedId) === -1) ids.push(focusedId)
- ids.sort(function(left, right) { return left - right })
- return ids
-}
-
-function stateFor(idValue, entries, focusedValue) {
- var id = positiveId(idValue)
- var focusedId = positiveId(focusedValue)
- var source = entries && typeof entries.length === "number" ? entries : []
- var count = 0
- for (var i = 0; i < source.length; i++) {
- if (positiveId(source[i] ? source[i].id : 0) !== id) continue
- count = Math.max(0, Number(source[i].windowCount) || 0)
- break
- }
- return {
- id: id,
- focused: id !== 0 && id === focusedId,
- occupied: count > 0,
- windowCount: count
- }
-}
diff --git a/services/WorkspaceService.qml b/services/WorkspaceService.qml
deleted file mode 100644
index dd05762..0000000
--- a/services/WorkspaceService.qml
+++ /dev/null
@@ -1,56 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell.Hyprland
-import "WorkspaceModel.js" as WorkspaceModel
-
-Item {
- id: root
-
- property var bar: null
- property var actionAdapter: null
- property var config: ({})
- property var workspaceSource: Hyprland.workspaces.values || []
- property var focusedWorkspaceSource: Hyprland.focusedWorkspace
- readonly property int focusedId: WorkspaceModel.positiveId(
- focusedWorkspaceSource ? focusedWorkspaceSource.id : 0)
- readonly property var entries: WorkspaceModel.snapshot(
- workspaceSource, focusedId)
- readonly property string mode: config && ["10", "5", "active"].indexOf(
- String(config.mode || "")) !== -1 ? String(config.mode) : "10"
- readonly property string style: config
- && ["default", "numbers", "magic", "kanji", "rings", "aurora", "pacman"].indexOf(
- String(config.style || "")) !== -1 ? String(config.style) : "default"
- readonly property var visibleWorkspaceIds: WorkspaceModel.visibleIds(
- mode, entries, focusedId)
-
- visible: false
- width: 0
- height: 0
-
- function workspaceState(id) {
- return WorkspaceModel.stateFor(id, entries, focusedId)
- }
-
- function focusWorkspace(id) {
- return actionAdapter
- && typeof actionAdapter.focusWorkspace === "function"
- ? actionAdapter.focusWorkspace(id) : false
- }
-
- function setPreference(name, value) {
- const key = String(name || "")
- const next = String(value || "")
- if (key === "mode" && ["10", "5", "active"].indexOf(next) < 0)
- return false
- if (key === "style"
- && ["default", "numbers", "magic", "kanji", "rings", "aurora", "pacman"].indexOf(next) < 0) return false
- if (key !== "mode" && key !== "style") return false
- if (!bar || typeof bar.mutateShibumiConfig !== "function") return false
- return bar.mutateShibumiConfig(function(shibumi) {
- if (!shibumi.workspace || typeof shibumi.workspace !== "object")
- shibumi.workspace = { version: 1, mode: "10", style: "default" }
- shibumi.workspace[key] = next
- })
- }
-}
diff --git a/shared/telemetry/GpuTelemetry.qml b/shared/telemetry/GpuTelemetry.qml
index b8b5d50..948b799 100644
--- a/shared/telemetry/GpuTelemetry.qml
+++ b/shared/telemetry/GpuTelemetry.qml
@@ -7,7 +7,7 @@ Scope {
property int consumers: 0
property string helperPath: String(Qt.resolvedUrl(
- "../scripts/shibumi-gpu-probe")).replace("file://", "")
+ "scripts/shibumi-gpu-probe")).replace("file://", "")
property var devices: []
property string backend: ""
property string name: ""
diff --git a/tests/ai-plugin-regression.sh b/tests/ai-plugin-regression.sh
index 4598c82..e668700 100755
--- a/tests/ai-plugin-regression.sh
+++ b/tests/ai-plugin-regression.sh
@@ -293,14 +293,9 @@ fi
if rg -q 'CACHE_FILE|stale_last' "$repo_root/hancore.shibumi.ai/scripts/opencode-usage"; then
fail "OpenCode provider persists a usage cache"
fi
-cmp -s "$repo_root/scripts/opencode-usage" \
- "$repo_root/hancore.shibumi.ai/scripts/opencode-usage" \
- || fail "canonical and vendored OpenCode scanners drifted"
-diff -u \
- <(sed 's#\.\./scripts/opencode-usage#scripts/opencode-usage#' \
- "$repo_root/services/OpenCodeProvider.qml") \
- "$repo_root/hancore.shibumi.ai/OpenCodeProvider.qml" >/dev/null \
- || fail "canonical and vendored OpenCode providers drifted"
+rg -Fq 'Qt.resolvedUrl("scripts/opencode-usage")' \
+ "$repo_root/hancore.shibumi.ai/OpenCodeProvider.qml" \
+ || fail "OpenCode provider does not resolve its shipped scanner"
rg -q 'con\.execute\("begin"\)' \
"$repo_root/hancore.shibumi.ai/scripts/opencode-usage" \
|| fail "OpenCode aggregates do not share one SQLite read snapshot"
diff --git a/tests/ai-usage-widget-smoke.qml b/tests/ai-usage-widget-smoke.qml
deleted file mode 100644
index e6785b8..0000000
--- a/tests/ai-usage-widget-smoke.qml
+++ /dev/null
@@ -1,194 +0,0 @@
-import QtQuick
-import Quickshell
-import "widgets" as Widgets
-
-ShellRoot {
- id: root
- property int phase: 0
- property real stableProviderWidth: 0
- property var clickTargets: []
-
- function fail(message) {
- console.error("ai-usage-widget-smoke:", message)
- Qt.exit(1)
- }
-
- QtObject {
- id: claudeProvider
- property string providerId: "claude"
- property string providerName: "Claude"
- property real rateLimitPercent: 17
- property string rateLimitLabel: "Weekly"
- property string rateLimitResetAt: ""
- property real secondaryRateLimitPercent: -1
- property string secondaryRateLimitLabel: ""
- property string secondaryRateLimitResetAt: ""
- property real todayTotalTokens: 900
- property string tierLabel: "Pro"
- property string usageStatusText: "Allowed"
- property string latestModel: "claude-test"
- }
-
- QtObject {
- id: codexProvider
- property string providerId: "codex"
- property string providerName: "Codex"
- property real rateLimitPercent: 13
- property string rateLimitLabel: "Weekly"
- property string rateLimitResetAt: ""
- property real secondaryRateLimitPercent: -1
- property string secondaryRateLimitLabel: ""
- property string secondaryRateLimitResetAt: ""
- property real todayTotalTokens: 1200
- property string tierLabel: "Plus"
- property string usageStatusText: "Allowed"
- property string latestModel: "gpt-test"
- }
-
- QtObject {
- id: openCodeProvider
- property string providerId: "opencode"
- property string providerName: "OpenCode"
- property real rateLimitPercent: 22
- property string rateLimitLabel: "5h soft cap"
- property string rateLimitResetAt: ""
- property real secondaryRateLimitPercent: 8
- property string secondaryRateLimitLabel: "7d soft cap"
- property string secondaryRateLimitResetAt: ""
- property real todayTotalTokens: 400
- property string tierLabel: "Local messages"
- property string usageStatusText: "Local activity"
- property string latestModel: "local-test"
- }
-
- QtObject {
- id: sharedAi
- property var providers: [claudeProvider, codexProvider, openCodeProvider]
- property string selectedTool: "codex"
- readonly property var selectedProvider: selectedTool === "opencode"
- ? openCodeProvider : selectedTool === "claude"
- ? claudeProvider : codexProvider
- property bool refreshing: false
- property int refreshCount: 0
- property int cycleCount: 0
- function usagePercent(provider) { return Math.round(provider.rateLimitPercent) }
- function tooltipText() { return "Codex\nWeekly: 13%\n\nOpenCode\n5h: 22%" }
- function refreshAll(_force) { refreshCount++ }
- function cycleTool(direction) {
- cycleCount++
- selectedTool = direction < 0 ? "codex" : "opencode"
- return true
- }
- function selectTool(tool) { selectedTool = tool; return true }
- function resetText(_provider, _timestamp) { return "" }
- function formatTokens(value) { return String(value) }
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 35
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#eeeeee"
- property color barForeground: foreground
- property color background: "#111111"
- property color urgent: "#dd7788"
- property bool foregroundAnimationEnabled: false
- property var aiUsageService: sharedAi
- property var clickTargets: root.clickTargets
- property var visualTokens: ({
- slotHeight: 28,
- pillHeight: 24,
- pillRadius: 12,
- pillPaddingX: 9,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- compactGap: 5,
- labelSize: 12
- })
- function registerClickTarget(target) {
- if (root.clickTargets.indexOf(target) < 0)
- root.clickTargets = root.clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- root.clickTargets = root.clickTargets.filter(item => item !== target)
- }
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- function targetBelongsToWindow(_target, _window) { return false }
- property var activePopout: null
- }
-
- Widgets.AiUsageWidget {
- id: aiWidget
- bar: fakeBar
- aiServiceOverride: sharedAi
- panelSource: Qt.resolvedUrl("AiTestPanel.qml")
- }
-
- Timer {
- interval: 100
- repeat: true
- running: true
- onTriggered: {
- if (root.phase === 0) {
- if (!aiWidget.visible || aiWidget.providerId !== "codex"
- || aiWidget.usagePercent !== 13 || aiWidget.implicitWidth <= 0
- || aiWidget.providerIconSlotWidth !== 20
- || aiWidget.providerIconSlotHeight !== 16
- || aiWidget.claudeGlyphPixelSize !== 15
- || aiWidget.providerGlyphWidth !== 14
- || aiWidget.providerGlyphHeight !== 14
- || aiWidget.providerGlyphHorizontalOffset !== 0
- || aiWidget.providerContentHorizontalOffset !== -1
- || aiWidget.providerGlyphHorizontalOffset
- + aiWidget.providerContentHorizontalOffset !== -1
- || aiWidget.childPanelWidget("omarchy.agents") !== aiWidget
- || aiWidget.childPanelWidget("omarchy.model-usage") !== aiWidget)
- return root.fail("single-provider facade")
- root.stableProviderWidth = aiWidget.implicitWidth
- sharedAi.cycleTool(1)
- sharedAi.refreshAll(true)
- aiWidget.open()
- } else if (root.phase === 1) {
- if (aiWidget.providerId !== "opencode" || sharedAi.cycleCount !== 1
- || sharedAi.refreshCount !== 1 || !aiWidget.opened
- || !aiWidget.panelLoaded || !aiWidget.panelItem.ready
- || aiWidget.implicitWidth !== root.stableProviderWidth
- || aiWidget.providerGlyphWidth !== 20
- || aiWidget.providerGlyphHeight !== 12
- || aiWidget.providerGlyphHorizontalOffset !== 0
- || aiWidget.providerContentHorizontalOffset !== 0
- || aiWidget.providerGlyphHorizontalOffset
- + aiWidget.providerContentHorizontalOffset !== 0)
- return root.fail("provider switch/panel lifecycle")
- sharedAi.selectTool("claude")
- } else if (root.phase === 2) {
- if (aiWidget.providerId !== "claude"
- || aiWidget.implicitWidth !== root.stableProviderWidth
- || aiWidget.providerGlyphWidth !== 15
- || aiWidget.providerGlyphHeight !== 15
- || aiWidget.providerGlyphHorizontalOffset !== 0
- || aiWidget.providerContentHorizontalOffset !== 0
- || aiWidget.providerGlyphHorizontalOffset
- + aiWidget.providerContentHorizontalOffset !== 0)
- return root.fail("stable provider icon slot")
- aiWidget.close()
- } else {
- if (aiWidget.opened || aiWidget.panelLoaded)
- return root.fail("panel cleanup")
- stop()
- console.log("ai usage widget smoke passed")
- Qt.quit()
- }
- root.phase++
- }
- }
-}
diff --git a/tests/audio-media-plugins-regression.sh b/tests/audio-media-plugins-regression.sh
index e0afa82..4edfa0c 100755
--- a/tests/audio-media-plugins-regression.sh
+++ b/tests/audio-media-plugins-regression.sh
@@ -109,25 +109,21 @@ if rg -q 'Quickshell\.Services\.(Pipewire|Mpris)|MprisPlayer' \
fi
audio_widget="$repo_root/hancore.shibumi.audio/BarWidget.qml"
-embedded_audio_widget="$repo_root/widgets/AudioWidget.qml"
audio_panel="$repo_root/hancore.shibumi.audio/AudioPanel.qml"
audio_bridge="$repo_root/hancore.shibumi.audio/AudioPanelBridge.qml"
-for volume_widget in "$audio_widget" "$embedded_audio_widget"; do
- full_content=$(sed -n \
- '/id: fullHorizontalContent/,/id: compactHorizontalContent/p' \
- "$volume_widget")
- compact_content=$(sed -n \
- '/id: compactHorizontalContent/,/id: verticalContent/p' \
- "$volume_widget")
- grep -Fq 'horizontalAlignment: Text.AlignRight' <<<"$full_content" \
- || fail "full audio value does not preserve its fixed right edge"
- grep -Fq 'horizontalAlignment: Text.AlignLeft' <<<"$compact_content" \
- || fail "compact audio value does not keep a fixed icon gap"
- if grep -Fq 'horizontalAlignment: Text.AlignRight' \
- <<<"$compact_content"; then
- fail "compact audio value still shifts inside its fixed-width field"
- fi
-done
+full_content=$(sed -n \
+ '/id: fullHorizontalContent/,/id: compactHorizontalContent/p' \
+ "$audio_widget")
+compact_content=$(sed -n \
+ '/id: compactHorizontalContent/,/id: verticalContent/p' \
+ "$audio_widget")
+grep -Fq 'horizontalAlignment: Text.AlignRight' <<<"$full_content" \
+ || fail "full audio value does not preserve its fixed right edge"
+grep -Fq 'horizontalAlignment: Text.AlignLeft' <<<"$compact_content" \
+ || fail "compact audio value does not keep a fixed icon gap"
+if grep -Fq 'horizontalAlignment: Text.AlignRight' <<<"$compact_content"; then
+ fail "compact audio value still shifts inside its fixed-width field"
+fi
rg -q 'registeredWidgetSource' "$audio_widget" \
|| fail "audio bridge does not resolve the official Omarchy source"
rg -q 'registeredWidgetComponent' "$audio_widget" \
@@ -234,12 +230,9 @@ fi
rg -q 'readonly property bool spectrumRequested: open && active && spectrumEnabled' \
"$repo_root/hancore.shibumi.media/MediaPanel.qml" \
|| fail "media spectrum work is not panel-lifecycle bounded"
-for media_panel in \
- "$repo_root/hancore.shibumi.media/MediaPanel.qml" \
- "$repo_root/widgets/MediaPanel.qml"; do
- rg -q 'property Timer positionTimer: Timer \{' "$media_panel" \
- || fail "$(basename "$media_panel") assigns a non-visual Timer to ShibumiPanel content"
-done
+rg -q 'property Timer positionTimer: Timer \{' \
+ "$repo_root/hancore.shibumi.media/MediaPanel.qml" \
+ || fail "MediaPanel.qml assigns a non-visual Timer to ShibumiPanel content"
rg -q 'current/theme/cava_theme' \
"$repo_root/hancore.shibumi.media/Service.qml" \
|| fail "media spectrum does not consume the active Cava theme"
diff --git a/tests/audio-network-ipc-contract-regression.sh b/tests/audio-network-ipc-contract-regression.sh
index 2185929..a0a1a9a 100755
--- a/tests/audio-network-ipc-contract-regression.sh
+++ b/tests/audio-network-ipc-contract-regression.sh
@@ -17,8 +17,6 @@ audio_bridge="$repo_root/hancore.shibumi.audio/AudioPanelBridge.qml"
network_service="$repo_root/hancore.shibumi.network/Service.qml"
network_widget="$repo_root/hancore.shibumi.network/BarWidget.qml"
network_bridge="$repo_root/hancore.shibumi.network/NetworkPanelBridge.qml"
-embedded_audio_bridge="$repo_root/adapters/AudioPanelBridge.qml"
-embedded_network_bridge="$repo_root/adapters/NetworkPanelBridge.qml"
host_network="$OMARCHY_PATH/shell/plugins/panels/network/Panel.qml"
[[ $(rg -l 'target: "omarchy\.audio"' \
@@ -34,8 +32,7 @@ rg -Fq 'manageIpc: false' "$audio_widget" \
|| fail 'visible Audio widget can duplicate direct IPC ownership'
rg -Fq 'manageIpc = false' "$audio_bridge" \
|| fail 'hidden official Audio backend can duplicate direct IPC ownership'
-for bridge in "$audio_bridge" "$network_bridge" \
- "$embedded_audio_bridge" "$embedded_network_bridge"; do
+for bridge in "$audio_bridge" "$network_bridge"; do
rg -Fq 'function suppressBackendKeyboardPanel()' "$bridge" \
|| fail "hidden official backend lacks KeyboardPanel suppression: $bridge"
rg -Fq 'typeof candidate.beginFocusPrime !== "function"' "$bridge" \
diff --git a/tests/audio-widget-smoke.qml b/tests/audio-widget-smoke.qml
deleted file mode 100644
index 34078d3..0000000
--- a/tests/audio-widget-smoke.qml
+++ /dev/null
@@ -1,213 +0,0 @@
-import QtQuick
-import Quickshell
-import "widgets" as Widgets
-import "fixtures" as Fixtures
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property int phaseTicks: 0
- property real fullWidth: 0
- property int wheelCallsBefore: 0
- property var clickTargets: []
-
- function fail(message) {
- console.error("audio-widget-smoke:", message)
- Qt.exit(1)
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 35
- property int sizeHorizontal: 35
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#eeeeee"
- property color barForeground: foreground
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var clickTargets: root.clickTargets
- property var shell: null
- property var barWidgetRegistry: null
- property var visualTokens: ({
- pillHeight: 24,
- pillRadius: 12,
- pillPaddingX: 9,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- slotHeight: 28,
- contentGap: 5,
- compactGap: 4,
- labelSize: 12,
- iconSize: 15
- })
-
- function widgetSettings(group, module) {
- return group === "G6" && module === "omarchy.audio"
- ? ({ testSetting: "retained" }) : ({})
- }
- function registerClickTarget(target) {
- if (root.clickTargets.indexOf(target) < 0)
- root.clickTargets = root.clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- root.clickTargets = root.clickTargets.filter(item => item !== target)
- }
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- function targetBelongsToWindow(_target, _window) { return true }
- }
-
- Loader {
- id: audioLoader
- active: true
- sourceComponent: Component {
- Widgets.AudioWidget {
- bar: fakeBar
- settings: ({ compact: false })
- panelComponent: audioPanelComponent
- popupSource: Qt.resolvedUrl("fixtures/AudioTestView.qml")
- }
- }
- }
-
- Component {
- id: audioPanelComponent
- Fixtures.AudioTestPanel {}
- }
-
- Widgets.AudioWidget {
- id: unavailableAudio
- bar: fakeBar
- panelComponent: null
- }
-
- Timer {
- interval: 80
- repeat: true
- running: true
- onTriggered: {
- root.phaseTicks++
- const audio = audioLoader.item
- if (root.phase === 0) {
- if (!audio || !audio.audioReady || root.phaseTicks < 3) return
- if (!audio || !audio.audioReady || audio.volume !== 42 || audio.muted
- || audio.implicitHeight !== 35 || unavailableAudio.visible)
- return root.fail("backend readiness/state/geometry")
- if (audio.compact)
- return root.fail("explicit full presentation resolved compact")
- if (!audio.audioPanel || audio.audioPanel.opacity !== 0
- || audio.audioPanel.settings.testSetting !== "retained"
- || audio.audioPanel.manageIpc
- || audio.audioPanel.opened || audio.audioPanel.openCount !== 0
- || audio.audioPanel.backendKeyboardPanelOpen
- || audio.audioPanel.backendKeyboardPanelVisible
- || audio.childPanelWidget("omarchy.audio") !== audio
- || !audio.ownsPanelWidget(audio)
- || !audio.ownsPanelWidget(audio.audioPanel))
- return root.fail("official panel bridge/routing/settings")
- if (root.clickTargets.length !== 1
- || audio.audioPanel.internalButton.registeredBar === fakeBar)
- return root.fail("duplicate official click target")
- audio.audioPanel.open()
- if (!audio.audioPanel.opened
- || audio.audioPanel.backendKeyboardPanelOpen
- || audio.audioPanel.backendKeyboardPanelVisible)
- return root.fail("hidden official KeyboardPanel became visible")
- audio.audioPanel.close()
-
- root.fullWidth = audio.implicitWidth
- audio.settings = ({ compact: true })
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 1) {
- if (root.phaseTicks < 3) return
- if (!audio.compact)
- return root.fail("compact setting did not update")
- if (audio.implicitWidth >= root.fullWidth)
- return root.fail("compact presentation did not reduce width: "
- + audio.implicitWidth + " >= " + root.fullWidth)
-
- audio.interactionTarget.triggerPress(Qt.RightButton)
- if (!audio.muted) return root.fail("right-click mute forwarding")
- root.wheelCallsBefore = audio.audioPanel.outputVolumeChanges
- audio.interactionTarget.wheelMoved(120)
- audio.interactionTarget.wheelMoved(120)
- audio.interactionTarget.wheelMoved(0)
- if (audio.volume !== 52)
- return root.fail("wheel target did not accumulate immediately")
- if (audio.audioPanel.outputVolumeChanges !== root.wheelCallsBefore)
- return root.fail("wheel burst wrote the backend before settling")
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 2) {
- if (root.phaseTicks < 3) return
- if (audio.audioPanel.outputVolumeChanges !== root.wheelCallsBefore + 1
- || Math.abs(audio.audioPanel.outputVolume - 0.52) > 0.001)
- return root.fail("wheel burst was not coalesced into one final write")
- audio.interactionTarget.triggerPress(Qt.LeftButton)
- if (!audio.opened || audio.audioPanel.opened
- || audio.audioPanel.openCount !== 1)
- return root.fail("local mixer lifecycle/official panel isolation")
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 3) {
- if (!audio.panelLoaded || !audio.panelItem) return
- if (fakeBar.activePopout !== audio
- || audio.panelItem.renderedSinkCount !== 3
- || audio.panelItem.renderedSourceCount !== 2
- || audio.panelItem.renderedStreamCount !== 2)
- return root.fail("local mixer model/popout ownership")
- if (audio.panelItem.firstSinkLabel()
- !== "SteelSeries Arctis 7 Chat"
- || audio.panelItem.secondSinkLabel()
- !== "SteelSeries Arctis 7 Game")
- return root.fail("description-first output device labels")
- if (!audio.panelItem.selectSecondSink()
- || audio.audioPanel.defaultSinkChanges !== 1
- || audio.audioPanel.sink.id !== 2)
- return root.fail("output device forwarding")
- if (!audio.panelItem.selectSecondSource()
- || audio.audioPanel.defaultSourceChanges !== 1
- || audio.audioPanel.source.id !== 4)
- return root.fail("input device forwarding")
- if (!audio.panelItem.setFirstStreamVolume(0.75)
- || audio.audioPanel.audioStreams[0].audio.volume !== 0.75)
- return root.fail("stream volume forwarding")
- if (!audio.panelItem.toggleFirstStreamMute()
- || !audio.audioPanel.audioStreams[0].audio.muted)
- return root.fail("stream mute forwarding")
- if (!audio.panelItem.setInputVolume(0.35)
- || audio.audioPanel.inputVolumeChanges !== 1
- || Math.abs(audio.audioPanel.inputVolume - 0.35) > 0.001)
- return root.fail("input volume forwarding")
- if (!audio.panelItem.toggleInputMute() || !audio.audioPanel.inputMuted)
- return root.fail("input mute forwarding fixture")
- audio.close()
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 4) {
- if (audio.opened || audio.panelLoaded || fakeBar.activePopout !== null)
- return root.fail("lazy local mixer close")
- audioLoader.active = false
- root.phase++
- root.phaseTicks = 0
- } else {
- if (root.clickTargets.length !== 0 || fakeBar.activePopout !== null)
- return root.fail("destruction cleanup")
- stop()
- console.log("audio widget smoke passed")
- Qt.quit()
- }
- }
- }
-}
diff --git a/tests/bluetooth-plugin-regression.sh b/tests/bluetooth-plugin-regression.sh
index 0adee13..4a1533d 100755
--- a/tests/bluetooth-plugin-regression.sh
+++ b/tests/bluetooth-plugin-regression.sh
@@ -149,12 +149,7 @@ rg -Fq 'if (discovering && !discoveryOwned) return true' "$adapter" \
rg -q 'property var discoveryOwnerAdapter: null' "$adapter" \
|| fail "Bluetooth discovery ownership is not tied to its adapter instance"
[[ -f $model ]] || fail "Bluetooth native model is missing"
-cmp -s "$repo_root/adapters/BluetoothBackendAdapter.qml" "$adapter" \
- || fail "root and plugin Bluetooth adapters drifted"
-cmp -s "$repo_root/adapters/BluetoothModel.js" "$model" \
- || fail "root and plugin Bluetooth models drifted"
-cmp -s "$repo_root/adapters/BluetoothDiscoveryGuard.qml" "$discovery_guard" \
- || fail "root and plugin Bluetooth discovery guards drifted"
+[[ -f $discovery_guard ]] || fail "Bluetooth discovery guard is missing"
if rg -q 'IpcHandler \{' "$adapter"; then
fail "Bluetooth backend adapter must not register a second IPC owner"
fi
diff --git a/tests/bluetooth-widget-smoke.qml b/tests/bluetooth-widget-smoke.qml
deleted file mode 100644
index 7c36759..0000000
--- a/tests/bluetooth-widget-smoke.qml
+++ /dev/null
@@ -1,307 +0,0 @@
-import QtQuick
-import Quickshell
-import "services" as Services
-import "widgets" as Widgets
-import "adapters" as Adapters
-import "fixtures" as Fixtures
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property int phaseTicks: 0
- property real fullWidth: 0
- property var clickTargets: []
- property int summonCount: 0
- property bool radioBeforeToggle: false
- property bool discoveryBeforeToggle: false
-
- function fail(message) {
- console.error("bluetooth-widget-smoke:", message)
- Qt.exit(1)
- }
-
- Item {
- id: fakeBar
- visible: false
- width: 0
- height: 0
- property bool vertical: false
- property int barSize: 35
- property int sizeHorizontal: 35
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#eeeeee"
- property color barForeground: foreground
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var clickTargets: root.clickTargets
- property var shell: null
- property var barWidgetRegistry: null
- property var bluetoothService: sharedBluetoothService
- property var visualTokens: ({
- pillHeight: 24,
- pillRadius: 12,
- pillPaddingX: 9,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- slotHeight: 28,
- contentGap: 5,
- compactGap: 4,
- labelSize: 12,
- iconSize: 15
- })
-
- function registeredWidgetSource(_id) { return "" }
- function registeredWidgetComponent(_id) { return null }
- function widgetSettings(group, module) {
- return group === "G15" && module === "omarchy.bluetooth"
- ? ({ testSetting: "retained" }) : ({})
- }
- function registerClickTarget(target) {
- if (root.clickTargets.indexOf(target) < 0)
- root.clickTargets = root.clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- root.clickTargets = root.clickTargets.filter(item => item !== target)
- }
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- function targetBelongsToWindow(_target, _window) { return true }
- function summonBarWidget(id) {
- if (id !== "omarchy.bluetooth") return false
- root.summonCount++
- return true
- }
- }
-
- Fixtures.BluetoothTestBackend {
- id: testBackend
- }
-
- QtObject {
- id: nativePendingDevice
- property string address: "FE:DC:BA:98:76:54"
- property string name: "Shibumi Pending Test Device"
- property string deviceName: name
- property bool connected: false
- property bool paired: true
- property bool bonded: true
- property bool trusted: true
- }
-
- Adapters.BluetoothBackendAdapter {
- id: nativeSignalAdapter
- nativeDevicesOverride: [nativePendingDevice]
- }
-
- Services.BluetoothService {
- id: sharedBluetoothService
- bar: fakeBar
- backendOverride: testBackend
- }
-
- QtObject {
- id: unavailableService
- property bool ready: false
- property bool adapterAvailable: false
- property bool radioEnabled: false
- property int connectedCount: 0
- }
-
- Loader {
- id: firstLoader
- active: true
- sourceComponent: Component {
- Widgets.BluetoothWidget {
- bar: fakeBar
- settings: ({ compact: false })
- bluetoothServiceOverride: sharedBluetoothService
- popupSource: Qt.resolvedUrl("fixtures/BluetoothTestView.qml")
- }
- }
- }
-
- Loader {
- id: secondLoader
- active: true
- sourceComponent: Component {
- Widgets.BluetoothWidget {
- bar: fakeBar
- settings: ({ compact: false })
- bluetoothServiceOverride: sharedBluetoothService
- popupSource: Qt.resolvedUrl("fixtures/BluetoothTestView.qml")
- }
- }
- }
-
- Widgets.BluetoothWidget {
- id: unavailableBluetooth
- bar: fakeBar
- bluetoothServiceOverride: unavailableService
- }
-
- Timer {
- interval: 80
- repeat: true
- running: true
- onTriggered: {
- root.phaseTicks++
- const first = firstLoader.item
- const second = secondLoader.item
- const backend = sharedBluetoothService.backend
-
- if (root.phase === 0) {
- if (!first || !second || !backend || !sharedBluetoothService.ready
- || root.phaseTicks < 3) return
- if (first.bluetoothService !== second.bluetoothService
- || first.bluetoothService !== sharedBluetoothService
- || !first.adapterAvailable || !first.radioEnabled
- || first.connectedCount !== 1 || first.implicitHeight !== 35
- || unavailableBluetooth.visible)
- return root.fail("shared backend readiness/state/geometry")
- if (backend !== testBackend)
- return root.fail("native adapter fixture boundary")
- if (first.childPanelWidget("omarchy.bluetooth") !== first
- || second.childPanelWidget("omarchy.bluetooth") !== second
- || !first.ownsPanelWidget(first))
- return root.fail("screen-local alias routing")
- if (root.clickTargets.length !== 2 || sharedBluetoothService.sessionCount !== 0)
- return root.fail("duplicate target or closed lifecycle")
-
- nativeSignalAdapter.setNativePendingAction(
- nativePendingDevice.address, "connecting")
- nativePendingDevice.connected = true
-
- root.fullWidth = first.implicitWidth
- first.settings = ({ compact: true })
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 1) {
- if (root.phaseTicks < 3) return
- if (!first.compact || first.implicitWidth >= root.fullWidth)
- return root.fail("compact presentation width")
- if (!first.showConnectedCount || first.connectedCount !== 1)
- return root.fail("connected count hidden in compact presentation")
- if (nativeSignalAdapter.pendingAction(nativePendingDevice.address) !== "")
- return root.fail("native connect transition did not clear pending state")
-
- nativeSignalAdapter.setNativePendingAction(
- nativePendingDevice.address, "disconnecting")
- nativePendingDevice.connected = false
-
- root.radioBeforeToggle = sharedBluetoothService.radioEnabled
- root.discoveryBeforeToggle = sharedBluetoothService.discovering
- first.toggleBluetooth()
- first.toggleBluetooth()
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 2) {
- if (root.phaseTicks < 4) return
- if (backend.toggleCount !== 2
- || first.radioEnabled !== root.radioBeforeToggle
- || sharedBluetoothService.discovering !== root.discoveryBeforeToggle)
- return root.fail("radio/discovery rollback after toggle round trip")
- if (nativeSignalAdapter.pendingAction(nativePendingDevice.address) !== "")
- return root.fail("native disconnect transition did not clear pending state")
-
- nativeSignalAdapter.setNativePendingAction(
- nativePendingDevice.address, "forgetting")
- nativePendingDevice.paired = false
- nativePendingDevice.bonded = false
- nativePendingDevice.trusted = false
-
- backend.fakeAdapter.discovering = true
- sharedBluetoothService.beginSession(root)
- sharedBluetoothService.restartDiscovery()
- sharedBluetoothService.endSession(root)
-
- sharedBluetoothService.openPanel()
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 3) {
- if (root.phaseTicks < 2) return
- if (nativeSignalAdapter.pendingAction(nativePendingDevice.address) !== "")
- return root.fail("native forget transition did not clear pending state")
- if (!sharedBluetoothService.discovering)
- return root.fail("external discovery refresh was claimed or stopped")
- backend.fakeAdapter.discovering = root.discoveryBeforeToggle
-
- first.interactionTarget.triggerPress(Qt.MiddleButton)
- if (root.summonCount !== 1
- || backend.toggleCount !== 2 || !first.radioEnabled || !first.opened)
- return root.fail("service IPC route, radio, or panel forwarding")
-
- second.open()
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 4) {
- if (root.phaseTicks < 3) return
- if (!first.opened || !second.opened || !first.panelLoaded
- || !second.panelLoaded || sharedBluetoothService.sessionCount !== 2
- || backend.viewLoadCount !== 2 || !sharedBluetoothService.discovering
- || fakeBar.activePopout !== second)
- return root.fail("two-output local panel sessions")
-
- sharedBluetoothService.connectDevice(backend.knownDevices[0])
- sharedBluetoothService.disconnectDevice(backend.connectedDevices[0])
- sharedBluetoothService.forgetDevice(backend.knownDevices[0])
- if (backend.connectCount !== 1 || backend.disconnectCount !== 1
- || backend.forgetCount !== 1
- || sharedBluetoothService.pendingAction("66:77:88:99:AA:BB") !== "forgetting")
- return root.fail("backend device action forwarding")
-
- sharedBluetoothService.restartDiscovery()
- first.close()
- secondLoader.active = false
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 5) {
- if (root.phaseTicks < 3) return
- if (sharedBluetoothService.sessionCount !== 0
- || sharedBluetoothService.discovering || first.panelLoaded
- || root.clickTargets.length !== 1 || fakeBar.activePopout !== null)
- return root.fail("discovery, session, or local panel teardown")
-
- backend.selectedAdapter = backend.fakeAdapter
- backend.fakeAdapter.discovering = false
- sharedBluetoothService.beginSession(root)
- backend.alternateAdapter.discovering = true
- backend.selectedAdapter = backend.alternateAdapter
- sharedBluetoothService.endSession(root)
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 6) {
- if (root.phaseTicks < 2) return
- if (backend.fakeAdapter.discovering
- || !backend.alternateAdapter.discovering
- || sharedBluetoothService.sessionCount !== 0)
- return root.fail("adapter replacement discovery ownership")
-
- backend.selectedAdapter = backend.fakeAdapter
- backend.alternateAdapter.discovering = false
- backend.adapterPresent = false
- root.phase++
- root.phaseTicks = 0
- } else {
- if (root.phaseTicks < 2) return
- if (first.visible) return root.fail("missing-adapter fallback visibility")
- firstLoader.active = false
- Qt.callLater(function() {
- if (root.clickTargets.length !== 0)
- return root.fail("click target destruction cleanup")
- console.log("bluetooth widget smoke passed")
- Qt.quit()
- })
- stop()
- }
- }
- }
-}
diff --git a/tests/brightness-widget-smoke.qml b/tests/brightness-widget-smoke.qml
deleted file mode 100644
index a5984c2..0000000
--- a/tests/brightness-widget-smoke.qml
+++ /dev/null
@@ -1,239 +0,0 @@
-import QtQuick
-import Quickshell
-import "services" as Services
-import "widgets" as Widgets
-import "fixtures" as Fixtures
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property int phaseTicks: 0
- property real fullWidth: 0
- property var clickTargets: []
- property int summonCount: 0
-
- function fail(message) {
- console.error("brightness-widget-smoke:", message)
- Qt.exit(1)
- }
-
- Item {
- id: fakeBar
- visible: false
- width: 0
- height: 0
- property bool vertical: false
- property int barSize: 35
- property int sizeHorizontal: 35
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#eeeeee"
- property color barForeground: foreground
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var clickTargets: root.clickTargets
- property var shell: null
- property var barWidgetRegistry: null
- property var monitorService: sharedMonitorService
- property var visualTokens: ({
- pillHeight: 24,
- pillRadius: 12,
- pillPaddingX: 9,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- slotHeight: 28,
- contentGap: 5,
- compactGap: 4,
- labelSize: 12,
- iconSize: 15
- })
-
- function registeredWidgetSource(_id) { return "" }
- function registeredWidgetComponent(_id) { return null }
- function widgetSettings(group, module) {
- return group === "G13" && module === "omarchy.monitor"
- ? ({ testSetting: "retained" }) : ({})
- }
- function registerClickTarget(target) {
- if (root.clickTargets.indexOf(target) < 0)
- root.clickTargets = root.clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- root.clickTargets = root.clickTargets.filter(item => item !== target)
- }
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- function targetBelongsToWindow(_target, _window) { return true }
- function summonBarWidget(id) {
- if (id !== "omarchy.monitor") return false
- root.summonCount++
- return true
- }
- }
-
- Component {
- id: monitorPanelComponent
- Fixtures.MonitorTestPanel {}
- }
-
- Services.MonitorService {
- id: sharedMonitorService
- bar: fakeBar
- panelComponent: monitorPanelComponent
- }
-
- QtObject {
- id: unavailableService
- property bool ready: false
- property bool brightnessAvailable: false
- property int brightnessPercent: 0
- property var displays: []
- }
-
- Loader {
- id: firstLoader
- active: true
- sourceComponent: Component {
- Widgets.BrightnessWidget {
- bar: fakeBar
- settings: ({ compact: false })
- monitorServiceOverride: sharedMonitorService
- popupSource: Qt.resolvedUrl("fixtures/MonitorTestView.qml")
- }
- }
- }
-
- Loader {
- id: secondLoader
- active: true
- sourceComponent: Component {
- Widgets.BrightnessWidget {
- bar: fakeBar
- settings: ({ compact: false })
- monitorServiceOverride: sharedMonitorService
- popupSource: Qt.resolvedUrl("fixtures/MonitorTestView.qml")
- }
- }
- }
-
- Widgets.BrightnessWidget {
- id: unavailableBrightness
- bar: fakeBar
- monitorServiceOverride: unavailableService
- }
-
- Timer {
- interval: 80
- repeat: true
- running: true
- onTriggered: {
- root.phaseTicks++
- const first = firstLoader.item
- const second = secondLoader.item
- const backend = sharedMonitorService.backend
-
- if (root.phase === 0) {
- if (!first || !second || !backend || !sharedMonitorService.ready
- || root.phaseTicks < 3) return
- if (first.monitorService !== second.monitorService
- || first.monitorService !== sharedMonitorService
- || !first.brightnessAvailable || first.percent !== 64
- || first.displayCount !== 2 || first.implicitHeight !== 35
- || !sharedMonitorService.textSizeAvailable
- || sharedMonitorService.textSizePx !== 12
- || sharedMonitorService.textSizeIndex !== 3
- || unavailableBrightness.visible)
- return root.fail("shared backend readiness/state/geometry")
- if (backend.opacity !== 0 || backend.manageIpc !== false
- || backend.settings.testSetting !== "retained")
- return root.fail("hidden official owner/settings")
- if (first.childPanelWidget("omarchy.monitor") !== first
- || second.childPanelWidget("omarchy.monitor") !== second
- || !first.ownsPanelWidget(first))
- return root.fail("screen-local alias routing")
- if (root.clickTargets.length !== 2)
- return root.fail("duplicate official click target")
-
- root.fullWidth = first.implicitWidth
- first.settings = ({ compact: true })
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 1) {
- if (root.phaseTicks < 3) return
- if (!first.compact || first.implicitWidth >= root.fullWidth)
- return root.fail("compact presentation width")
-
- backend.open()
- if (root.summonCount !== 1)
- return root.fail("legacy monitor IPC redirect")
- first.interactionTarget.wheelMoved(120)
- if (sharedMonitorService.brightnessPercent !== 69 || backend.setCount !== 1)
- return root.fail("wheel brightness forwarding")
- sharedMonitorService.previewBrightness(71)
- sharedMonitorService.setScale("1.6")
- sharedMonitorService.setTextSize(14)
- sharedMonitorService.adjustTextSize(1)
- sharedMonitorService.toggleDisplay("DP-1", true)
- if (backend.previewCount !== 1 || backend.scaleCount !== 1
- || backend.textSizeSetCount !== 2 || backend.textSizePx !== 16
- || backend.toggleCount !== 1 || sharedMonitorService.monitorScale !== "1.6"
- || sharedMonitorService.enabledDisplayCount !== 1)
- return root.fail("monitor action forwarding")
-
- first.interactionTarget.triggerPress(Qt.LeftButton)
- second.open()
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 2) {
- if (root.phaseTicks < 3) return
- if (!first.opened || !second.opened || !first.panelLoaded
- || !second.panelLoaded || backend.viewLoadCount !== 2
- || backend.refreshCount !== 2 || fakeBar.activePopout !== second)
- return root.fail("two-output lazy local panels: first=" + first.opened
- + " second=" + second.opened + " firstLoaded=" + first.panelLoaded
- + " secondLoaded=" + second.panelLoaded + " views="
- + backend.viewLoadCount + " refreshes=" + backend.refreshCount
- + " activeSecond=" + (fakeBar.activePopout === second))
- if (backend.opened)
- return root.fail("hidden official popup remained open")
-
- backend.brightnessAvailable = false
- backend.internalMonitor = ""
- backend.displays = [
- { name: "DP-1", enabled: true, focused: true }
- ]
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 3) {
- if (root.phaseTicks < 2) return
- if (!first.visible || first.brightnessAvailable || first.internalDisplay
- || first.tooltipText !== "Display controls" || first.implicitWidth <= 0)
- return root.fail("display controls fallback without backlight")
- first.close()
- secondLoader.active = false
- root.phase++
- root.phaseTicks = 0
- } else {
- if (first.panelLoaded || root.clickTargets.length !== 1
- || fakeBar.activePopout !== null)
- return root.fail("local panel teardown")
- firstLoader.active = false
- Qt.callLater(function() {
- if (root.clickTargets.length !== 0)
- return root.fail("click target destruction cleanup")
- console.log("brightness widget smoke passed")
- Qt.quit()
- })
- stop()
- }
- }
- }
-}
diff --git a/tests/center-model-regression.qml b/tests/center-model-regression.qml
index ac0635f..ce5b0bf 100644
--- a/tests/center-model-regression.qml
+++ b/tests/center-model-regression.qml
@@ -1,6 +1,6 @@
import QtQuick
-import "../widgets/CenterLayout.js" as CenterLayout
-import "../widgets/CalendarModel.js" as CalendarModel
+import "../hancore.shibumi.center/CenterLayout.js" as CenterLayout
+import "../hancore.shibumi.center/CalendarModel.js" as CalendarModel
import "../hancore.shibumi.center/WeatherLocationModel.js" as WeatherLocationModel
QtObject {
diff --git a/tests/center-widget-smoke.qml b/tests/center-widget-smoke.qml
deleted file mode 100644
index d9ea2a2..0000000
--- a/tests/center-widget-smoke.qml
+++ /dev/null
@@ -1,254 +0,0 @@
-import QtQuick
-import Quickshell
-import "widgets" as Widgets
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property var clickTargets: []
-
- function fail(message) {
- console.error("center-widget-smoke:", message)
- Qt.exit(1)
- }
-
- component FakeWeather: Item {
- property var bar: null
- property string moduleName: ""
- property var settings: ({})
- property bool opened: false
- implicitWidth: 44
- implicitHeight: 35
- function open() { opened = true }
- function close() { opened = false }
- function togglePanel() { opened = !opened }
- function refresh() {}
- }
-
- component FakeUpdate: Item {
- property var bar: null
- property string moduleName: ""
- property var settings: ({})
- property bool updateAvailable: true
- property int runCount: 0
- visible: updateAvailable
- implicitWidth: 24
- implicitHeight: 35
- function runUpdate() { runCount++ }
- }
-
- Component { id: weatherComponent; FakeWeather {} }
- Component { id: updateComponent; FakeUpdate {} }
-
- QtObject {
- id: registry
- property int revision: 1
- property var widgets: ({
- "omarchy.weather": { component: weatherComponent },
- "omarchy.system-update": { component: updateComponent }
- })
- }
-
- QtObject {
- id: sharedClock
- property date date: new Date(2026, 6, 16, 13, 5, 0)
- }
-
- QtObject {
- id: sharedStatus
- property bool stayAwake: true
- property bool notificationsSilenced: false
- property bool recording: false
- property int recordingElapsed: 0
- property bool voxtypeActive: false
- property string voxtypeState: "idle"
- property string voxtypeHint: ""
- property int stopCount: 0
- property int modelCount: 0
- property int configCount: 0
- function toggleStayAwake() { stayAwake = !stayAwake; return true }
- function toggleNotifications() { notificationsSilenced = !notificationsSilenced; return true }
- function stopRecording() { stopCount++; return true }
- function openVoxtypeModel() { modelCount++; return true }
- function openVoxtypeConfig() { configCount++; return true }
- }
-
- QtObject {
- id: sharedWeather
- property string icon: "W"
- property string place: "Berlin"
- property string tempC: "24"
- property string tempF: "75"
- property string description: "Clear"
- property bool loaded: true
- property bool unavailable: false
- function refresh(_force) {}
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 35
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#eeeeee"
- property color barForeground: foreground
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var barWidgetRegistry: registry
- property var clockService: sharedClock
- property var statusService: sharedStatus
- property var weatherService: sharedWeather
- property var clickTargets: root.clickTargets
- property var visualTokens: ({
- pillHeight: 24,
- pillRadius: 12,
- pillPaddingX: 9,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- labelSize: 12,
- captionSize: 10,
- mutedInk: "#999999"
- })
-
- function widgetSettings(_group, module) {
- if (module === "omarchy.weather") return ({ unit: "C" })
- if (module === "omarchy.system-update") return ({ marker: "G8-update" })
- return ({ clock12h: false })
- }
- function setWidgetSetting(_group, _module, _key, _value) { return true }
- function run(_command) {}
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function registerClickTarget(target) {
- if (root.clickTargets.indexOf(target) < 0)
- root.clickTargets = root.clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- root.clickTargets = root.clickTargets.filter(item => item !== target)
- }
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- }
-
- Widgets.CenterWidget {
- id: center
- bar: fakeBar
- availableWidth: 500
- calendarSource: Qt.resolvedUrl("CenterTestCalendar.qml")
- }
-
- Timer {
- interval: 130
- repeat: true
- running: true
- onTriggered: {
- if (root.phase === 0) {
- if (center.stage !== 0 || center.dateText !== "Thu 16"
- || !center.weatherWidget || !center.indicatorWidget || !center.updateWidget
- || center.weatherWidget.settings.unit !== "C"
- || center.weatherWidget.weatherService !== sharedWeather
- || center.weatherWidget.implicitWidth !== 20
- || center.indicatorWidget.statusService !== sharedStatus
- || center.indicatorWidget.dndOpticalCenterOffset !== 1
- || center.indicatorWidget.implicitWidth <= 0
- || center.updateWidget.settings.marker !== "G8-update"
- || center.updateWidget.moduleName !== "omarchy.system-update"
- || center.updateWidget.implicitWidth !== 20
- || center.updateWidget.opticalCenterOffset !== 1
- || center.weatherWidget.implicitWidth
- !== center.updateWidget.implicitWidth)
- return root.fail("center children/settings/presentation"
- + " stage=" + center.stage
- + " date=" + center.dateText
- + " weather=" + Boolean(center.weatherWidget)
- + " indicator=" + Boolean(center.indicatorWidget)
- + " update=" + Boolean(center.updateWidget)
- + " indicatorWidth=" + (center.indicatorWidget
- ? center.indicatorWidget.implicitWidth : -1)
- + " hasActive=" + (center.indicatorWidget
- ? center.indicatorWidget.hasActive : false)
- + " updateModule=" + (center.updateWidget
- ? center.updateWidget.moduleName : "missing"))
- if (!center.updateBackend
- || center.updateBackend.settings.marker !== "G8-update"
- || center.updateWidget.backendWidget !== center.updateBackend
- || center.updateWidget.tooltipText !== "Omarchy update available"
- || center.updateWidget.iconFamily !== "Material Symbols Rounded"
- || center.updateWidget.width <= 0
- || center.updateWidget.height <= 0
- || !center.updateWidget.interactionTarget
- || center.updateWidget.interactionTarget.width <= 0
- || center.updateWidget.interactionTarget.height <= 0
- || !center.updateWidget.activate()
- || center.updateBackend.runCount !== 1)
- return root.fail("update facade/backend contract")
- if (center.childPanelWidget("omarchy.weather") !== center.weatherWidget
- || center.childPanelWidget("omarchy.clock") !== center
- || center.childPanelWidget("omarchy.indicators") !== null)
- return root.fail("nested panel routing contract")
- center.weatherWidget.panelSource = Qt.resolvedUrl(
- "WeatherPanelTestView.qml")
- center.weatherWidget.togglePanel()
- if (!center.weatherWidget.opened)
- return root.fail("weather panel contract")
- center.open()
- } else if (root.phase === 1) {
- if (!center.weatherWidget.panelLoaded
- || center.weatherWidget.panelItem.weatherService !== sharedWeather)
- return root.fail("lazy weather panel")
- if (!center.opened || !center.calendarLoaded
- || !center.calendarLoaderReady)
- return root.fail("lazy calendar open")
- center.weatherWidget.close()
- center.close()
- center.availableWidth = 80
- } else if (root.phase === 2) {
- if (center.opened || center.calendarLoaded || center.stage !== 2)
- return root.fail("calendar close/minimal stage")
- sharedStatus.notificationsSilenced = true
- sharedStatus.recording = true
- sharedStatus.recordingElapsed = 3661
- sharedStatus.voxtypeActive = true
- sharedStatus.voxtypeState = "recording"
- center.availableWidth = 500
- } else if (root.phase === 3) {
- if (!center.indicatorWidget.hasActive
- || center.indicatorWidget.elapsedText() !== "1:01:01"
- || center.indicatorWidget.idleIconFamily !== center.bar.fontFamily
- || center.indicatorWidget.dndIconFamily !== "Material Symbols Rounded"
- || center.indicatorWidget.recordingIconFamily !== center.bar.fontFamily
- || center.indicatorWidget.recordingIconGlyph !== ""
- || center.indicatorWidget.recordingIconColor !== center.bar.urgent
- || center.indicatorWidget.voxtypeIconFamily !== "Material Symbols Rounded"
- || !center.indicatorWidget.toggleStayAwake()
- || sharedStatus.stayAwake
- || !center.indicatorWidget.toggleNotifications()
- || sharedStatus.notificationsSilenced
- || !center.indicatorWidget.stopRecording()
- || !center.indicatorWidget.activateVoxtype(Qt.LeftButton)
- || !center.indicatorWidget.activateVoxtype(Qt.RightButton)
- || sharedStatus.stopCount !== 1
- || sharedStatus.modelCount !== 1
- || sharedStatus.configCount !== 1)
- return root.fail("status indicator state/action contract")
- sharedStatus.recording = false
- sharedStatus.voxtypeActive = false
- sharedStatus.voxtypeState = "idle"
- } else {
- if (center.stage !== 0 || center.implicitHeight !== 35)
- return root.fail("normal stage restore/geometry")
- stop()
- console.log("center widget smoke passed")
- Qt.quit()
- }
- root.phase++
- }
- }
-}
diff --git a/tests/clock-widget-smoke.qml b/tests/clock-widget-smoke.qml
deleted file mode 100644
index 43af885..0000000
--- a/tests/clock-widget-smoke.qml
+++ /dev/null
@@ -1,94 +0,0 @@
-import QtQuick
-import Quickshell
-import "widgets" as Widgets
-
-ShellRoot {
- id: root
-
- property string changedGroup: ""
- property string changedModule: ""
- property string changedKey: ""
- property var changedValue: null
- property string command: ""
-
- function fail(message) {
- console.error("clock widget smoke failed: " + message)
- Qt.exit(1)
- }
-
- QtObject {
- id: sharedClock
- property date date: new Date(2026, 6, 16, 13, 5, 0)
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 35
- property string fontFamily: "monospace"
- property color foreground: "#f0f0f0"
- property color barForeground: foreground
- property color urgent: "#ff7a90"
- property bool foregroundAnimationEnabled: false
- property var clockService: sharedClock
- property var visualTokens: ({ labelSize: 12 })
-
- function setWidgetSetting(group, module, key, value) {
- root.changedGroup = group
- root.changedModule = module
- root.changedKey = key
- root.changedValue = value
- return true
- }
- function run(value) { root.command = value }
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function registerClickTarget(_target) {}
- function unregisterClickTarget(_target) {}
- }
-
- Widgets.ClockWidget {
- id: firstClock
- bar: fakeBar
- settings: ({ clock12h: false })
- }
-
- Widgets.ClockWidget {
- id: secondClock
- bar: fakeBar
- settings: ({ clock12h: false })
- }
-
- Timer {
- interval: 50
- running: true
- onTriggered: {
- if (firstClock.clock !== sharedClock || secondClock.clock !== sharedClock)
- return root.fail("widgets do not share the root clock")
- if (firstClock.timeText !== "13:05" || firstClock.implicitHeight !== 35)
- return root.fail("V1 24-hour presentation")
- if (firstClock.tooltipText.indexOf("2026") < 0)
- return root.fail("date tooltip")
- if (!firstClock.toggleFormat()
- || root.changedGroup !== "G8"
- || root.changedModule !== "omarchy.clock"
- || root.changedKey !== "clock12h"
- || root.changedValue !== true)
- return root.fail("host-owned format persistence")
-
- firstClock.settings = ({ clock12h: true })
- if (firstClock.timeText !== "1:05 PM")
- return root.fail("V1 12-hour presentation")
- if (!firstClock.openTimezoneMenu()
- || root.command !== "omarchy-menu-timezone")
- return root.fail("Quattro timezone action")
-
- fakeBar.vertical = true
- if (firstClock.implicitWidth !== 35)
- return root.fail("vertical defensive geometry")
-
- console.log("clock widget smoke passed")
- Qt.quit()
- }
- }
-}
diff --git a/tests/contract-regression.sh b/tests/contract-regression.sh
index 083bda8..82212ee 100755
--- a/tests/contract-regression.sh
+++ b/tests/contract-regression.sh
@@ -19,6 +19,7 @@ command -v python3 >/dev/null 2>&1 || fail "python3 is required"
"$repo_root/tests/baseline-contract-regression.sh"
"$repo_root/tests/documentation-regression.py"
+python3 "$repo_root/tests/production-boundary-regression.py"
python3 "$repo_root/tests/test_package_release.py"
python3 "$repo_root/tests/test_shibumi_manager.py"
python3 "$repo_root/tests/test_shibumi_suite.py"
@@ -47,6 +48,24 @@ fi
[[ ! -e manifest.json ]] \
|| fail "repository root must not masquerade as one native Omarchy plugin"
+for retired_root_copy in \
+ adapters/BluetoothBackendAdapter.qml \
+ adapters/BluetoothDiscoveryGuard.qml \
+ adapters/BluetoothModel.js \
+ adapters/WorkspaceActions.qml \
+ services/GpuTelemetry.qml \
+ services/OpenCodeProvider.qml \
+ scripts/opencode-usage \
+ scripts/shibumi-gpu-probe; do
+ [[ ! -e $retired_root_copy ]] \
+ || fail "plugin-canonical source regained a root copy: $retired_root_copy"
+done
+unexpected_root_sources=$(find adapters assets services widgets -type f 2>/dev/null \
+ | grep -Ev '^(services/(HostWidgetResolver\.qml|PickerModel\.js|PowerModel\.js|PowerService\.qml|QuoteDefaults\.js|ReactorModel\.js|SystemTelemetry\.qml|ThemePalette\.qml|ThemePaletteModel\.js|ThermalTelemetry\.qml)|widgets/(IconText\.qml|PacmanWorkspaceMarker\.qml|PillSurface\.qml|ShibumiButtonGroup\.qml|ShibumiPanel\.qml|ShibumiPanelToolTip\.qml|ShibumiSlider\.qml))$' \
+ || true)
+[[ -z $unexpected_root_sources ]] || fail \
+ "historical root implementation remains outside declared vendoring maps: $unexpected_root_sources"
+
jq -e '
.schemaVersion == 1 and
.id == "hancore.shibumi.bar" and
@@ -55,7 +74,7 @@ jq -e '
' hancore.shibumi.bar/manifest.json >/dev/null \
|| fail "invalid Shibumi bar manifest"
-[[ -s assets/logo-tint.frag.qsb ]] \
+[[ -s hancore.shibumi.control-center/assets/logo-tint.frag.qsb ]] \
|| fail "G1 exact flat-tint shader is missing"
[[ ! -e hancore.shibumi.menu && ! -e menu ]] \
|| fail "retired Shibumi App Menu source is still present"
@@ -160,14 +179,16 @@ if rg -q 'Services\.(SystemTelemetry|GpuTelemetry|PowerService|StatusService|Wea
fi
rg -q 'Qt\.createComponent\(url, Component\.PreferSynchronous\)' services/HostWidgetResolver.qml \
|| fail "host widget resolver does not load official manifest entry points"
-[[ $(rg -l 'bar\.registeredWidgetComponent' widgets/{Audio,Status,Center}Widget.qml | wc -l) -eq 3 ]] \
+[[ $(rg -l 'bar\.registeredWidgetComponent' \
+ hancore.shibumi.{audio,status,center}/BarWidget.qml | wc -l) -eq 3 ]] \
|| fail "host-backed composites bypass the stable widget resolver"
-rg -q 'registeredWidgetComponent\("omarchy\.network"\)' services/NetworkService.qml \
- || fail "root network owner bypasses the stable widget resolver"
-rg -q 'registeredWidgetComponent\("omarchy\.monitor"\)' services/MonitorService.qml \
- || fail "root monitor owner bypasses the stable widget resolver"
-if rg -q 'registeredWidgetComponent\("omarchy\.bluetooth"\)' services/BluetoothService.qml; then
- fail "root Bluetooth owner must not instantiate the complete host widget"
+rg -q 'registeredComponent\("omarchy\.network"\)' hancore.shibumi.network/Service.qml \
+ || fail "shipped network owner bypasses the stable widget resolver"
+rg -q 'registeredComponent\("omarchy\.monitor"\)' hancore.shibumi.brightness/Service.qml \
+ || fail "shipped monitor owner bypasses the stable widget resolver"
+if rg -q 'registeredWidgetComponent\("omarchy\.bluetooth"\)' \
+ hancore.shibumi.bluetooth/Service.qml; then
+ fail "shipped Bluetooth owner must not instantiate the complete host widget"
fi
if rg -U -q 'visible: root\.anchorIndex < 0\n[[:space:]]*bar: root\.bar\n[[:space:]]*region: "center"\n[[:space:]]*entries: root\.entries' core/CenterSection.qml; then
fail "inactive center fallback must not instantiate duplicate widgets"
@@ -383,7 +404,7 @@ rg -q 'screenName: root\.screenName' core/GroupSlot.qml core/BarSection.qml \
rg -Fq 'screenName: root ? root.screenName : ""' \
styles/shibumi/GroupSection.qml \
|| fail "group renderer does not propagate guarded output identity"
-rg -q 'function childPanelWidget\(pluginId\)' widgets/CenterWidget.qml \
+rg -q 'function childPanelWidget\(pluginId\)' hancore.shibumi.center/BarWidget.qml \
|| fail "center composite does not expose nested weather routing"
rg -q 'G3: \["hancore.shibumi.status"\]' core/GroupRegistry.js \
|| fail "G3 is not owned by the Shibumi status composite"
@@ -393,125 +414,125 @@ for consumed_alias in omarchy.audio omarchy.clock omarchy.network omarchy.power;
done
rg -Fq 'entry.shibumiModule === true' core/GroupRegistry.js \
|| fail "explicit Quattro modules cannot opt into the Shibumi bar"
-rg -q 'hancore\.shibumi\.status' widgets/WidgetRegistry.qml \
+rg -q 'hancore\.shibumi\.status' contracts/plugin-suite-v1.json \
|| fail "Shibumi status composite is not registered"
-rg -q 'hancore\.shibumi\.update-center' widgets/StatusWidget.qml \
+rg -q 'hancore\.shibumi\.update-center' hancore.shibumi.status/BarWidget.qml \
|| fail "G3 status presentation does not own the Shibumi update center"
-if rg -q 'omarchy\.system-update' widgets/StatusWidget.qml; then
+if rg -q 'omarchy\.system-update' hancore.shibumi.status/BarWidget.qml; then
fail "G3 status presentation must not own the Omarchy update widget"
fi
-rg -q 'omarchy\.system-update' widgets/CenterWidget.qml \
+rg -q 'omarchy\.system-update' hancore.shibumi.center/BarWidget.qml \
|| fail "G8 center presentation does not own the Omarchy update widget"
-rg -q 'StatusIndicators' widgets/CenterWidget.qml \
+rg -q 'StatusIndicators' hancore.shibumi.center/BarWidget.qml \
|| fail "G8 does not render the V1 status indicator facade"
-rg -q 'SystemUpdateWidget' widgets/CenterWidget.qml \
+rg -q 'SystemUpdateWidget' hancore.shibumi.center/BarWidget.qml \
|| fail "G8 does not render the V1 Omarchy-update facade"
-rg -q '^ width: implicitWidth$' widgets/SystemUpdateWidget.qml \
+rg -q '^ width: implicitWidth$' hancore.shibumi.center/SystemUpdateWidget.qml \
|| fail "G8 update indicator does not expose its visual width as a hit target"
-rg -q '^ height: implicitHeight$' widgets/SystemUpdateWidget.qml \
+rg -q '^ height: implicitHeight$' hancore.shibumi.center/SystemUpdateWidget.qml \
|| fail "G8 update indicator does not expose its visual height as a hit target"
-if rg -q 'Process \{|Timer \{|IpcHandler \{' widgets/SystemUpdateWidget.qml; then
+if rg -q 'Process \{|Timer \{|IpcHandler \{' hancore.shibumi.center/SystemUpdateWidget.qml; then
fail "Omarchy-update facade duplicates the official backend owner"
fi
-rg -q 'onTabRequested' widgets/CalendarPanel.qml \
+rg -q 'onTabRequested' hancore.shibumi.center/CalendarPanel.qml \
|| fail "G8 calendar is missing sibling-panel keyboard routing"
-rg -q 'ShibumiPanelToolTip \{' widgets/CalendarPanel.qml \
+rg -q 'ShibumiPanelToolTip \{' hancore.shibumi.center/CalendarPanel.qml \
|| fail "G8 calendar navigation bypasses the Shibumi tooltip"
-if rg -q 'registeredSource\("omarchy\.indicators"\)' widgets/CenterWidget.qml; then
+if rg -q 'registeredSource\("omarchy\.indicators"\)' hancore.shibumi.center/BarWidget.qml; then
fail "G8 must not instantiate Quattro's stock indicator presentation"
fi
rg -q '"service": "Service.qml"' hancore.shibumi.status/manifest.json \
|| fail "status backend is not process-wide"
rg -q 'WeatherService \{' hancore.shibumi.center/Service.qml \
|| fail "weather backend is not process-wide"
-rg -q 'WeatherWidget' widgets/CenterWidget.qml \
+rg -q 'WeatherWidget' hancore.shibumi.center/BarWidget.qml \
|| fail "G8 does not render the V1 weather facade"
if rg -q 'SystemTray\.items|NotificationServer|Process \{|FileView \{' \
- widgets/StatusWidget.qml widgets/TrayStatusView.qml \
- widgets/NotificationStatusView.qml widgets/TrayDrawerPanel.qml; then
+ hancore.shibumi.status/BarWidget.qml hancore.shibumi.status/TrayStatusView.qml \
+ hancore.shibumi.status/NotificationStatusView.qml hancore.shibumi.status/TrayDrawerPanel.qml; then
fail "status composite must not duplicate official service or polling ownership"
fi
-[[ $(rg -c 'Timer \{' widgets/StatusWidget.qml) -eq 1 ]] \
+[[ $(rg -c 'Timer \{' hancore.shibumi.status/BarWidget.qml) -eq 1 ]] \
|| fail "status composite must own exactly one lifecycle timer"
rg -U -q 'Timer \{\n[[:space:]]*id: childSyncTimer\n[[:space:]]*interval: 0\n' \
- widgets/StatusWidget.qml \
+ hancore.shibumi.status/BarWidget.qml \
|| fail "status lifecycle timer must be the zero-delay child loader sync"
-if rg -q 'Timer \{' widgets/TrayStatusView.qml \
- widgets/NotificationStatusView.qml widgets/TrayDrawerPanel.qml; then
+if rg -q 'Timer \{' hancore.shibumi.status/TrayStatusView.qml \
+ hancore.shibumi.status/NotificationStatusView.qml hancore.shibumi.status/TrayDrawerPanel.qml; then
fail "status child presentations must not own timers"
fi
-rg -q 'trayBackend: root\.trayWidget' widgets/StatusWidget.qml \
+rg -q 'trayBackend: root\.trayWidget' hancore.shibumi.status/BarWidget.qml \
|| fail "G3 tray presentation is not bound to the official tray owner"
-rg -q 'notificationService: root\.notificationService' widgets/StatusWidget.qml \
+rg -q 'notificationService: root\.notificationService' hancore.shibumi.status/BarWidget.qml \
|| fail "G3 notification presentation is not bound to the official service"
rg -q 'G6: \["hancore.shibumi.audio"\]' core/GroupRegistry.js \
|| fail "G6 is not owned by the Shibumi audio composite"
-rg -q 'hancore\.shibumi\.audio' widgets/WidgetRegistry.qml \
+rg -q 'hancore\.shibumi\.audio' contracts/plugin-suite-v1.json \
|| fail "Shibumi audio composite is not registered"
-rg -q 'Adapters\.AudioPanelBridge' widgets/AudioWidget.qml \
+rg -q 'AudioPanelBridge' hancore.shibumi.audio/BarWidget.qml \
|| fail "audio view does not preserve the official panel owner"
-rg -q 'popupSource: Qt\.resolvedUrl\("AudioPanel\.qml"\)' widgets/AudioWidget.qml \
+rg -q 'popupSource: Qt\.resolvedUrl\("AudioPanel\.qml"\)' hancore.shibumi.audio/BarWidget.qml \
|| fail "audio widget does not lazy-load the Shibumi mixer panel"
rg -q 'return String\(pluginId \|\| ""\) === "omarchy\.audio" \? root : null' \
- widgets/AudioWidget.qml \
+ hancore.shibumi.audio/BarWidget.qml \
|| fail "official audio routing is not redirected to the Shibumi owner"
-rg -q 'manageIpc: false' widgets/AudioWidget.qml \
+rg -q 'manageIpc: false' hancore.shibumi.audio/BarWidget.qml \
|| fail "audio aliases must use screen-aware host routing, not duplicate IPC handlers"
-if rg -q 'Quickshell\.Services\.Pipewire|Pipewire\.' widgets/AudioWidget.qml \
- adapters/AudioPanelBridge.qml; then
+if rg -q 'Quickshell\.Services\.Pipewire|Pipewire\.' hancore.shibumi.audio/BarWidget.qml \
+ hancore.shibumi.audio/AudioPanelBridge.qml; then
fail "Shibumi audio presentation must not create a second PipeWire owner"
fi
-if rg -q 'Process \{|FileView \{' widgets/AudioWidget.qml \
- adapters/AudioPanelBridge.qml; then
+if rg -q 'Process \{|FileView \{' hancore.shibumi.audio/BarWidget.qml \
+ hancore.shibumi.audio/AudioPanelBridge.qml; then
fail "audio presentation bridge must remain event-driven and worker-free"
fi
-rg -q 'panelLoader\.active = false' adapters/AudioPanelBridge.qml \
+rg -q 'panelLoader\.active = false' hancore.shibumi.audio/AudioPanelBridge.qml \
|| fail "audio backend panel is not unloaded before its host facade"
-if rg -q 'panel\.bar = null' adapters/AudioPanelBridge.qml; then
+if rg -q 'panel\.bar = null' hancore.shibumi.audio/AudioPanelBridge.qml; then
fail "audio teardown invalidates the host facade before panel destruction"
fi
-[[ $(rg -c 'Timer \{' widgets/AudioWidget.qml) -eq 2 ]] \
+[[ $(rg -c 'Timer \{' hancore.shibumi.audio/BarWidget.qml) -eq 2 ]] \
|| fail "audio widget must own only the bounded wheel commit/settle timers"
rg -U -q 'Timer \{\n[[:space:]]*id: wheelCommitTimer\n[[:space:]]*interval: 70\n' \
- widgets/AudioWidget.qml \
+ hancore.shibumi.audio/BarWidget.qml \
|| fail "audio wheel commit timer contract changed"
rg -U -q 'Timer \{\n[[:space:]]*id: wheelSettleTimer\n[[:space:]]*interval: 300\n' \
- widgets/AudioWidget.qml \
+ hancore.shibumi.audio/BarWidget.qml \
|| fail "audio wheel settle timer contract changed"
if rg -U -q 'Timer \{([^}]|\n)*(repeat:[[:space:]]*true|running:[[:space:]]*true)' \
- widgets/AudioWidget.qml; then
+ hancore.shibumi.audio/BarWidget.qml; then
fail "audio wheel timers must remain dormant, non-repeating interaction timers"
fi
-[[ $(rg -c 'PwNodePeakMonitor \{' widgets/AudioPanel.qml) -eq 1 ]] \
+[[ $(rg -c 'PwNodePeakMonitor \{' hancore.shibumi.audio/AudioPanel.qml) -eq 1 ]] \
|| fail "audio panel must own exactly one lifecycle-bound microphone meter"
-rg -q 'enabled: panel\.open' widgets/AudioPanel.qml \
+rg -q 'enabled: panel\.open' hancore.shibumi.audio/AudioPanel.qml \
|| fail "microphone meter is not bounded to the open mixer lifecycle"
-if rg -q 'Pipewire\.|Process \{|FileView \{' widgets/AudioPanel.qml; then
+if rg -q 'Pipewire\.|Process \{|FileView \{' hancore.shibumi.audio/AudioPanel.qml; then
fail "Shibumi audio panel duplicates Quattro audio ownership or shell workers"
fi
-rg -q 'displaySinks = \[\]' widgets/AudioPanel.qml \
+rg -q 'displaySinks = \[\]' hancore.shibumi.audio/AudioPanel.qml \
|| fail "audio panel does not release sink rows on close"
-rg -q 'displaySources = \[\]' widgets/AudioPanel.qml \
+rg -q 'displaySources = \[\]' hancore.shibumi.audio/AudioPanel.qml \
|| fail "audio panel does not release source rows on close"
-rg -q 'displayStreams = \[\]' widgets/AudioPanel.qml \
+rg -q 'displayStreams = \[\]' hancore.shibumi.audio/AudioPanel.qml \
|| fail "audio panel does not release stream rows on close"
rg -q 'G9: \["hancore.shibumi.media"\]' core/GroupRegistry.js \
|| fail "G9 is not owned by the Shibumi media presentation"
-rg -q 'hancore\.shibumi\.media' widgets/WidgetRegistry.qml \
+rg -q 'hancore\.shibumi\.media' contracts/plugin-suite-v1.json \
|| fail "Shibumi media presentation is not registered"
-rg -q 'firstPartyServiceFor\("omarchy\.media"\)' widgets/MediaWidget.qml \
+rg -q 'firstPartyServiceFor\("omarchy\.media"\)' hancore.shibumi.media/BarWidget.qml \
|| fail "media presentation does not reuse the official service"
if rg -q 'Quickshell\.Services\.(Mpris|Pipewire)|Mpris\.|Pipewire\.' \
- widgets/MediaWidget.qml widgets/MediaPanel.qml widgets/MediaPulse.qml \
- widgets/MediaSpectrum.qml widgets/MediaMuse.qml \
+ hancore.shibumi.media/BarWidget.qml hancore.shibumi.media/MediaPanel.qml hancore.shibumi.media/MediaPulse.qml \
+ hancore.shibumi.media/MediaSpectrum.qml hancore.shibumi.media/MediaMuse.qml \
hancore.shibumi.media/Service.qml; then
fail "Shibumi media presentation must not create a second media owner"
fi
-if rg -q 'Process \{|Timer \{|FileView \{' widgets/MediaWidget.qml \
- widgets/MediaPulse.qml widgets/MediaSpectrum.qml widgets/MediaMuse.qml; then
+if rg -q 'Process \{|Timer \{|FileView \{' hancore.shibumi.media/BarWidget.qml \
+ hancore.shibumi.media/MediaPulse.qml hancore.shibumi.media/MediaSpectrum.qml hancore.shibumi.media/MediaMuse.qml; then
fail "closed media bar presentation must remain worker-free"
fi
-rg -q 'serviceFor\("hancore\.shibumi\.media"\)' widgets/MediaWidget.qml \
+rg -q 'serviceFor\("hancore\.shibumi\.media"\)' hancore.shibumi.media/BarWidget.qml \
|| fail "media presentation does not resolve the process-wide spectrum service"
[[ $(rg -c 'Process \{' hancore.shibumi.media/Service.qml) -eq 2 ]] \
|| fail "media spectrum service must own one probe and one Cava process"
@@ -526,13 +547,13 @@ rg -q 'target: "shibumi-media-spectrum"' hancore.shibumi.media/Service.qml \
|| fail "media spectrum service lacks read-only runtime diagnostics"
rg -q 'bars = 24' hancore.shibumi.media/Service.qml \
|| fail "media spectrum service does not preserve the V1 band count"
-if rg -q 'Process \{|FileView \{' widgets/MediaPanel.qml \
+if rg -q 'Process \{|FileView \{' hancore.shibumi.media/MediaPanel.qml \
hancore.shibumi.media/MediaPanel.qml; then
fail "screen-local media panels must not own spectrum workers"
fi
rg -q 'G10: \["hancore.shibumi.quick-access"\]' core/GroupRegistry.js \
|| fail "G10 is not owned by the Shibumi quick-access presentation"
-rg -q 'hancore\.shibumi\.quick-access' widgets/WidgetRegistry.qml \
+rg -q 'hancore\.shibumi\.quick-access' contracts/plugin-suite-v1.json \
|| fail "Shibumi quick-access presentation is not registered"
rg -q 'IdleInhibitor \{' hancore.shibumi.quick-access/BarWidget.qml \
|| fail "G10 idle inhibitor is not attached to the output surface"
@@ -545,9 +566,9 @@ rg -q 'Hyprland\.focusedMonitor' hancore.shibumi.quick-access/Service.qml \
|| fail "G10 IPC picker does not resolve the focused output"
rg -q 'function screenForName\(value\)' Bar.qml \
|| fail "G10 IPC picker cannot resolve host output objects"
-if rg -q 'Commons\.Style\.font\.size\.' widgets/PickerOverlay.qml \
- widgets/PickerImage.qml widgets/TanzakuPickerView.qml \
- widgets/HearthstonePickerView.qml; then
+if rg -q 'Commons\.Style\.font\.size\.' hancore.shibumi.quick-access/PickerOverlay.qml \
+ hancore.shibumi.quick-access/PickerImage.qml hancore.shibumi.quick-access/TanzakuPickerView.qml \
+ hancore.shibumi.quick-access/HearthstonePickerView.qml; then
fail "G10 picker uses V1-only font token paths"
fi
rg -q 'Commons\.Util\.fileUrl\(entry\.thumbnailPath\)' \
@@ -555,14 +576,14 @@ rg -q 'Commons\.Util\.fileUrl\(entry\.thumbnailPath\)' \
|| fail "G10 thumbnail URLs do not use the host-safe file URL helper"
rg -q 'file -Lb --mime-type' hancore.shibumi.quick-access/Service.qml \
|| fail "G10 screenshot copy hard-codes an incorrect image MIME type"
-for picker_view in widgets/TanzakuPickerView.qml \
- widgets/HearthstonePickerView.qml; do
+for picker_view in hancore.shibumi.quick-access/TanzakuPickerView.qml \
+ hancore.shibumi.quick-access/HearthstonePickerView.qml; do
if rg -q 'Process \{|Timer \{|FileView \{' "$picker_view"; then
fail "picker presentation owns controller work: $picker_view"
fi
done
rg -q 'readonly property bool spectrumRequested: open && active && spectrumEnabled' \
- widgets/MediaPanel.qml \
+ hancore.shibumi.media/MediaPanel.qml \
|| fail "media spectrum worker is not panel-lifecycle gated"
rg -q 'command: \["cava", "-p", "/dev/stdin"\]' \
hancore.shibumi.media/Service.qml \
@@ -574,85 +595,81 @@ if rg -q 'mktemp|/tmp/|<\(printf' hancore.shibumi.media/Service.qml; then
fi
rg -q 'G11: \["hancore.shibumi.network"\]' core/GroupRegistry.js \
|| fail "G11 is not owned by the Shibumi network presentation"
-rg -q 'hancore\.shibumi\.network' widgets/WidgetRegistry.qml \
+rg -q 'hancore\.shibumi\.network' contracts/plugin-suite-v1.json \
|| fail "Shibumi network presentation is not registered"
[[ $(rg -c 'NetworkPanelBridge \{' hancore.shibumi.network/Service.qml) -eq 1 ]] \
|| fail "network backend must be process-wide"
-rg -q 'Adapters\.NetworkPanelBridge' services/NetworkService.qml \
- || fail "root network service does not preserve the official panel owner"
-if rg -q 'Adapters\.NetworkPanelBridge|Quickshell\.Networking|Networking\.' \
- widgets/NetworkWidget.qml widgets/NetworkPanel.qml; then
+rg -q 'NetworkPanelBridge' hancore.shibumi.network/Service.qml \
+ || fail "process-wide network service does not preserve the official panel owner"
+if rg -q 'NetworkPanelBridge|Quickshell\.Networking|Networking\.' \
+ hancore.shibumi.network/BarWidget.qml hancore.shibumi.network/NetworkPanel.qml; then
fail "screen-local network presentation must not own NetworkManager"
fi
-if rg -q 'Process \{|FileView \{' widgets/NetworkWidget.qml \
- widgets/NetworkPanel.qml adapters/NetworkPanelBridge.qml; then
+if rg -q 'Process \{|FileView \{' hancore.shibumi.network/BarWidget.qml \
+ hancore.shibumi.network/NetworkPanel.qml hancore.shibumi.network/NetworkPanelBridge.qml; then
fail "screen-local network presentation must not own backend workers"
fi
-if rg -q 'Timer \{' widgets/NetworkWidget.qml adapters/NetworkPanelBridge.qml; then
- fail "network widget and backend bridge must remain event-driven"
-fi
-[[ $(rg -c 'Adapters\.NetworkPanelBridge \{' services/NetworkService.qml) -eq 1 ]] \
- || fail "root network service must own exactly one official backend"
-rg -q 'property var sessionOwners: \[\]' services/NetworkService.qml \
+[[ $(rg -c 'NetworkPanelBridge \{' hancore.shibumi.network/Service.qml) -eq 1 ]] \
+ || fail "process-wide network service must own exactly one official backend"
+rg -q 'property var sessionOwners: \[\]' hancore.shibumi.network/Service.qml \
|| fail "network service lacks multi-output session accounting"
-rg -q 'profileList\.running = false' services/NetworkService.qml \
+rg -q 'profileList\.running = false' hancore.shibumi.network/Service.qml \
|| fail "network service does not stop saved-profile discovery on final close"
-rg -q 'detailsProc\.running = false' services/NetworkService.qml \
+rg -q 'detailsProc\.running = false' hancore.shibumi.network/Service.qml \
|| fail "network service does not stop detail sampling on final close"
-rg -q 'property var scannerDevice: null' services/NetworkService.qml \
+rg -q 'property var scannerDevice: null' hancore.shibumi.network/Service.qml \
|| fail "network service does not track its scanner lease"
rg -q 'if \(scannerDevice && scannerDevice !== nextDevice\)' \
- services/NetworkService.qml \
+ hancore.shibumi.network/Service.qml \
|| fail "network service does not release replaced scanner devices"
-rg -q 'releaseWifiScanner\(\)' services/NetworkService.qml \
+rg -q 'releaseWifiScanner\(\)' hancore.shibumi.network/Service.qml \
|| fail "network scanner is not released outside panel sessions"
-if rg -q 'scannerEnabled' adapters/NetworkPanelBridge.qml; then
- fail "network bridge competes with the root scanner owner"
+if rg -q 'scannerEnabled' hancore.shibumi.network/NetworkPanelBridge.qml; then
+ fail "network bridge competes with the process-wide scanner owner"
fi
rg -q 'command: \["omarchy-network-status", "--verbose"\]' \
- services/NetworkService.qml \
- || fail "network panel details do not use the shared root lifecycle worker"
+ hancore.shibumi.network/Service.qml \
+ || fail "network panel details do not use the shared lifecycle worker"
if rg -U -q 'id: detailsPoll[\s\S]{0,240}root\.refresh' \
- services/NetworkService.qml; then
+ hancore.shibumi.network/Service.qml; then
fail "network details poll must not restart DNS/profile refresh work"
fi
-rg -q 'childPanelWidget\("omarchy\.network"\)' tests/network-widget-smoke.qml \
+rg -q 'childPanelWidget\("omarchy\.network"\)' tests/network-plugin-smoke.qml \
|| fail "network alias routing is not regression-tested"
rg -q 'G13: \["hancore.shibumi.brightness"\]' core/GroupRegistry.js \
|| fail "G13 is not owned by the Shibumi brightness presentation"
-rg -q 'hancore\.shibumi\.brightness' widgets/WidgetRegistry.qml \
+rg -q 'hancore\.shibumi\.brightness' contracts/plugin-suite-v1.json \
|| fail "Shibumi brightness presentation is not registered"
[[ $(rg -c 'MonitorPanelBridge \{' hancore.shibumi.brightness/Service.qml) -eq 1 ]] \
- || fail "brightness/display state must have one root monitor owner"
-[[ $(rg -c 'Adapters\.MonitorPanelBridge \{' services/MonitorService.qml) -eq 1 ]] \
- || fail "root monitor service must own exactly one official backend"
-rg -q 'bar\.monitorService' widgets/BrightnessWidget.qml \
+ || fail "brightness service must own exactly one official backend"
+rg -q 'serviceFor\("hancore\.shibumi\.brightness"\)' \
+ hancore.shibumi.brightness/BarWidget.qml \
|| fail "brightness view does not consume the shared monitor owner"
if rg -q 'Quickshell\.Services\.UPower|UPower\.|Quickshell\.Io' \
- widgets/BrightnessWidget.qml widgets/BrightnessPanel.qml \
- adapters/MonitorPanelBridge.qml services/MonitorService.qml; then
+ hancore.shibumi.brightness/BarWidget.qml hancore.shibumi.brightness/BrightnessPanel.qml \
+ hancore.shibumi.brightness/MonitorPanelBridge.qml hancore.shibumi.brightness/Service.qml; then
fail "Shibumi brightness presentation must not create a second monitor owner"
fi
-if rg -q 'Process \{|Timer \{|FileView \{' widgets/BrightnessWidget.qml \
- widgets/BrightnessPanel.qml services/MonitorService.qml \
- adapters/MonitorPanelBridge.qml; then
+if rg -q 'Process \{|Timer \{|FileView \{' hancore.shibumi.brightness/BarWidget.qml \
+ hancore.shibumi.brightness/BrightnessPanel.qml hancore.shibumi.brightness/Service.qml \
+ hancore.shibumi.brightness/MonitorPanelBridge.qml; then
fail "brightness presentation and monitor adapter must remain worker-free"
fi
-if rg -q '^[[:space:]]*selected:' widgets/BrightnessPanel.qml; then
+if rg -q '^[[:space:]]*selected:' hancore.shibumi.brightness/BrightnessPanel.qml; then
fail "brightness panel uses Button-only selected state on CursorSurface"
fi
-rg -q 'childPanelWidget\("omarchy\.monitor"\)' tests/brightness-widget-smoke.qml \
+rg -q 'childPanelWidget\("omarchy\.monitor"\)' tests/brightness-plugin-smoke.qml \
|| fail "monitor alias routing is not regression-tested"
rg -q 'G12: \["hancore.shibumi.battery"\]' core/GroupRegistry.js \
|| fail "G12 is not owned by the Shibumi battery presentation"
rg -q 'G14: \["hancore.shibumi.power-profile"\]' core/GroupRegistry.js \
|| fail "G14 is not owned by the Shibumi power-profile presentation"
for power_widget in hancore.shibumi.battery hancore.shibumi.power-profile; do
- rg -q "$power_widget" widgets/WidgetRegistry.qml \
+ rg -q "$power_widget" contracts/plugin-suite-v1.json \
|| fail "Shibumi power presentation is not registered: $power_widget"
done
rg -q '"service": "Service.qml"' hancore.shibumi.power-state/manifest.json \
- || fail "battery/profile state must have one root power owner"
+ || fail "battery/profile state must have one process-wide power owner"
rg -q 'Quickshell.Services.UPower' services/PowerService.qml \
|| fail "power owner does not consume the event-driven UPower singleton"
rg -q 'command: \["omarchy-powerprofiles-list", "--active-state"\]' \
@@ -670,47 +687,53 @@ rg -q 'omarchy-battery-status --shell' \
services/PowerService.qml \
|| fail "power owner does not use the Quattro battery detail contract"
if rg -q 'Quickshell\.Services\.UPower|Quickshell\.Io|Process \{|Timer \{|FileView \{' \
- widgets/BatteryWidget.qml widgets/BatteryPanel.qml \
- widgets/PowerProfileWidget.qml widgets/PowerProfilePanel.qml; then
+ hancore.shibumi.battery/BarWidget.qml hancore.shibumi.battery/BatteryPanel.qml \
+ hancore.shibumi.power-profile/BarWidget.qml hancore.shibumi.power-profile/PowerProfilePanel.qml; then
fail "power views must not duplicate UPower, worker, or polling ownership"
fi
-rg -q 'acquireBatteryDetails' widgets/BatteryWidget.qml \
+rg -q 'acquireBatteryDetails' hancore.shibumi.battery/BarWidget.qml \
|| fail "battery detail process is not panel-lifecycle gated"
-rg -q 'releaseBatteryDetails' widgets/BatteryWidget.qml \
+rg -q 'releaseBatteryDetails' hancore.shibumi.battery/BarWidget.qml \
|| fail "battery detail process lease is not released"
-for monitor_widget in Cpu Memory Battery; do
+for monitor_plugin in cpu memory battery; do
rg -q 'bar\.run\("omarchy-launch-or-focus-tui btop"\)' \
- "widgets/${monitor_widget}Widget.qml" \
- || fail "$monitor_widget does not use the Quattro TUI launcher"
+ "hancore.shibumi.${monitor_plugin}/BarWidget.qml" \
+ || fail "$monitor_plugin does not use the Quattro TUI launcher"
+ panel_name="$(tr '[:lower:]' '[:upper:]' \
+ <<<"${monitor_plugin:0:1}")${monitor_plugin:1}Panel.qml"
rg -q 'panel\.ownerWidget\.openSystemMonitor\(\)' \
- "widgets/${monitor_widget}Panel.qml" \
- || fail "$monitor_widget panel bypasses its owner action"
+ "hancore.shibumi.${monitor_plugin}/${panel_name}" \
+ || fail "$monitor_plugin panel bypasses its owner action"
done
-rg -q 'acquireProfiles' widgets/PowerProfileWidget.qml \
+rg -q 'acquireProfiles' hancore.shibumi.power-profile/BarWidget.qml \
|| fail "profile state does not use the shared service lease"
-rg -q 'releaseProfiles' widgets/PowerProfileWidget.qml \
+rg -q 'releaseProfiles' hancore.shibumi.power-profile/BarWidget.qml \
|| fail "profile state lease is not released"
rg -q 'G15: \["hancore.shibumi.bluetooth"\]' core/GroupRegistry.js \
|| fail "G15 is not owned by the Shibumi bluetooth presentation"
-rg -q 'hancore\.shibumi\.bluetooth' widgets/WidgetRegistry.qml \
- || fail "Shibumi bluetooth presentation is not registered"
-[[ $(rg -c 'BluetoothBackendAdapter \{' hancore.shibumi.bluetooth/Service.qml) -eq 1 ]] \
- || fail "Bluetooth state must have one root owner"
-[[ $(rg -c 'Adapters\.BluetoothBackendAdapter \{' services/BluetoothService.qml) -eq 1 ]] \
- || fail "root Bluetooth service must own exactly one native backend"
-rg -q 'bar\.bluetoothService' widgets/BluetoothWidget.qml \
- || fail "Bluetooth view does not consume the shared owner"
-rg -q 'property var sessionOwners: \[\]' services/BluetoothService.qml \
+jq -e '
+ .id == "hancore.shibumi.bluetooth" and
+ .entryPoints.barWidget == "BarWidget.qml" and
+ .entryPoints.service == "Service.qml"
+' hancore.shibumi.bluetooth/manifest.json >/dev/null \
+ || fail "Shibumi Bluetooth plugin entry points are not declared"
+bluetooth_service=hancore.shibumi.bluetooth/Service.qml
+bluetooth_adapter=hancore.shibumi.bluetooth/BluetoothBackendAdapter.qml
+bluetooth_widget=hancore.shibumi.bluetooth/BarWidget.qml
+bluetooth_panel=hancore.shibumi.bluetooth/BluetoothPanel.qml
+[[ $(rg -c 'BluetoothBackendAdapter \{' "$bluetooth_service") -eq 1 ]] \
+ || fail "Bluetooth state must have one shipped native owner"
+rg -q 'serviceFor\("hancore\.shibumi\.bluetooth"\)' "$bluetooth_widget" \
+ || fail "Bluetooth view does not resolve the shipped service"
+rg -q 'property var sessionOwners: \[\]' "$bluetooth_service" \
|| fail "Bluetooth service lacks multi-output session accounting"
-rg -q 'target: "omarchy\.bluetooth"' services/BluetoothService.qml \
+rg -q 'target: "omarchy\.bluetooth"' "$bluetooth_service" \
|| fail "Bluetooth service does not own the single legacy IPC target"
-rg -q 'bar\.hideBarWidget\("omarchy\.bluetooth"\)' services/BluetoothService.qml \
+rg -q 'bar\.hideBarWidget\("omarchy\.bluetooth"\)' "$bluetooth_service" \
|| fail "Bluetooth legacy close/hide is not routed to the local panel"
-rg -q 'adapter\.stopDiscovery\(\)' services/BluetoothService.qml \
+rg -q 'adapter\.stopDiscovery\(\)' "$bluetooth_service" \
|| fail "Bluetooth discovery is not stopped after the final panel closes"
-for bluetooth_adapter in \
- adapters/BluetoothBackendAdapter.qml \
- hancore.shibumi.bluetooth/BluetoothBackendAdapter.qml; do
+for bluetooth_adapter in "$bluetooth_adapter"; do
rg -q '^import Quickshell\.Bluetooth$' "$bluetooth_adapter" \
|| fail "$bluetooth_adapter does not own the native BlueZ model"
rg -q '^import Quickshell\.Services\.Pipewire$' "$bluetooth_adapter" \
@@ -740,58 +763,46 @@ for bluetooth_adapter in \
fail "$bluetooth_adapter still owns IPC or loads a foreign UI component"
fi
done
-cmp -s adapters/BluetoothBackendAdapter.qml \
- hancore.shibumi.bluetooth/BluetoothBackendAdapter.qml \
- || fail "root and plugin Bluetooth adapters drifted"
-cmp -s adapters/BluetoothModel.js hancore.shibumi.bluetooth/BluetoothModel.js \
- || fail "root and plugin Bluetooth models drifted"
-cmp -s adapters/BluetoothDiscoveryGuard.qml \
- hancore.shibumi.bluetooth/BluetoothDiscoveryGuard.qml \
- || fail "root and plugin Bluetooth discovery guards drifted"
-for bluetooth_service in \
- services/BluetoothService.qml \
- hancore.shibumi.bluetooth/Service.qml; do
- rg -U -q 'id: discoveryRetry[^}]*repeat: true[^}]*running: root\.sessionCount > 0 && root\.adapterAvailable[^}]*root\.radioEnabled && !root\.discovering' \
- "$bluetooth_service" \
- || fail "$bluetooth_service does not bound discovery retries to an open session"
-done
+rg -U -q 'id: discoveryRetry[^}]*repeat: true[^}]*running: root\.sessionCount > 0 && root\.adapterAvailable[^}]*root\.radioEnabled && !root\.discovering' \
+ "$bluetooth_service" \
+ || fail "Bluetooth service does not bound discovery retries to an open session"
if rg -q 'registeredWidget|registeredSource|registeredComponent|panelSource|panelComponent|Loader \{' \
- services/BluetoothService.qml hancore.shibumi.bluetooth/Service.qml; then
+ "$bluetooth_service"; then
fail "Bluetooth service still resolves or loads the complete Omarchy panel"
fi
if rg -q 'Quickshell\.Bluetooth|Quickshell\.Services\.Pipewire|Bluetooth\.|Pipewire\.' \
- widgets/BluetoothWidget.qml widgets/BluetoothPanel.qml \
- services/BluetoothService.qml; then
+ "$bluetooth_widget" "$bluetooth_panel"; then
fail "Bluetooth presentation bypasses the process-wide native adapter"
fi
-if rg -q 'Process \{|Timer \{|FileView \{' widgets/BluetoothWidget.qml \
- widgets/BluetoothPanel.qml; then
+if rg -q 'Process \{|Timer \{|FileView \{' \
+ "$bluetooth_widget" "$bluetooth_panel"; then
fail "Bluetooth presentation must remain worker-free"
fi
-if rg -q 'Process \{|FileView \{' services/BluetoothService.qml; then
- fail "Bluetooth service facade must remain process- and file-worker-free"
+if rg -q 'Process \{|FileView \{' "$bluetooth_service" "$bluetooth_adapter"; then
+ fail "Bluetooth native owner uses a worker instead of native APIs"
fi
-if rg -q 'Process \{|FileView \{' adapters/BluetoothBackendAdapter.qml \
- hancore.shibumi.bluetooth/BluetoothBackendAdapter.qml; then
- fail "Bluetooth native adapter uses a worker instead of native APIs"
-fi
-rg -q 'childPanelWidget\("omarchy\.bluetooth"\)' tests/bluetooth-widget-smoke.qml \
- || fail "Bluetooth alias routing is not regression-tested"
+rg -q 'childPanelWidget\("omarchy\.bluetooth"\)' tests/bluetooth-plugin-smoke.qml \
+ || fail "Bluetooth alias routing is not regression-tested against shipped code"
[[ $(rg -c 'SystemTelemetry \{' hancore.shibumi.telemetry/Service.qml) -eq 1 ]] \
- || fail "system telemetry must have one root owner"
+ || fail "system telemetry must have one process-wide owner"
if rg -q 'Process \{' services/SystemTelemetry.qml; then
fail "system telemetry must read procfs without child processes"
fi
rg -q '"service": "WorkspaceService.qml"' hancore.shibumi.workspaces/manifest.json \
- || fail "workspace state must have exactly one root owner"
+ || fail "workspace state must have exactly one shipped owner"
+workspace_service=hancore.shibumi.workspaces/WorkspaceService.qml
+workspace_actions=hancore.shibumi.workspaces/WorkspaceActions.qml
+workspace_widget=hancore.shibumi.workspaces/BarWidget.qml
+workspace_panel=hancore.shibumi.workspaces/WorkspacePanel.qml
+workspace_panel_content=hancore.shibumi.workspaces/WorkspacePanelContent.qml
[[ $(rg -c 'ClockService \{' hancore.shibumi.center/Service.qml) -eq 1 ]] \
- || fail "clock state must have exactly one root owner"
-if rg -q 'Process \{|Timer \{|FileView \{' services/ClockService.qml \
- widgets/ClockWidget.qml; then
+ || fail "clock state must have exactly one process-wide owner"
+if rg -q 'Process \{|Timer \{|FileView \{' hancore.shibumi.center/ClockService.qml \
+ hancore.shibumi.center/ClockWidget.qml; then
fail "clock slice must use the shared event-driven SystemClock"
fi
-rg -q 'Ui\.WidgetButton \{' widgets/ClockWidget.qml \
+rg -q 'Ui\.WidgetButton \{' hancore.shibumi.center/ClockWidget.qml \
|| fail "clock interaction is not registered for overlay click forwarding"
[[ $(rg -c 'WorkspaceActions \{' hancore.shibumi.workspaces/WorkspaceService.qml) -eq 1 ]] \
|| fail "workspace actions must have exactly one root adapter"
@@ -814,42 +825,44 @@ if rg -q 'Process \{|Timer \{|FileView \{' core/LayoutController.qml \
core/DragSession.qml; then
fail "layout and drag core must remain event-driven and worker-free"
fi
-rg -q 'omarchy\.workspaces' widgets/WidgetRegistry.qml \
- || fail "Shibumi workspace widget does not override the host workspace slot"
-rg -q '^Ui\.Panel \{' widgets/WorkspaceWidget.qml \
- || fail "workspace widget does not implement the shared panel contract"
-rg -q '^ShibumiPanel \{' widgets/WorkspacePanel.qml \
+rg -q 'serviceFor\("hancore\.shibumi\.workspaces"\)' "$workspace_widget" \
+ || fail "workspace widget does not resolve the shipped service"
+rg -q '^ShibumiPanel \{' "$workspace_panel" \
|| fail "workspace panel does not use the screen-local keyboard panel contract"
- if rg -q 'Process \{|Timer \{|FileView \{' services/WorkspaceService.qml \
- adapters/WorkspaceActions.qml widgets/WorkspaceWidget.qml widgets/WorkspacePanel.qml \
- widgets/WorkspacePanelContent.qml; then
- fail "workspace slice must remain event-driven and worker-free"
+if rg -q 'Process \{|Timer \{|FileView \{' \
+ "$workspace_service" "$workspace_widget" "$workspace_panel" \
+ "$workspace_panel_content"; then
+ fail "workspace state and views must remain event-driven and worker-free"
fi
-if rg -q 'Quickshell\.Hyprland|Hyprland\.' widgets/WorkspaceWidget.qml \
- widgets/WorkspacePanel.qml; then
+if rg -q 'Quickshell\.Hyprland|Hyprland\.' \
+ "$workspace_widget" "$workspace_panel" "$workspace_panel_content"; then
fail "workspace views must consume the shared workspace service"
fi
-rg -q 'Number\.isInteger\(id\)' adapters/WorkspaceActions.qml \
+rg -q 'Number\.isInteger\(id\)' "$workspace_actions" \
|| fail "workspace action adapter does not validate ids"
-rg -q 'bar\.run\("hyprctl dispatch' adapters/WorkspaceActions.qml \
- || fail "workspace action adapter does not use the host launcher"
-rg -q 'hancore\.shibumi\.memory' widgets/WidgetRegistry.qml \
+rg -Fq '"hl.dsp.focus({ workspace = \"" + id + "\" })"' \
+ "$workspace_actions" \
+ || fail "workspace action adapter does not preserve the typed dispatch"
+if rg -q 'bash|-c|bar\.run' "$workspace_actions"; then
+ fail "workspace action crosses a shell or bar-command boundary"
+fi
+rg -q 'hancore\.shibumi\.memory' contracts/plugin-suite-v1.json \
|| fail "internal memory widget is not registered"
-rg -q 'hancore\.shibumi\.cpu' widgets/WidgetRegistry.qml \
+rg -q 'hancore\.shibumi\.cpu' contracts/plugin-suite-v1.json \
|| fail "internal CPU widget is not registered"
rg -q 'G7: \["hancore\.shibumi\.ai"\]' core/GroupRegistry.js \
|| fail "G7 is not owned by the Shibumi AI facade"
-rg -q 'hancore\.shibumi\.ai' widgets/WidgetRegistry.qml \
- || fail "Shibumi AI facade is not registered"
rg -q '"service": "Service.qml"' hancore.shibumi.ai/manifest.json \
- || fail "AI provider state must have one root owner"
-rg -q 'function childPanelWidget\(pluginId\)' widgets/AiUsageWidget.qml \
+ || fail "AI provider state must have one shipped owner"
+rg -q 'function childPanelWidget\(pluginId\)' hancore.shibumi.ai/BarWidget.qml \
|| fail "AI facade does not expose legacy model-usage panel routing"
-if rg -q 'CACHE_FILE|stale_last' scripts/opencode-usage; then
+if rg -q 'CACHE_FILE|stale_last' \
+ hancore.shibumi.ai/scripts/opencode-usage; then
fail "OpenCode provider must not persist a V1 usage cache"
fi
-if rg -q 'Process \{|Timer \{|FileView \{' widgets/AiUsageWidget.qml \
- widgets/AiUsagePanel.qml; then
+if rg -q 'Process \{|Timer \{|FileView \{' \
+ hancore.shibumi.ai/BarWidget.qml \
+ hancore.shibumi.ai/AiUsagePanel.qml; then
fail "AI views must not own provider polling or file watchers"
fi
rg -q 'G1: \["hancore.shibumi.control-center"\]' core/GroupRegistry.js \
@@ -864,13 +877,13 @@ rg -q 'function setBarPosition\(value, ownerValue, screenName\)' Bar.qml \
|| fail "G1 cannot persist output-local top/bottom position"
rg -q 'function setAllSplits\(value\)' Bar.qml \
|| fail "G1 cannot persist split presets"
-detail_panel_count=$(rg --files widgets -g '*Panel.qml' \
- | rg -v '(^|/)ShibumiPanel\.qml$' | wc -l)
-[[ $(rg -l '^ShibumiPanel \{' widgets/*Panel.qml | wc -l) \
+detail_panel_count=$(find hancore.shibumi.* -maxdepth 1 -type f \
+ -name '*Panel.qml' ! -name ShibumiPanel.qml | wc -l)
+[[ $(rg -l '^ShibumiPanel \{' hancore.shibumi.*/*Panel.qml | wc -l) \
-eq $detail_panel_count ]] \
- || fail "local detail panels do not share the Shibumi surface contract"
-[[ $(rg -l '^Ui\.KeyboardPanel \{' widgets/*Panel.qml | wc -l) -eq 0 ]] \
- || fail "a local detail panel still layers host panel chrome"
+ || fail "shipped detail panels do not share the Shibumi surface contract"
+[[ $(rg -l '^Ui\.KeyboardPanel \{' hancore.shibumi.*/*Panel.qml | wc -l) -eq 0 ]] \
+ || fail "a shipped detail panel still layers host panel chrome"
rg -q 'default property alias panelContent: shibumiContent\.children' \
widgets/ShibumiPanel.qml \
|| fail "Shibumi panel surface does not isolate chrome from panel content"
@@ -899,14 +912,14 @@ for token in controlBorderColor controlHoverBorderColor controlFillColor \
|| fail "Shibumi panel is missing the shared inner-control token: $token"
done
for pair in \
- 'widgets/AiUsagePanel.qml:panel.controlBorderColor' \
- 'widgets/AudioPanel.qml:panel.controlHoverBorderColor' \
- 'widgets/BluetoothPanel.qml:panel.controlBorderColor' \
- 'widgets/BrightnessPanel.qml:panel.controlActiveFillColor' \
- 'widgets/BrightnessPanel.qml:panel.controlBorderColor' \
- 'widgets/NetworkPanel.qml:panel.controlBorderColor' \
- 'widgets/PowerProfilePanel.qml:panel.controlBorderColor' \
- 'widgets/WorkspacePanelContent.qml:root.controller.controlFillColor'; do
+ 'hancore.shibumi.ai/AiUsagePanel.qml:panel.controlBorderColor' \
+ 'hancore.shibumi.audio/AudioPanel.qml:panel.controlHoverBorderColor' \
+ 'hancore.shibumi.bluetooth/BluetoothPanel.qml:panel.controlBorderColor' \
+ 'hancore.shibumi.brightness/BrightnessPanel.qml:panel.controlActiveFillColor' \
+ 'hancore.shibumi.brightness/BrightnessPanel.qml:panel.controlBorderColor' \
+ 'hancore.shibumi.network/NetworkPanel.qml:panel.controlBorderColor' \
+ 'hancore.shibumi.power-profile/PowerProfilePanel.qml:panel.controlBorderColor' \
+ 'hancore.shibumi.workspaces/WorkspacePanelContent.qml:root.controller.controlFillColor'; do
file=${pair%%:*}
token=${pair#*:}
rg -Fq "$token" "$file" \
@@ -919,9 +932,9 @@ rg -q 'themeNamePath' hancore.shibumi.state/ThemePalette.qml \
if rg -q 'Process \{|Timer \{' hancore.shibumi.state/ThemePalette.qml; then
fail "theme palette bridge must remain event-driven"
fi
-rg -q 'omarchy\.clock' widgets/WidgetRegistry.qml \
+rg -q 'omarchy\.clock' core/GroupRegistry.js \
|| fail "Shibumi clock does not override the host clock slot"
-rg -q 'hancore\.shibumi\.center' widgets/WidgetRegistry.qml \
+rg -q 'hancore\.shibumi\.center' contracts/plugin-suite-v1.json \
|| fail "Shibumi center composite is not registered"
rg -q 'GroupSection' styles/shibumi/BarSurface.qml \
|| fail "Shibumi surface does not render persisted groups"
@@ -970,13 +983,13 @@ for unsafe_teardown_access in \
fail "group teardown retains an unsafe owner access: $unsafe_teardown_access"
fi
done
-rg -q 'acquire\("memory"\)' widgets/MemoryWidget.qml \
+rg -q 'acquire\("memory"\)' hancore.shibumi.memory/BarWidget.qml \
|| fail "memory widget does not activate shared telemetry"
-rg -q 'release\("memory"\)' widgets/MemoryWidget.qml \
+rg -q 'release\("memory"\)' hancore.shibumi.memory/BarWidget.qml \
|| fail "memory widget does not release shared telemetry"
-rg -q 'acquire\("cpu"\)' widgets/CpuWidget.qml \
+rg -q 'acquire\("cpu"\)' hancore.shibumi.cpu/BarWidget.qml \
|| fail "CPU widget does not activate shared telemetry"
-rg -q 'release\("cpu"\)' widgets/CpuWidget.qml \
+rg -q 'release\("cpu"\)' hancore.shibumi.cpu/BarWidget.qml \
|| fail "CPU widget does not release shared telemetry"
for facade_name in \
@@ -1057,8 +1070,9 @@ quote_smoke_root=$(mktemp -d)
mkdir -p "$quote_smoke_root/services" "$quote_smoke_root/runtime" \
"$quote_smoke_root/home"
chmod 700 "$quote_smoke_root/runtime"
-cp services/QuoteDefaults.js services/ReactorModel.js \
- services/QuoteService.qml "$quote_smoke_root/services/"
+cp hancore.shibumi.reactor/QuoteDefaults.js \
+ hancore.shibumi.reactor/ReactorModel.js \
+ hancore.shibumi.reactor/QuoteService.qml "$quote_smoke_root/services/"
cp tests/quote-service-smoke.qml "$quote_smoke_root/shell.qml"
set +e
quote_service_output=$(timeout 4 env \
@@ -1252,320 +1266,14 @@ OMARCHY_PATH="$OMARCHY_PATH" "$repo_root/tests/state-service-regression.sh"
<<<"$v1_slot_interaction_output" \
|| fail "V1 slot interaction smoke did not reach its marker"
- cp services/SystemTelemetry.qml "$smoke_root/services/"
- cp services/ClockService.qml "$smoke_root/services/"
- cp widgets/MemoryWidget.qml "$smoke_root/widgets/"
- cp widgets/MemoryRing.qml "$smoke_root/widgets/"
- cp widgets/CpuWidget.qml "$smoke_root/widgets/"
- cp widgets/CpuWave.qml "$smoke_root/widgets/"
- cp widgets/PillSurface.qml widgets/IconText.qml "$smoke_root/widgets/"
- cp widgets/ClockWidget.qml "$smoke_root/widgets/"
- cp tests/memory-widget-smoke.qml "$smoke_root/shell.qml"
- sed -i \
- -e 's#import "../services"#import "services"#' \
- -e 's#import "../widgets"#import "widgets"#' \
- "$smoke_root/shell.qml"
-
- set +e
- smoke_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- smoke_rc=$?
- set -e
- printf '%s\n' "$smoke_output"
- [[ $smoke_rc -eq 0 ]] || fail "telemetry widget smoke exited $smoke_rc"
- grep -q 'telemetry widget smoke passed' <<<"$smoke_output" \
- || fail "telemetry widget smoke did not reach its lifecycle marker"
-
- cp tests/clock-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- clock_smoke_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- clock_smoke_rc=$?
- set -e
- printf '%s\n' "$clock_smoke_output"
- [[ $clock_smoke_rc -eq 0 ]] || fail "clock widget smoke exited $clock_smoke_rc"
- grep -q 'clock widget smoke passed' <<<"$clock_smoke_output" \
- || fail "clock widget smoke did not reach its lifecycle marker"
-
- cp widgets/CalendarModel.js widgets/CenterLayout.js widgets/CenterWidget.qml \
- widgets/IconText.qml widgets/StatusIndicators.qml widgets/SystemUpdateWidget.qml \
- widgets/WeatherWidget.qml \
- "$smoke_root/widgets/"
- cp tests/fixtures/CenterTestCalendar.qml \
- tests/fixtures/WeatherPanelTestView.qml "$smoke_root/"
- cp tests/center-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- center_smoke_output=$(timeout 6 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- center_smoke_rc=$?
- set -e
- printf '%s\n' "$center_smoke_output"
- [[ $center_smoke_rc -eq 0 ]] || fail "center widget smoke exited $center_smoke_rc"
- grep -q 'center widget smoke passed' <<<"$center_smoke_output" \
- || fail "center widget smoke did not reach its lifecycle marker"
- ! grep -Eq 'Type .* unavailable|No PanelWindow backend loaded' \
- <<<"$center_smoke_output" \
- || fail "center widget smoke accepted an unavailable panel type"
-
- cp adapters/AudioPanelBridge.qml "$smoke_root/adapters/"
- cp widgets/AudioWidget.qml "$smoke_root/widgets/"
- mkdir -p "$smoke_root/fixtures"
- cp tests/fixtures/AudioTestPanel.qml tests/fixtures/AudioTestView.qml \
- "$smoke_root/fixtures/"
- cp tests/audio-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- audio_smoke_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- audio_smoke_rc=$?
- set -e
- printf '%s\n' "$audio_smoke_output"
- [[ $audio_smoke_rc -eq 0 ]] || fail "audio widget smoke exited $audio_smoke_rc"
- grep -q 'audio widget smoke passed' <<<"$audio_smoke_output" \
- || fail "audio widget smoke did not reach its lifecycle marker"
-
- # Exercise the packaged plugin payload. The predecessor widget tree is not
- # part of release archives and may intentionally lag the extracted plugin.
- cp hancore.shibumi.network/BarWidget.qml \
- "$smoke_root/widgets/NetworkWidget.qml"
- cp hancore.shibumi.network/HostTokens.qml \
- hancore.shibumi.network/IconText.qml \
- hancore.shibumi.network/PillSurface.qml "$smoke_root/widgets/"
- cp tests/fixtures/NetworkTestService.qml tests/fixtures/NetworkTestView.qml \
- "$smoke_root/fixtures/"
- cp tests/network-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- network_smoke_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- network_smoke_rc=$?
- set -e
- printf '%s\n' "$network_smoke_output"
- [[ $network_smoke_rc -eq 0 ]] || fail "network widget smoke exited $network_smoke_rc"
- grep -q 'network widget smoke passed' <<<"$network_smoke_output" \
- || fail "network widget smoke did not reach its lifecycle marker"
-
- cp adapters/MonitorPanelBridge.qml "$smoke_root/adapters/"
- cp services/MonitorService.qml "$smoke_root/services/"
- cp widgets/BrightnessWidget.qml widgets/BrightnessPanel.qml \
- "$smoke_root/widgets/"
- cp tests/fixtures/MonitorTestPanel.qml tests/fixtures/MonitorTestView.qml \
- "$smoke_root/fixtures/"
- cp tests/brightness-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- brightness_smoke_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- brightness_smoke_rc=$?
- set -e
- printf '%s\n' "$brightness_smoke_output"
- [[ $brightness_smoke_rc -eq 0 ]] \
- || fail "brightness widget smoke exited $brightness_smoke_rc"
- grep -q 'brightness widget smoke passed' <<<"$brightness_smoke_output" \
- || fail "brightness widget smoke did not reach its lifecycle marker"
-
- cp services/PowerModel.js "$smoke_root/services/"
- cp widgets/BatteryWidget.qml widgets/BatteryPanel.qml \
- widgets/PowerProfileWidget.qml widgets/PowerProfilePanel.qml \
- "$smoke_root/widgets/"
- cp tests/fixtures/PowerTestService.qml tests/fixtures/PowerTestPanel.qml \
- "$smoke_root/fixtures/"
- cp tests/power-widgets-smoke.qml "$smoke_root/shell.qml"
- set +e
- power_smoke_output=$(timeout 7 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- power_smoke_rc=$?
- set -e
- printf '%s\n' "$power_smoke_output"
- [[ $power_smoke_rc -eq 0 ]] || fail "power widgets smoke exited $power_smoke_rc"
- grep -q 'power widgets smoke passed' <<<"$power_smoke_output" \
- || fail "power widgets smoke did not reach its lifecycle marker"
-
- cp services/PowerService.qml "$smoke_root/services/"
- mkdir -p "$smoke_root/power-bin"
- cp tests/fixtures/power-bin/* "$smoke_root/power-bin/"
- chmod 700 "$smoke_root/power-bin/"*
- printf 'balanced\n' >"$smoke_root/power-state"
- cp tests/power-service-runtime-smoke.qml "$smoke_root/shell.qml"
- set +e
- power_service_output=$(timeout 7 env \
- PATH="$smoke_root/power-bin:$PATH" \
- SHIBUMI_POWER_FIXTURE_STATE="$smoke_root/power-state" \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- power_service_rc=$?
- set -e
- printf '%s\n' "$power_service_output"
- [[ $power_service_rc -eq 0 ]] \
- || fail "power service runtime smoke exited $power_service_rc"
- grep -q 'power service runtime smoke passed' <<<"$power_service_output" \
- || fail "power service runtime smoke did not reach its lifecycle marker"
- [[ $(<"$smoke_root/power-state") == performance ]] \
- || fail "power service did not execute the exact validated profile action"
-
- cp adapters/BluetoothBackendAdapter.qml adapters/BluetoothModel.js \
- adapters/BluetoothDiscoveryGuard.qml adapters/qmldir \
- "$smoke_root/adapters/"
- cp services/BluetoothService.qml "$smoke_root/services/"
- cp widgets/BluetoothWidget.qml widgets/BluetoothPanel.qml "$smoke_root/widgets/"
- cp tests/fixtures/BluetoothTestBackend.qml \
- tests/fixtures/BluetoothTestView.qml "$smoke_root/fixtures/"
- cp tests/bluetooth-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- bluetooth_smoke_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- bluetooth_smoke_rc=$?
- set -e
- printf '%s\n' "$bluetooth_smoke_output"
- [[ $bluetooth_smoke_rc -eq 0 ]] \
- || fail "bluetooth widget smoke exited $bluetooth_smoke_rc"
- grep -q 'bluetooth widget smoke passed' <<<"$bluetooth_smoke_output" \
- || fail "bluetooth widget smoke did not reach its lifecycle marker"
-
- cp widgets/StatusWidget.qml widgets/TrayStatusView.qml \
- widgets/NotificationStatusView.qml widgets/TrayDrawerPanel.qml \
- "$smoke_root/widgets/"
- cp tests/fixtures/StatusTestWidget.qml "$smoke_root/fixtures/"
- cp tests/fixtures/TrayDrawerTestPanel.qml "$smoke_root/fixtures/"
- cp tests/fixtures/NotificationPanelTestView.qml "$smoke_root/fixtures/"
- cp tests/status-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- status_smoke_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- status_smoke_rc=$?
- set -e
- printf '%s\n' "$status_smoke_output"
- [[ $status_smoke_rc -eq 0 ]] \
- || fail "status widget smoke exited $status_smoke_rc"
- grep -q 'status widget smoke passed' <<<"$status_smoke_output" \
- || fail "status widget smoke did not reach its lifecycle marker"
-
- mkdir -p "$smoke_root/widgets/assets"
- cp hancore.shibumi.ai/BarWidget.qml \
- "$smoke_root/widgets/AiUsageWidget.qml"
- cp hancore.shibumi.ai/HostTokens.qml \
- hancore.shibumi.ai/IconText.qml \
- hancore.shibumi.ai/PillSurface.qml \
- hancore.shibumi.ai/TintedImage.qml "$smoke_root/widgets/"
- cp hancore.shibumi.ai/assets/codex.svg \
- hancore.shibumi.ai/assets/opencode-mark.svg \
- "$smoke_root/widgets/assets/"
- cp tests/fixtures/AiTestPanel.qml "$smoke_root/"
- cp tests/ai-usage-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- ai_usage_smoke_output=$(timeout 6 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- ai_usage_smoke_rc=$?
- set -e
- printf '%s\n' "$ai_usage_smoke_output"
- [[ $ai_usage_smoke_rc -eq 0 ]] \
- || fail "AI usage widget smoke exited $ai_usage_smoke_rc"
- grep -q 'ai usage widget smoke passed' <<<"$ai_usage_smoke_output" \
- || fail "AI usage widget smoke did not reach its lifecycle marker"
-
- cp widgets/MediaWidget.qml widgets/MediaPanel.qml widgets/MediaPulse.qml \
- widgets/MediaSpectrum.qml widgets/MediaMuse.qml widgets/CavaThemeModel.js \
- "$smoke_root/widgets/"
- cp tests/fixtures/MediaTestPanel.qml "$smoke_root/fixtures/"
- cp tests/media-widget-smoke.qml "$smoke_root/shell.qml"
- set +e
- media_smoke_output=$(timeout 7 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- media_smoke_rc=$?
- set -e
- printf '%s\n' "$media_smoke_output"
- [[ $media_smoke_rc -eq 0 ]] || fail "media widget smoke exited $media_smoke_rc"
- grep -q 'media widget smoke passed' <<<"$media_smoke_output" \
- || fail "media widget smoke did not reach its lifecycle marker"
-
- cp styles/shibumi/Style.qml styles/shibumi/VisualTokens.qml \
- styles/shibumi/BarSurface.qml styles/shibumi/TooltipSurface.qml \
- styles/shibumi/DragGhost.qml styles/shibumi/GroupSection.qml \
- styles/shibumi/RunChrome.qml styles/shibumi/GapEffectsLayer.qml \
- styles/shibumi/ReactorEventLayer.qml \
- "$smoke_root/styles/shibumi/"
- cp tests/shibumi-presentation-smoke.qml "$smoke_root/shell.qml"
+ # Capability smokes above exercise the shipped plugin payloads directly.
- set +e
- presentation_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- presentation_rc=$?
- set -e
- printf '%s\n' "$presentation_output"
- [[ $presentation_rc -eq 0 ]] \
- || fail "Shibumi presentation smoke exited $presentation_rc"
- grep -q 'shibumi presentation smoke passed' <<<"$presentation_output" \
- || fail "Shibumi presentation smoke did not reach its marker"
+ # Shared presentation is exercised through shipped capability smokes.
- cp adapters/WorkspaceActions.qml "$smoke_root/adapters/"
- cp services/WorkspaceModel.js services/WorkspaceService.qml "$smoke_root/services/"
- cp widgets/WorkspaceWidget.qml widgets/PacmanWorkspaceMarker.qml \
- widgets/WorkspacePanel.qml \
- widgets/WorkspacePanelContent.qml widgets/ShibumiPanelToolTip.qml \
+ cp hancore.shibumi.workspaces/WorkspacePanelContent.qml \
+ hancore.shibumi.workspaces/ShibumiPanelToolTip.qml \
+ hancore.shibumi.workspaces/IconText.qml \
"$smoke_root/widgets/"
- cp tests/fixtures/WorkspaceTestPanel.qml "$smoke_root/"
- cp tests/workspace-widget-smoke.qml "$smoke_root/shell.qml"
-
- set +e
- workspace_smoke_output=$(timeout 5 env \
- QT_QPA_PLATFORM=offscreen \
- XDG_RUNTIME_DIR="$smoke_root/runtime" \
- QML_IMPORT_PATH="${OMARCHY_PATH}/shell${QML_IMPORT_PATH:+:${QML_IMPORT_PATH}}" \
- QML2_IMPORT_PATH="${OMARCHY_PATH}/shell${QML2_IMPORT_PATH:+:${QML2_IMPORT_PATH}}" \
- /usr/bin/quickshell -p "$smoke_root" 2>&1)
- workspace_smoke_rc=$?
- set -e
- printf '%s\n' "$workspace_smoke_output"
- [[ $workspace_smoke_rc -eq 0 ]] || fail "workspace widget smoke exited $workspace_smoke_rc"
- grep -q 'workspace widget smoke passed' <<<"$workspace_smoke_output" \
- || fail "workspace widget smoke did not reach its lifecycle marker"
-
cp tests/workspace-panel-smoke.qml "$smoke_root/shell.qml"
set +e
workspace_panel_output=$(timeout 5 env \
diff --git a/tests/media-widget-smoke.qml b/tests/media-widget-smoke.qml
deleted file mode 100644
index 19b9c89..0000000
--- a/tests/media-widget-smoke.qml
+++ /dev/null
@@ -1,288 +0,0 @@
-import QtQuick
-import Quickshell
-import "widgets" as Widgets
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property int waits: 0
- property real activeWidth: 0
- property var mediaSettings: ({ spectrum: false, compact: false })
-
- function fail(message) {
- console.error("media-widget-smoke:", message)
- Qt.exit(1)
- }
-
- QtObject {
- id: playerA
- property string key: "player-a"
- property string identity: "Vesktop"
- property string desktopEntry: "vesktop"
- property string trackTitle: "A deliberately long track title for marquee validation"
- property string trackArtist: "Artist A"
- property string trackAlbum: "Album A"
- property string trackArtUrl: ""
- property bool isPlaying: true
- property bool canGoPrevious: true
- property bool canGoNext: true
- property bool canTogglePlaying: true
- property bool canPlay: true
- property bool canPause: true
- property real position: 61
- property real length: 245
- }
-
- QtObject {
- id: playerB
- property string key: "player-b"
- property string identity: "Firefox"
- property string desktopEntry: "firefox"
- property string trackTitle: "Track B"
- property string trackArtist: "Artist B"
- property string trackAlbum: ""
- property string trackArtUrl: ""
- property bool isPlaying: false
- property bool canGoPrevious: false
- property bool canGoNext: true
- property bool canTogglePlaying: true
- property bool canPlay: true
- property bool canPause: true
- property real position: 0
- property real length: 180
- }
-
- QtObject {
- id: mediaState
- property var activePlayer: playerA
- property var sourcePlayers: [playerA, playerB]
- readonly property bool hasMedia: activePlayer !== null
- && (activePlayer.trackTitle !== "" || activePlayer.trackArtist !== "")
- property string lastAction: ""
- property string lastTarget: ""
- property string selectedKey: ""
- property int actionCount: 0
-
- function playerKey(player) { return player ? String(player.key || "") : "" }
-
- function runAction(action, showFeedback, targetKey) {
- lastAction = String(action || "")
- lastTarget = String(targetKey || "")
- actionCount++
- if (lastAction === "playPause" && activePlayer)
- activePlayer.isPlaying = !activePlayer.isPlaying
- return activePlayer !== null
- }
-
- function selectPlayer(key) {
- selectedKey = String(key || "")
- if (selectedKey === playerA.key) activePlayer = playerA
- else if (selectedKey === playerB.key) activePlayer = playerB
- else return false
- return true
- }
- }
-
- QtObject {
- id: fakeSpectrum
- property var clients: []
- property var levels: [
- 0.04, 0.04, 0.04, 0.04, 0.04, 0.04,
- 0.04, 0.04, 0.04, 0.04, 0.04, 0.04,
- 0.04, 0.04, 0.04, 0.04, 0.04, 0.04,
- 0.04, 0.04, 0.04, 0.04, 0.04, 0.04
- ]
- property var themeColors: []
- property string state: "running"
- property bool workerRunning: false
- readonly property int clientCount: clients.length
-
- function beginSpectrum(owner) {
- if (!owner || clients.indexOf(owner) >= 0) return false
- clients = clients.concat([owner])
- return true
- }
-
- function endSpectrum(owner) {
- if (!owner || clients.indexOf(owner) < 0) return false
- clients = clients.filter(function(candidate) { return candidate !== owner })
- return true
- }
- }
-
- QtObject {
- id: fakeShell
- property bool serviceAvailable: true
- function firstPartyServiceFor(id) {
- return serviceAvailable && String(id || "") === "omarchy.media"
- ? mediaState : null
- }
- function serviceFor(id) {
- return String(id || "") === "hancore.shibumi.media"
- ? fakeSpectrum : null
- }
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 28
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#e8e8e8"
- property color barForeground: foreground
- property color background: "#181818"
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var shell: fakeShell
- property var activePopout: null
- property var clickTargets: []
- property var visualTokens: ({
- pillHeight: 24,
- pillRadius: 12,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- pillPaddingX: 9,
- slotHeight: 28,
- contentGap: 5,
- compactGap: 4,
- labelSize: 12,
- iconSize: 15,
- presentation: ({ radius: "large" })
- })
-
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- function targetBelongsToWindow(_target, _window) { return true }
- function registerClickTarget(target) {
- if (clickTargets.indexOf(target) < 0) clickTargets = clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- clickTargets = clickTargets.filter(function(item) { return item !== target })
- }
- }
-
- Loader {
- id: mediaLoader
- active: true
- sourceComponent: Component {
- Widgets.MediaWidget {
- bar: fakeBar
- settings: root.mediaSettings
- panelSource: Qt.resolvedUrl("fixtures/MediaTestPanel.qml")
- }
- }
- }
-
- Widgets.MediaWidget {
- id: unavailableMedia
- bar: unavailableBar
- settings: ({ spectrum: false })
- }
-
- QtObject {
- id: unavailableBar
- property var shell: null
- property int barSize: 28
- property bool vertical: false
- property string fontFamily: "monospace"
- property color foreground: "#e8e8e8"
- property color barForeground: foreground
- property color background: "#181818"
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var visualTokens: fakeBar.visualTokens
- property var clickTargets: []
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function registerClickTarget(target) {
- if (clickTargets.indexOf(target) < 0) clickTargets = clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- clickTargets = clickTargets.filter(function(item) { return item !== target })
- }
- }
-
- Timer {
- interval: 60
- repeat: true
- running: true
- onTriggered: {
- const media = mediaLoader.item
- if (root.phase === 0) {
- if (!media || !media.mediaService || !media.active || !media.playing
- || media.trackLabel.indexOf("Artist A") < 0 || media.panelLoaded
- || media.fullMode || media.museBandCount !== 24
- || fakeSpectrum.clientCount !== 0
- || unavailableMedia.visible || fakeBar.clickTargets.length !== 1)
- return root.fail("initial media service/presentation contract")
- root.activeWidth = media.implicitWidth
- media.interactionTarget.triggerPress(Qt.LeftButton)
- if (mediaState.lastAction !== "playPause" || playerA.isPlaying)
- return root.fail("play/pause action forwarding")
- media.interactionTarget.wheelMoved(120)
- if (mediaState.lastAction !== "previous")
- return root.fail("wheel previous action forwarding")
- root.mediaSettings = ({ spectrum: false, compact: true })
- } else if (root.phase === 1) {
- if (!media.fullMode || !media.museVisible
- || fakeSpectrum.clientCount !== 1)
- return root.fail("FULL muse presentation/lease")
- media.runMusePrimaryAction()
- if (mediaState.lastAction !== "playPause" || !playerA.isPlaying)
- return root.fail("FULL muse play/pause forwarding")
- media.runMuseWheel(120)
- if (mediaState.lastAction !== "next")
- return root.fail("FULL muse wheel-next forwarding")
- media.runMuseWheel(-120)
- if (mediaState.lastAction !== "previous")
- return root.fail("FULL muse wheel-previous forwarding")
- root.mediaSettings = ({ spectrum: false, compact: false })
- } else if (root.phase === 2) {
- if (media.fullMode || media.museVisible
- || fakeSpectrum.clientCount !== 0)
- return root.fail("FULL muse lease release")
- media.interactionTarget.triggerPress(Qt.RightButton)
- } else if (root.phase === 3) {
- if (!media.opened || !media.panelLoaded || !media.panelItem) return
- if (media.panelItem.renderedSourceCount !== 2
- || media.panelItem.spectrumWorkerRunning
- || media.panelItem.formatTime(61) !== "1:01"
- || media.panelItem.currentLength !== 245)
- return root.fail("lazy panel/state contract")
- if (!media.panelItem.selectSource(1)
- || mediaState.selectedKey !== "player-b"
- || mediaState.activePlayer !== playerB)
- return root.fail("source selection forwarding")
- media.close()
- } else if (root.phase === 4) {
- if (media.opened || media.panelLoaded || media.activePlayer !== playerB)
- return root.fail("lazy panel close")
- mediaState.activePlayer = null
- } else if (root.phase === 5) {
- if (media.active || media.implicitWidth >= root.activeWidth)
- return root.fail("idle V1 presentation")
- media.interactionTarget.triggerPress(Qt.LeftButton)
- } else if (root.phase === 6) {
- if (!media.opened || !media.panelLoaded) return
- media.close()
- mediaLoader.active = false
- } else {
- if (fakeBar.clickTargets.length !== 0 || fakeBar.activePopout !== null
- || fakeSpectrum.clientCount !== 0)
- return root.fail("media teardown")
- stop()
- console.log("media widget smoke passed")
- Qt.quit()
- }
- root.phase++
- }
- }
-}
diff --git a/tests/memory-widget-smoke.qml b/tests/memory-widget-smoke.qml
deleted file mode 100644
index 44523d8..0000000
--- a/tests/memory-widget-smoke.qml
+++ /dev/null
@@ -1,128 +0,0 @@
-import QtQuick
-import Quickshell
-import "../services" as Services
-import "../widgets" as Widgets
-
-ShellRoot {
- id: root
-
- property bool failed: false
-
- function fail(message) {
- failed = true
- console.error("memory-widget-smoke:", message)
- Qt.quit()
- }
-
- Services.SystemTelemetry {
- id: telemetry
- }
-
- QtObject {
- id: actions
- function openSystemMonitor() { return true }
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 26
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#e8e8e8"
- property color barForeground: foreground
- property color background: "#181818"
- property color urgent: "#88bbee"
- property var visualTokens: ({
- pillPaddingX: 9,
- slotHeight: 28,
- pillHeight: 24,
- pillRadius: 12,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- contentGap: 5,
- compactGap: 4,
- labelSize: 12,
- iconSize: 15
- })
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var systemTelemetry: telemetry
- property var gpuTelemetry: null
- property var systemActions: actions
-
- function showTooltip(target, text) {}
- function hideTooltip(target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(owner, direction) { return false }
- }
-
- Loader {
- id: memoryLoader
- active: true
- sourceComponent: Component {
- Widgets.MemoryWidget {
- bar: fakeBar
- settings: ({ compact: false })
- }
- }
- }
-
- Loader {
- id: cpuLoader
- active: true
- sourceComponent: Component {
- Widgets.CpuWidget {
- bar: fakeBar
- settings: ({ compact: false })
- }
- }
- }
-
- Timer {
- interval: 800
- running: true
- onTriggered: {
- if (!memoryLoader.item) {
- root.fail("widget did not instantiate")
- return
- }
- if (telemetry.memoryConsumers !== 1) {
- root.fail("widget did not acquire exactly one telemetry consumer")
- return
- }
- if (telemetry.cpuConsumers !== 1 || telemetry.previousCpuTotal <= 0) {
- root.fail("CPU widget did not acquire or prime shared telemetry")
- return
- }
- if (telemetry.memTotalMiB <= 0 || telemetry.memAvailableMiB <= 0) {
- root.fail("procfs memory data did not load")
- return
- }
- memoryLoader.active = false
- cpuLoader.active = false
- releaseCheck.restart()
- }
- }
-
- Timer {
- id: releaseCheck
- interval: 0
- onTriggered: {
- if (telemetry.memoryConsumers !== 0) {
- root.fail("widget did not release telemetry on destruction")
- return
- }
- if (telemetry.cpuConsumers !== 0) {
- root.fail("CPU widget did not release telemetry on destruction")
- return
- }
- console.log("telemetry widget smoke passed")
- Qt.quit()
- }
- }
-}
diff --git a/tests/network-plugin-regression.sh b/tests/network-plugin-regression.sh
index 9e71d9a..d654084 100755
--- a/tests/network-plugin-regression.sh
+++ b/tests/network-plugin-regression.sh
@@ -23,8 +23,6 @@ chmod 700 "$tmpdir/runtime"
cp -a -- "$repo_root/hancore.shibumi.network" "$tmpdir/network"
cp -a -- "$repo_root/hancore.shibumi.network" \
"$tmpdir/hancore.shibumi.network"
-cp -a -- "$repo_root/services" "$tmpdir/services"
-cp -a -- "$repo_root/adapters" "$tmpdir/adapters"
cp -a -- "$omarchy_path/shell/Commons" "$tmpdir/Commons"
cp -a -- "$omarchy_path/shell/Ui" "$tmpdir/Ui"
install -m 0644 "$repo_root/tests/network-plugin-smoke.qml" "$tmpdir/shell.qml"
@@ -107,36 +105,30 @@ fi
install -m 0644 "$repo_root/tests/network-scanner-lifecycle-smoke.qml" \
"$tmpdir/shell.qml"
-for scanner_mode in plugin canonical; do
- mkdir -p "$tmpdir/scanner-$scanner_mode-runtime" \
- "$tmpdir/scanner-$scanner_mode-home"
- chmod 700 "$tmpdir/scanner-$scanner_mode-runtime"
- canonical_flag=0
- [[ $scanner_mode == canonical ]] && canonical_flag=1
- set +e
- scanner_output=$(timeout 12 env \
- HOME="$tmpdir/scanner-$scanner_mode-home" \
- PATH="$tmpdir/bin:$PATH" \
- SHIBUMI_TEST_CANONICAL_NETWORK="$canonical_flag" \
- QT_QPA_PLATFORM=offscreen \
- WAYLAND_DISPLAY= \
- XDG_RUNTIME_DIR="$tmpdir/scanner-$scanner_mode-runtime" \
- QML_IMPORT_PATH="$omarchy_path/shell${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}" \
- QML2_IMPORT_PATH="$omarchy_path/shell${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}" \
- "$quickshell_bin" -p "$tmpdir" 2>&1)
- scanner_rc=$?
- set -e
- printf '%s\n' "$scanner_output"
- [[ $scanner_rc -eq 0 ]] \
- || fail "$scanner_mode scanner lifecycle smoke exited $scanner_rc"
- grep -F 'network scanner lifecycle smoke passed' \
- <<<"$scanner_output" >/dev/null \
- || fail "$scanner_mode scanner lifecycle success marker missing"
- if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign' \
- <<<"$scanner_output"; then
- fail "$scanner_mode scanner lifecycle produced a QML runtime error"
- fi
-done
+mkdir -p "$tmpdir/scanner-runtime" "$tmpdir/scanner-home"
+chmod 700 "$tmpdir/scanner-runtime"
+set +e
+scanner_output=$(timeout 12 env \
+ HOME="$tmpdir/scanner-home" \
+ PATH="$tmpdir/bin:$PATH" \
+ QT_QPA_PLATFORM=offscreen \
+ WAYLAND_DISPLAY= \
+ XDG_RUNTIME_DIR="$tmpdir/scanner-runtime" \
+ QML_IMPORT_PATH="$omarchy_path/shell${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}" \
+ QML2_IMPORT_PATH="$omarchy_path/shell${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}" \
+ "$quickshell_bin" -p "$tmpdir" 2>&1)
+scanner_rc=$?
+set -e
+printf '%s\n' "$scanner_output"
+[[ $scanner_rc -eq 0 ]] \
+ || fail "scanner lifecycle smoke exited $scanner_rc"
+grep -F 'network scanner lifecycle smoke passed' \
+ <<<"$scanner_output" >/dev/null \
+ || fail "scanner lifecycle success marker missing"
+if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign' \
+ <<<"$scanner_output"; then
+ fail "scanner lifecycle produced a QML runtime error"
+fi
normal_pid_log="$tmpdir/bounded-parent-pid"
normal_child_pid_log="$tmpdir/bounded-child-pid"
diff --git a/tests/network-scanner-lifecycle-smoke.qml b/tests/network-scanner-lifecycle-smoke.qml
index d0e8006..ce61869 100644
--- a/tests/network-scanner-lifecycle-smoke.qml
+++ b/tests/network-scanner-lifecycle-smoke.qml
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import "network" as Network
-import "services" as Services
import "fixtures" as Fixtures
ShellRoot {
@@ -14,8 +13,6 @@ ShellRoot {
property int activationBaseline: 0
property int legacyActivationBaseline: 0
property var clickTargets: []
- readonly property bool canonicalService:
- Quickshell.env("SHIBUMI_TEST_CANONICAL_NETWORK") === "1"
readonly property var networkService: networkServiceLoader.item
readonly property var legacyService: legacyServiceLoader.item
readonly property var destructionService: destructionServiceLoader.item
@@ -101,46 +98,25 @@ ShellRoot {
Network.Service { bar: fakeBar; panelComponent: legacyScannerPanel }
}
- Component {
- id: canonicalScannerService
- Services.NetworkService { bar: fakeBar; panelComponent: scannerGatePanel }
- }
-
- Component {
- id: canonicalLegacyService
- Services.NetworkService { bar: fakeBar; panelComponent: legacyScannerPanel }
- }
-
Component {
id: pluginDestructionService
Network.Service { bar: fakeBar; panelComponent: destructionScannerPanel }
}
- Component {
- id: canonicalDestructionService
- Services.NetworkService {
- bar: fakeBar
- panelComponent: destructionScannerPanel
- }
- }
-
Loader {
id: networkServiceLoader
- sourceComponent: root.canonicalService
- ? canonicalScannerService : pluginScannerService
+ sourceComponent: pluginScannerService
}
Loader {
id: legacyServiceLoader
- sourceComponent: root.canonicalService
- ? canonicalLegacyService : pluginLegacyService
+ sourceComponent: pluginLegacyService
}
Loader {
id: destructionServiceLoader
active: false
- sourceComponent: root.canonicalService
- ? canonicalDestructionService : pluginDestructionService
+ sourceComponent: pluginDestructionService
}
Item { id: firstOwner }
diff --git a/tests/network-widget-smoke.qml b/tests/network-widget-smoke.qml
deleted file mode 100644
index 04c435e..0000000
--- a/tests/network-widget-smoke.qml
+++ /dev/null
@@ -1,196 +0,0 @@
-import QtQuick
-import Quickshell
-import "widgets" as Widgets
-import "fixtures" as Fixtures
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property int phaseTicks: 0
- property real fullWidth: 0
- property var clickTargets: []
-
- function fail(message) {
- console.error("network-widget-smoke:", message)
- Qt.exit(1)
- }
-
- Fixtures.NetworkTestService { id: sharedNetworkService }
- Fixtures.NetworkTestService { id: unavailableService; ready: false }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 35
- property int sizeHorizontal: 35
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#eeeeee"
- property color barForeground: foreground
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var clickTargets: root.clickTargets
- property var shell: null
- property var networkService: sharedNetworkService
- property var visualTokens: ({
- pillHeight: 24,
- pillRadius: 12,
- pillPaddingX: 9,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- slotHeight: 28,
- contentGap: 5,
- compactGap: 4,
- labelSize: 12,
- iconSize: 15
- })
-
- function registerClickTarget(target) {
- if (root.clickTargets.indexOf(target) < 0)
- root.clickTargets = root.clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- root.clickTargets = root.clickTargets.filter(item => item !== target)
- }
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- function targetBelongsToWindow(_target, _window) { return true }
- }
-
- Loader {
- id: firstLoader
- active: true
- sourceComponent: Component {
- Widgets.NetworkWidget {
- bar: fakeBar
- settings: ({ compact: false })
- networkServiceOverride: sharedNetworkService
- popupSource: Qt.resolvedUrl("fixtures/NetworkTestView.qml")
- }
- }
- }
-
- Loader {
- id: secondLoader
- active: true
- sourceComponent: Component {
- Widgets.NetworkWidget {
- bar: fakeBar
- settings: ({ compact: false })
- networkServiceOverride: sharedNetworkService
- popupSource: Qt.resolvedUrl("fixtures/NetworkTestView.qml")
- }
- }
- }
-
- Widgets.NetworkWidget {
- id: unavailableNetwork
- bar: fakeBar
- networkServiceOverride: unavailableService
- }
-
- Timer {
- interval: 80
- repeat: true
- running: true
- onTriggered: {
- root.phaseTicks++
- const first = firstLoader.item
- const second = secondLoader.item
- if (root.phase === 0) {
- if (!first || !second || !first.networkReady || !second.networkReady
- || root.phaseTicks < 3) return
- if (first.networkService !== second.networkService
- || first.networkService !== sharedNetworkService
- || first.mode !== "wifi" || first.label !== "Test Network"
- || first.signal !== 73 || first.implicitHeight !== 35
- || unavailableNetwork.visible)
- return root.fail("shared backend readiness/state/geometry")
- if (first.childPanelWidget("omarchy.network") !== first
- || !first.ownsPanelWidget(first)
- || second.childPanelWidget("omarchy.network") !== second)
- return root.fail("screen-local alias routing")
- if (root.clickTargets.length !== 2 || sharedNetworkService.sessionCount !== 0)
- return root.fail("visible click targets or closed lifecycle")
-
- root.fullWidth = first.implicitWidth
- first.settings = ({ compact: true })
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 1) {
- if (root.phaseTicks < 3) return
- if (!first.compact || first.implicitWidth >= root.fullWidth)
- return root.fail("compact presentation width")
-
- first.interactionTarget.triggerPress(Qt.LeftButton)
- second.open()
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 2) {
- if (root.phaseTicks < 3) return
- if (!first.opened || !second.opened || !first.panelLoaded
- || !second.panelLoaded || sharedNetworkService.sessionCount !== 2
- || sharedNetworkService.beginCount !== 2
- || sharedNetworkService.viewLoadCount !== 2)
- return root.fail("two-output local panel sessions")
-
- first.interactionTarget.triggerPress(Qt.RightButton)
- if (!first.opened || !sharedNetworkService.lastScanWifi
- || sharedNetworkService.refreshCount !== 1)
- return root.fail("right-click scan forwarding")
-
- sharedNetworkService.kind = "ethernet"
- sharedNetworkService.label = "enp1s0"
- sharedNetworkService.downloadRate = 1536
- sharedNetworkService.uploadRate = 2 * 1024 * 1024
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 3) {
- if (root.phaseTicks < 2) return
- if (first.mode !== "ethernet" || second.label !== "enp1s0"
- || second.displayLabel !== "Ethernet"
- || !second.v1TrafficPresentation || second.v2TrafficPresentation
- || second.compactRate(second.downloadRate) !== "1.5K"
- || second.compactRate(second.uploadRate) !== "2.0M"
- || first.tooltipText.indexOf("Ethernet") !== 0)
- return root.fail("shared reactive ethernet state")
- const v2Tokens = ({})
- for (const key in fakeBar.visualTokens)
- v2Tokens[key] = fakeBar.visualTokens[key]
- v2Tokens.v2Shell = true
- fakeBar.visualTokens = v2Tokens
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 4) {
- if (root.phaseTicks < 2) return
- if (second.v1TrafficPresentation || !second.v2TrafficPresentation)
- return root.fail("V2 ethernet traffic presentation")
- first.close()
- secondLoader.active = false
- root.phase++
- root.phaseTicks = 0
- } else {
- if (sharedNetworkService.sessionCount !== 0
- || sharedNetworkService.endCount !== 2
- || first.panelLoaded || root.clickTargets.length !== 1)
- return root.fail("session and loader teardown")
- firstLoader.active = false
- Qt.callLater(function() {
- if (root.clickTargets.length !== 0)
- return root.fail("click target destruction cleanup")
- console.log("network widget smoke passed")
- Qt.quit()
- })
- stop()
- }
- }
- }
-}
diff --git a/tests/opencode-usage-regression.py b/tests/opencode-usage-regression.py
index 1be86b2..4766194 100755
--- a/tests/opencode-usage-regression.py
+++ b/tests/opencode-usage-regression.py
@@ -13,10 +13,7 @@
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
-SCRIPTS = [
- REPO_ROOT / "scripts" / "opencode-usage",
- REPO_ROOT / "hancore.shibumi.ai" / "scripts" / "opencode-usage",
-]
+SCRIPT = REPO_ROOT / "hancore.shibumi.ai" / "scripts" / "opencode-usage"
class OpenCodeUsageRegressionTests(unittest.TestCase):
@@ -72,24 +69,15 @@ def add_message(
def run_scripts(self) -> dict:
environment = os.environ.copy()
environment["OPENCODE_DATA_DIR"] = str(self.data_dir)
- outputs = []
- for script in SCRIPTS:
- result = subprocess.run(
- [str(script)],
- check=True,
- capture_output=True,
- text=True,
- env=environment,
- timeout=10,
- )
- outputs.append(json.loads(result.stdout))
- stable_outputs = []
- for output in outputs:
- stable = dict(output)
- stable.pop("updated_at", None)
- stable_outputs.append(stable)
- self.assertEqual(stable_outputs[0], stable_outputs[1])
- return outputs[0]
+ result = subprocess.run(
+ [str(SCRIPT)],
+ check=True,
+ capture_output=True,
+ text=True,
+ env=environment,
+ timeout=10,
+ )
+ return json.loads(result.stdout)
def test_models_and_latest_model_use_positive_today_scope(self) -> None:
now = datetime.now()
@@ -173,7 +161,7 @@ def test_local_day_bounds_follow_dst_midnights(self) -> None:
os.environ["TZ"] = "Europe/Berlin"
time.tzset()
loader = importlib.machinery.SourceFileLoader(
- "shibumi_opencode_usage", str(SCRIPTS[0])
+ "shibumi_opencode_usage", str(SCRIPT)
)
spec = importlib.util.spec_from_loader(loader.name, loader)
self.assertIsNotNone(spec)
diff --git a/tests/power-plugins-regression.sh b/tests/power-plugins-regression.sh
index 4e74e1c..5a92984 100755
--- a/tests/power-plugins-regression.sh
+++ b/tests/power-plugins-regression.sh
@@ -22,6 +22,7 @@ mkdir -p "$tmpdir/runtime" "$tmpdir/fixtures"
chmod 700 "$tmpdir/runtime"
cp -a -- "$repo_root/hancore.shibumi.battery" "$tmpdir/battery"
cp -a -- "$repo_root/hancore.shibumi.power-profile" "$tmpdir/powerProfile"
+cp -a -- "$repo_root/hancore.shibumi.power-state" "$tmpdir/powerState"
cp -a -- "$omarchy_path/shell/Commons" "$tmpdir/Commons"
cp -a -- "$omarchy_path/shell/Ui" "$tmpdir/Ui"
install -m 0644 "$repo_root/tests/fixtures/ShibumiPanelTest.qml" \
@@ -46,6 +47,31 @@ printf '%s\n' "$output"
grep -F 'power plugins smoke passed' <<<"$output" >/dev/null \
|| fail "success marker missing"
+mkdir -p "$tmpdir/power-runtime" "$tmpdir/power-bin"
+chmod 700 "$tmpdir/power-runtime"
+install -m 0755 "$repo_root"/tests/fixtures/power-bin/* "$tmpdir/power-bin/"
+printf 'balanced\n' >"$tmpdir/power-state"
+install -m 0644 "$repo_root/tests/power-service-runtime-smoke.qml" \
+ "$tmpdir/shell.qml"
+set +e
+service_output=$(timeout 8 env \
+ PATH="$tmpdir/power-bin:$PATH" \
+ SHIBUMI_POWER_FIXTURE_STATE="$tmpdir/power-state" \
+ QT_QPA_PLATFORM=offscreen \
+ WAYLAND_DISPLAY= \
+ XDG_RUNTIME_DIR="$tmpdir/power-runtime" \
+ QML_IMPORT_PATH="$omarchy_path/shell${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}" \
+ QML2_IMPORT_PATH="$omarchy_path/shell${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}" \
+ "$quickshell_bin" -p "$tmpdir" 2>&1)
+service_rc=$?
+set -e
+printf '%s\n' "$service_output"
+[[ $service_rc -eq 0 ]] || fail "power-state service smoke exited $service_rc"
+grep -F 'power service runtime smoke passed' <<<"$service_output" >/dev/null \
+ || fail "power-state service success marker missing"
+[[ $(<"$tmpdir/power-state") == performance ]] \
+ || fail "power-state service did not execute the validated profile action"
+
for plugin in battery power-profile; do
widget="$repo_root/hancore.shibumi.$plugin/BarWidget.qml"
rg -q 'serviceFor\("hancore\.shibumi\.power-state"\)' "$widget" \
diff --git a/tests/power-service-runtime-smoke.qml b/tests/power-service-runtime-smoke.qml
index 860895f..5687941 100644
--- a/tests/power-service-runtime-smoke.qml
+++ b/tests/power-service-runtime-smoke.qml
@@ -2,7 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
-import "services" as Services
+import "powerState" as PowerState
ShellRoot {
id: root
@@ -10,7 +10,7 @@ ShellRoot {
property int phase: 0
property int attempts: 0
- Services.PowerService { id: power }
+ PowerState.Service { id: power }
function fail(message) {
console.error("power-service-runtime-smoke:", message)
diff --git a/tests/power-widgets-smoke.qml b/tests/power-widgets-smoke.qml
deleted file mode 100644
index f03f2ca..0000000
--- a/tests/power-widgets-smoke.qml
+++ /dev/null
@@ -1,203 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import "widgets" as Widgets
-import "fixtures" as Fixtures
-import "services/PowerModel.js" as PowerModel
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property int ticks: 0
- property real batteryFullWidth: 0
- property real profileFullWidth: 0
-
- function fail(message) {
- console.error("power-widgets-smoke:", message)
- Qt.exit(1)
- }
-
- Fixtures.PowerTestService { id: power }
-
- QtObject {
- id: actions
- property int btopCalls: 0
- function openSystemMonitor() { btopCalls++; return true }
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 35
- property int sizeHorizontal: 35
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#eeeeee"
- property color barForeground: foreground
- property color background: "#191919"
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var powerService: power
- property var systemActions: actions
- property var visualTokens: ({
- pillHeight: 24,
- pillRadius: 12,
- pillPaddingX: 9,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- slotHeight: 28,
- contentGap: 5,
- compactGap: 4,
- labelSize: 12,
- iconSize: 15
- })
-
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) {
- var previous = activePopout
- activePopout = owner
- if (previous && previous !== owner && typeof previous.close === "function")
- previous.close()
- }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- function targetBelongsToWindow(_target, _window) { return true }
- }
-
- Loader {
- id: batteryA
- active: true
- sourceComponent: Component {
- Widgets.BatteryWidget {
- bar: fakeBar
- settings: ({ compact: false })
- panelSource: Qt.resolvedUrl("fixtures/PowerTestPanel.qml")
- }
- }
- }
- Loader {
- id: batteryB
- active: true
- sourceComponent: Component {
- Widgets.BatteryWidget {
- bar: fakeBar
- settings: ({ compact: true })
- panelSource: Qt.resolvedUrl("fixtures/PowerTestPanel.qml")
- }
- }
- }
- Loader {
- id: profileA
- active: true
- sourceComponent: Component {
- Widgets.PowerProfileWidget {
- bar: fakeBar
- settings: ({ compact: false })
- panelSource: Qt.resolvedUrl("fixtures/PowerTestPanel.qml")
- }
- }
- }
- Loader {
- id: profileB
- active: true
- sourceComponent: Component {
- Widgets.PowerProfileWidget {
- bar: fakeBar
- settings: ({ compact: true })
- panelSource: Qt.resolvedUrl("fixtures/PowerTestPanel.qml")
- }
- }
- }
-
- Timer {
- interval: 80
- repeat: true
- running: true
- onTriggered: {
- root.ticks++
- var batA = batteryA.item
- var batB = batteryB.item
- var pwrA = profileA.item
- var pwrB = profileB.item
- if (!batA || !batB || !pwrA || !pwrB) return
-
- if (root.phase === 0) {
- if (root.ticks < 3) return
- var parsed = PowerModel.parseProfiles(
- "power-saver\t0\nbalanced\t1\nperformance\t0\ninvalid name\t1\n")
- if (parsed.profiles.length !== 3 || parsed.activeProfile !== "balanced"
- || PowerModel.clampPercent(0.2) !== 20
- || PowerModel.duration(4260) !== "1h 11m")
- return root.fail("power model parsing")
- if (batA.visible || batA.implicitWidth !== 0
- || !pwrA.visible || pwrA.implicitWidth <= 0
- || power.profileConsumers !== 2)
- return root.fail("desktop without battery must retain both profile views")
- root.profileFullWidth = pwrA.implicitWidth
- if (pwrB.implicitWidth >= root.profileFullWidth)
- return root.fail("power-profile compact width")
- power.hasBattery = true
- root.phase++
- root.ticks = 0
- } else if (root.phase === 1) {
- if (root.ticks < 3) return
- if (!batA.visible || batA.implicitWidth <= 0 || batA.percent !== 20)
- return root.fail("laptop battery visibility/state")
- root.batteryFullWidth = batA.implicitWidth
- if (batB.implicitWidth >= root.batteryFullWidth)
- return root.fail("battery compact width")
- if (batA.powerService !== power || batB.powerService !== power
- || pwrA.powerService !== power || pwrB.powerService !== power)
- return root.fail("multi-output views do not share one service owner")
- if (typeof pwrA.childPanelWidget !== "function"
- || pwrA.childPanelWidget("omarchy.power") !== pwrA
- || typeof batA.childPanelWidget === "function"
- && batA.childPanelWidget("omarchy.power") !== null)
- return root.fail("canonical omarchy.power routing owner")
- batA.activate()
- root.phase++
- root.ticks = 0
- } else if (root.phase === 2) {
- if (root.ticks < 3) return
- if (!batA.opened || !batA.panelItem || power.detailConsumers !== 1
- || fakeBar.activePopout !== batA)
- return root.fail("battery panel/detail lifecycle: opened=" + batA.opened
- + " panel=" + batA.panelItem + " details=" + power.detailConsumers
- + " active=" + fakeBar.activePopout + " expected=" + batA)
- pwrA.activate(Qt.LeftButton)
- root.phase++
- root.ticks = 0
- } else if (root.phase === 3) {
- if (root.ticks < 3) return
- if (batA.opened || power.detailConsumers !== 0 || !pwrA.opened
- || !pwrA.panelItem || fakeBar.activePopout !== pwrA)
- return root.fail("cross-group popout ownership")
- pwrA.close()
- var before = power.setProfileCalls
- if (!pwrA.activate(Qt.RightButton)
- || power.setProfileCalls !== before + 1
- || power.activeProfile !== "performance")
- return root.fail("right-click profile cycle")
- batB.activate()
- power.hasBattery = false
- root.phase++
- root.ticks = 0
- } else if (root.phase === 4) {
- if (root.ticks < 3) return
- if (batB.opened || power.detailConsumers !== 0
- || batA.visible || batB.visible || !pwrA.visible || !pwrB.visible)
- return root.fail("battery removal must not remove power profiles")
- stop()
- console.log("power widgets smoke passed")
- Qt.quit()
- }
- }
- }
-}
diff --git a/tests/production-boundary-regression.py b/tests/production-boundary-regression.py
new file mode 100755
index 0000000..ef8ae71
--- /dev/null
+++ b/tests/production-boundary-regression.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+
+from __future__ import annotations
+
+import subprocess
+import tempfile
+import unittest
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+LINTER = ROOT / "scripts/check-production-boundary"
+
+
+class ProductionBoundaryRegressionTests(unittest.TestCase):
+ def run_linter(self, root: Path) -> subprocess.CompletedProcess[str]:
+ return subprocess.run(
+ [str(LINTER), "--root", str(root)],
+ cwd=ROOT,
+ check=False,
+ capture_output=True,
+ text=True,
+ )
+
+ def fixture_result(
+ self, source: str, name: str = "Service.qml"
+ ) -> subprocess.CompletedProcess[str]:
+ temporary = tempfile.TemporaryDirectory(prefix="shibumi-boundary-test.")
+ self.addCleanup(temporary.cleanup)
+ root = Path(temporary.name)
+ plugin = root / "hancore.shibumi.fixture"
+ plugin.mkdir()
+ (plugin / name).write_text(source, encoding="utf-8")
+ return self.run_linter(root)
+
+ def test_shipped_source_passes(self) -> None:
+ result = self.run_linter(ROOT)
+ self.assertEqual(result.returncode, 0, result.stderr)
+ self.assertIn("production boundary check passed", result.stdout)
+
+ def test_direct_forbidden_boundaries_fail_closed(self) -> None:
+ cases = {
+ "absolute-component": (
+ 'property string source: "/usr/share/omarchy/shell/plugins/'
+ 'services/example/Service.qml"\n',
+ "private component path",
+ ),
+ "relative-component": (
+ 'property string source: "shell/plugins/services/example.qml"\n',
+ "private component path",
+ ),
+ "registry-provider": (
+ 'property var backend: bar.registeredWidgetSource('
+ '"omarchy.example")\n',
+ "private provider QML",
+ ),
+ "first-party-service": (
+ 'property var backend: shell.firstPartyServiceFor('
+ '"omarchy.example")\n',
+ "undeclared first-party service",
+ ),
+ "helper-state": (
+ 'property var command: ["omarchy-example-status"]\n',
+ "helper-backed state polling",
+ ),
+ "root-escape": (
+ 'import "../services" as Services\n',
+ "package escape",
+ ),
+ }
+ for name, (source, expected) in cases.items():
+ with self.subTest(name=name):
+ result = self.fixture_result(source)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn(expected, result.stderr)
+
+ def test_all_executables_are_scanned_and_invalid_utf8_fails(self) -> None:
+ with tempfile.TemporaryDirectory(
+ prefix="shibumi-boundary-test."
+ ) as temporary:
+ root = Path(temporary)
+ plugin = root / "hancore.shibumi.fixture"
+ plugin.mkdir()
+ executable = plugin / "probe.bin"
+ executable.write_text("omarchy-example-status\n", encoding="utf-8")
+ executable.chmod(0o755)
+ result = self.run_linter(root)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn("helper-backed state polling", result.stderr)
+
+ executable.write_text("#!/bin/sh\npgrep example\n", encoding="utf-8")
+ result = self.run_linter(root)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn("helper-backed state polling", result.stderr)
+
+ executable.write_bytes(b"#!/bin/sh\n\xff\n")
+ result = self.run_linter(root)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn("undecodable production source", result.stderr)
+
+ def test_production_symlinks_fail_closed(self) -> None:
+ with tempfile.TemporaryDirectory(
+ prefix="shibumi-boundary-test."
+ ) as temporary:
+ root = Path(temporary)
+ payload = root / "payload"
+ payload.mkdir()
+ (payload / "Service.qml").write_text(
+ "property bool safe: true\n", encoding="utf-8"
+ )
+ plugin = root / "hancore.shibumi.fixture"
+ plugin.symlink_to(payload.name, target_is_directory=True)
+ result = self.run_linter(root)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn("production symlink", result.stderr)
+
+ with tempfile.TemporaryDirectory(
+ prefix="shibumi-boundary-test."
+ ) as temporary:
+ root = Path(temporary)
+ plugin = root / "hancore.shibumi.fixture"
+ plugin.mkdir()
+ target = plugin / "payload.bin"
+ target.write_text("property bool safe: true\n", encoding="utf-8")
+ (plugin / "Service.qml").symlink_to(target.name)
+ result = self.run_linter(root)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn("production symlink", result.stderr)
+
+ def test_removed_debt_requires_allowance_cleanup(self) -> None:
+ with tempfile.TemporaryDirectory(
+ prefix="shibumi-boundary-test."
+ ) as temporary:
+ root = Path(temporary)
+ plugin = root / "hancore.shibumi.status"
+ plugin.mkdir()
+ source = (
+ ROOT / "hancore.shibumi.status/Service.qml"
+ ).read_text(encoding="utf-8")
+ (plugin / "Service.qml").write_text(
+ source.replace("omarchy-voxtype-status", "voxtype-status"),
+ encoding="utf-8",
+ )
+ result = self.run_linter(root)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn("approved omarchy-voxtype-status", result.stderr)
+
+ def test_missing_allowlisted_file_fails_when_plugin_is_present(self) -> None:
+ with tempfile.TemporaryDirectory(
+ prefix="shibumi-boundary-test."
+ ) as temporary:
+ root = Path(temporary)
+ plugin = root / "hancore.shibumi.media"
+ plugin.mkdir()
+ (plugin / "manifest.json").write_text("{}\n", encoding="utf-8")
+ result = self.run_linter(root)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn(
+ "hancore.shibumi.media/Service.qml: stale boundary allowance",
+ result.stderr,
+ )
+
+ def test_complete_suite_rejects_allowance_for_missing_plugin(self) -> None:
+ with tempfile.TemporaryDirectory(
+ prefix="shibumi-boundary-test."
+ ) as temporary:
+ root = Path(temporary)
+ contracts = root / "contracts"
+ contracts.mkdir()
+ (contracts / "plugin-suite-v1.json").write_text(
+ "{}\n", encoding="utf-8"
+ )
+ result = self.run_linter(root)
+ self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
+ self.assertIn(
+ "hancore.shibumi.audio/BarWidget.qml: stale boundary allowance",
+ result.stderr,
+ )
+
+ def test_package_job_runs_boundary_regression_directly(self) -> None:
+ workflow = (
+ ROOT / ".github/workflows/package-release.yml"
+ ).read_text(encoding="utf-8")
+ self.assertIn(
+ "python3 tests/production-boundary-regression.py",
+ workflow,
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/quote-service-smoke.qml b/tests/quote-service-smoke.qml
index 59138d6..ae8cea4 100644
--- a/tests/quote-service-smoke.qml
+++ b/tests/quote-service-smoke.qml
@@ -6,14 +6,9 @@ ShellRoot {
id: root
property var receivedEvent: null
- QtObject {
- id: fakeBar
- property int reactorMode: 8
- }
-
Services.QuoteService {
id: quoteService
- bar: fakeBar
+ backendEnabled: true
}
Connections {
diff --git a/tests/shibumi-presentation-smoke.qml b/tests/shibumi-presentation-smoke.qml
deleted file mode 100644
index d682ecd..0000000
--- a/tests/shibumi-presentation-smoke.qml
+++ /dev/null
@@ -1,288 +0,0 @@
-import QtQuick
-import Quickshell
-import "services" as Services
-import "styles/shibumi" as ShibumiStyle
-import "widgets" as Widgets
-
-ShellRoot {
- id: root
-
- Services.SystemTelemetry { id: telemetry }
-
- QtObject {
- id: actions
- function openSystemMonitor() { return true }
- }
-
- QtObject {
- id: fakeStateService
- property var config: ({
- presentation: {
- border: true,
- shadow: false,
- frost: false,
- radius: "large",
- shellStyle: "shibumi"
- }
- })
- property color selectedColor: "#88aaff"
-
- function paletteColor(id) {
- return id === "color05" ? selectedColor : "#ffffff"
- }
-
- function paletteContrastColor(id) {
- return id === "color05" ? "#111111" : "#ffffff"
- }
- }
-
- QtObject {
- id: dynamicTooltipTarget
- property string tooltipText: "Audio 90%"
- }
-
- QtObject {
- id: fakeShell
- function serviceFor(pluginId) {
- return pluginId === "hancore.shibumi.state" ? fakeStateService : null
- }
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: style.sizeHorizontal
- property string position: "top"
- property string fontFamily: style.fontFamily
- property color foreground: style.foreground
- property color barForeground: style.barForeground
- property color background: style.background
- property color urgent: style.urgent
- property var visualTokens: style.visualTokens
- property var shell: fakeShell
- property var activePopout: null
- property var tooltipTarget: dynamicTooltipTarget
- property string tooltipText: "stale snapshot"
- property var systemTelemetry: telemetry
- property var gpuTelemetry: null
- property var systemActions: actions
-
- function showTooltip(target, text) {}
- function hideTooltip(target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(owner, direction) { return false }
- }
-
- ShibumiStyle.Style {
- id: style
- bar: fakeBar
- }
-
- Loader {
- id: tooltipSurface
- sourceComponent: style.tooltipSurfaceComponent
- }
-
- Widgets.MemoryWidget {
- id: memoryFull
- bar: fakeBar
- settings: ({ compact: false })
- }
-
- Widgets.MemoryWidget {
- id: memoryCompact
- bar: fakeBar
- settings: ({ compact: true })
- }
-
- Widgets.CpuWidget {
- id: cpuFull
- bar: fakeBar
- settings: ({ compact: false })
- }
-
- Widgets.PillSurface {
- id: pillProbe
- width: 100
- height: 24
- bar: fakeBar
- settings: ({ color: "color05", colorMode: "fill" })
- }
-
- Widgets.PillSurface {
- id: v1FillProbe
- width: 100
- height: 24
- bar: fakeBar
- v1AppearanceEnabled: true
- settings: ({
- color: "color05",
- colorMode: "fill",
- tone: "background",
- surfaceOpacity: 0.4
- })
- }
-
- Widgets.CpuWidget {
- id: cpuCompact
- bar: fakeBar
- settings: ({ compact: true })
- }
-
- function fail(message) {
- console.error("shibumi-presentation-smoke:", message)
- Qt.exit(1)
- }
-
- Timer {
- interval: 1000
- running: true
- onTriggered: {
- if (style.sizeHorizontal !== 35 || style.exclusiveSizeHorizontal !== 38)
- return root.fail("V1 bar geometry contract changed")
- if (style.visualTokens.islandHeight !== 32
- || style.visualTokens.pillHeight !== 24
- || style.visualTokens.pillRadius !== 12
- || style.visualTokens.panelRadius !== 12
- || style.visualTokens.panelBorderWidth !== 1
- || style.visualTokens.panelBackground.a < 0.93
- || style.visualTokens.panelBackground.a > 0.95
- || style.visualTokens.islandInsetX !== 5
- || style.visualTokens.islandContentInsetX !== 4
- || style.visualTokens.tooltipRadius !== 6
- || style.visualTokens.tooltipPaddingX !== 10
- || style.visualTokens.tooltipPaddingY !== 4
- || style.tooltipGap !== 6)
- return root.fail("V1 surface token contract changed")
- const outlineSettings = {
- colorMode: "border",
- color: "color01",
- widgetBorder: true,
- widgetBorderColor: "color05"
- }
- if (style.visualTokens.widgetBorderColor(outlineSettings)
- !== fakeStateService.selectedColor)
- return root.fail("widget outline ignored its selected palette color")
- if (style.visualTokens.widgetBorderColor({
- colorMode: "border",
- color: "color05",
- widgetBorder: true
- }) !== style.visualTokens.panelBorder)
- return root.fail("widget outline used the surface color without consent")
- if (style.visualTokens.widgetBorderColor({
- colorMode: "border",
- color: "inherit",
- widgetBorder: true,
- widgetBorderColor: "inherit"
- }) !== style.visualTokens.panelBorder)
- return root.fail("inherited widget outline lost the panel border token")
- if (style.visualTokens.widgetBorderColor({
- colorMode: "border",
- color: "color05",
- widgetBorder: true,
- widgetBorderUsesSurfaceColor: true
- }) !== fakeStateService.selectedColor)
- return root.fail("legacy coupled widget outline lost compatibility")
- if (style.visualTokens.widgetBorderWidth({
- widgetBorderWidth: 1.5
- }) !== 1.5)
- return root.fail("half-pixel widget outline width was rounded away")
- if (style.visualTokens.widgetBorderWidth({
- widgetBorderWidth: 0.5
- }) !== 0.5)
- return root.fail("minimum widget outline width was clamped away")
- if (style.visualTokens.widgetSurfaceOpacity({
- surfaceOpacity: 0.4
- }) !== 0.4)
- return root.fail("40 percent widget opacity was clamped away")
- if (memoryCompact.implicitWidth >= memoryFull.implicitWidth)
- return root.fail("memory compact presentation did not reduce width")
- if (cpuCompact.implicitWidth >= cpuFull.implicitWidth)
- return root.fail("CPU compact presentation did not reduce width")
- if (memoryFull.implicitHeight !== 35 || cpuFull.implicitHeight !== 35)
- return root.fail("widgets do not follow Shibumi bar height")
- if (pillProbe.renderedSurfaceCount !== 1
- || pillProbe.renderedShadowCount !== 0
- || !pillProbe.shellPillVisible
- || pillProbe.v1CustomFill
- || pillProbe.renderedFillColor !== style.visualTokens.pill)
- return root.fail("non-opted V1 pill did not retain its surface")
- if (!v1FillProbe.v1CustomFill
- || v1FillProbe.renderedSurfaceCount !== 1
- || Math.abs(v1FillProbe.v1FillOpacity - 0.4) > 0.001
- || Math.abs(v1FillProbe.renderedFillColor.r
- - fakeStateService.selectedColor.r) > 0.001
- || Math.abs(v1FillProbe.renderedFillColor.g
- - fakeStateService.selectedColor.g) > 0.001
- || Math.abs(v1FillProbe.renderedFillColor.b
- - fakeStateService.selectedColor.b) > 0.001
- || Math.abs(v1FillProbe.renderedFillColor.a - 0.4) > 0.001)
- return root.fail("V1 fill did not reach the native pill surface")
- const staleV2SurfaceSettings = {
- color: "color05",
- colorMode: "border",
- tone: "background"
- }
- if (!style.visualTokens.widgetHasFill(staleV2SurfaceSettings)
- || style.visualTokens.widgetContentColor(
- staleV2SurfaceSettings, "#ffffff") !== style.visualTokens.paper)
- return root.fail("hidden V2 surface state disabled V1 appearance")
- fakeStateService.config = ({
- presentation: {
- border: true,
- shadow: true,
- frost: true,
- radius: "large",
- shellStyle: "shibumi"
- }
- })
- if (pillProbe.renderedShadowCount !== 1
- || style.visualTokens.barBackground.a < 0.67
- || style.visualTokens.barBackground.a > 0.69
- || style.visualTokens.pill.a < 0.17
- || style.visualTokens.pill.a > 0.19
- || style.visualTokens.panelBackground.a < 0.93
- || style.visualTokens.panelBackground.a > 0.95)
- return root.fail("V1 Shadow or reference Frost alpha did not apply")
- fakeStateService.config = ({
- presentation: {
- border: true,
- shadow: true,
- frost: true,
- radius: "large",
- shellStyle: "full"
- }
- })
- if (pillProbe.renderedSurfaceCount !== 0
- || pillProbe.renderedShadowCount !== 0
- || pillProbe.shellPillVisible
- || v1FillProbe.v1CustomFill
- || style.visualTokens.widgetHasFill(staleV2SurfaceSettings))
- return root.fail("V1 widget appearance leaked into V2 shell")
- if (style.sizeHorizontal !== 33
- || style.exclusiveSizeHorizontal !== 36
- || style.visualTokens.panelRadius !== 6
- || style.visualTokens.tileRadius !== 10
- || style.visualTokens.shellFitRadius !== 6
- || style.visualTokens.shellDockRadius !== 8)
- return root.fail("V2 shell geometry contract changed")
- fakeStateService.config = ({
- presentation: {
- border: true,
- shadow: false,
- frost: false,
- radius: "large",
- shellStyle: "shibumi"
- }
- })
- if (!tooltipSurface.item || tooltipSurface.item.resolvedText !== "Audio 90%")
- return root.fail("dynamic tooltip target was not resolved")
- dynamicTooltipTarget.tooltipText = "Audio 100%"
- if (tooltipSurface.item.resolvedText !== "Audio 100%")
- return root.fail("visible tooltip did not update reactively")
- console.log("shibumi presentation smoke passed")
- Qt.exit(0)
- }
- }
-}
diff --git a/tests/status-widget-smoke.qml b/tests/status-widget-smoke.qml
deleted file mode 100644
index 01791a7..0000000
--- a/tests/status-widget-smoke.qml
+++ /dev/null
@@ -1,270 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import "widgets" as Widgets
-import "fixtures" as Fixtures
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property int phaseTicks: 0
- property real initialWidth: 0
- property var clickTargets: []
-
- function fail(message) {
- console.error("status-widget-smoke:", message)
- Qt.exit(1)
- }
-
- ListModel {
- id: pendingNotifications
- ListElement { summary: "First" }
- ListElement { summary: "Second" }
- ListElement { summary: "Third" }
- }
-
- QtObject {
- id: notificationService
- property var pendingModel: pendingNotifications
- property bool doNotDisturb: false
- function setDoNotDisturb(value) { doNotDisturb = value }
- function markAllSeen() {}
- function dismissPending(_index) {}
- }
-
- QtObject {
- id: fakeState
- property var writes: []
-
- function setWidgetSetting(groupId, moduleId, key, value) {
- writes = writes.concat([{
- groupId: String(groupId || ""),
- moduleId: String(moduleId || ""),
- key: String(key || ""),
- value: JSON.parse(JSON.stringify(value))
- }])
- return true
- }
- }
-
- QtObject {
- id: fakeShell
- function serviceFor(pluginId) {
- return pluginId === "hancore.shibumi.state" ? fakeState : null
- }
- function firstPartyServiceFor(pluginId) {
- return pluginId === "omarchy.notifications" ? notificationService : null
- }
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 35
- property string fontFamily: "monospace"
- property color foreground: "#eeeeee"
- property color barForeground: foreground
- property color background: "#111111"
- property color urgent: "#88bbee"
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var shell: fakeShell
- property var layoutConfig: ({ left: [], center: [], right: [] })
- property var clickTargets: root.clickTargets
- property var barWidgetRegistry: null
- property var visualTokens: ({
- v2Shell: false,
- pillHeight: 24,
- pillRadius: 12,
- pillPaddingX: 9,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false
- })
-
- function widgetSettings(group, module) {
- return group === "G3" ? ({
- marker: module,
- layoutRevision: Number(layoutConfig.revision || 0)
- }) : ({})
- }
- function registerClickTarget(target) {
- if (root.clickTargets.indexOf(target) < 0)
- root.clickTargets = root.clickTargets.concat([target])
- }
- function unregisterClickTarget(target) {
- root.clickTargets = root.clickTargets.filter(item => item !== target)
- }
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return true }
- function targetBelongsToWindow(_target, _window) { return true }
- }
-
- Component {
- id: childComponent
- Fixtures.StatusTestWidget {}
- }
-
- Loader {
- id: statusLoader
- active: true
- sourceComponent: Component {
- Widgets.StatusWidget {
- bar: fakeBar
- updateComponent: childComponent
- trayComponent: childComponent
- trayDrawerSource: Qt.resolvedUrl("fixtures/TrayDrawerTestPanel.qml")
- notificationPanelSource: Qt.resolvedUrl(
- "fixtures/NotificationPanelTestView.qml")
- }
- }
- }
-
- Timer {
- interval: 80
- repeat: true
- running: true
- onTriggered: {
- root.phaseTicks++
- const status = statusLoader.item
- if (root.phase === 0) {
- if (!status || !status.ready || root.phaseTicks < 3) return
- if (!status.visible
- || !status.updateWidget || !status.trayWidget
- || !status.notificationService || root.clickTargets.length !== 4)
- return root.fail("child lifecycle/readiness: visible=" + status.visible
- + ", update=" + !!status.updateWidget
- + ", tray=" + !!status.trayWidget
- + ", notifications=" + !!status.notificationService
- + ", trayModule=" + String(status.trayWidget
- ? status.trayWidget.moduleName : "")
- + ", trayPinned=" + Number(status.trayWidget
- ? status.trayWidget.pinnedItems.length : -1)
- + ", trayDrawer=" + Number(status.trayWidget
- ? status.trayWidget.drawerCount : -1)
- + ", clickTargets=" + root.clickTargets.length)
- if (status.updateWidget.moduleName !== "hancore.shibumi.update-center"
- || status.trayWidget.moduleName !== "omarchy.tray"
- || status.updateWidget.settings.marker !== "hancore.shibumi.update-center"
- || status.trayWidget.settings.marker !== "omarchy.tray")
- return root.fail("child identity/settings injection")
- if (status.childPanelWidget("hancore.shibumi.update-center")
- !== status.updateWidget
- || status.childPanelWidget("omarchy.notifications") !== status
- || !status.ownsPanelWidget(status.updateWidget)
- || status.childPanelWidget("omarchy.tray") !== null)
- return root.fail("nested panel routing")
- if (!status.updatePresented || !status.trayPresented
- || !status.notificationPresented
- || status.notificationService.pendingModel.count !== 3
- || status.pendingCount !== 3 || status.recentCount !== 0
- || status.notificationCount !== 3)
- return root.fail("V1 tray/notification facade state")
- if (status.v2Mode
- || status.horizontalInset !== 9
- || status.childGap !== 4
- || status.updateSlotWidth !== 22
- || status.traySlotWidth !== 42
- || status.notificationSlotWidth !== 22
- || status.trayPinnedIconOffset !== 1
- || status.notificationIconOffset !== 1
- || status.implicitWidth !== 112)
- return root.fail("V1 status geometry is not symmetric: inset="
- + Number(status.horizontalInset) + ", gap="
- + Number(status.childGap) + ", slots="
- + JSON.stringify([status.updateSlotWidth,
- status.traySlotWidth, status.notificationSlotWidth])
- + ", trayOffset=" + Number(status.trayPinnedIconOffset)
- + ", bellOffset=" + Number(status.notificationIconOffset)
- + ", width=" + Number(status.implicitWidth))
- status.updateWidget.open()
- if (!status.toggleTrayDrawer() || !status.trayDrawerOpen
- || status.updateWidget.popupOpen)
- return root.fail("tray drawer did not open")
-
- root.initialWidth = status.implicitWidth
- fakeBar.layoutConfig = ({
- left: [], center: [], right: [], revision: 1
- })
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 1) {
- if (!status.trayDrawerLoaded || root.phaseTicks < 3) return
- if (status.trayWidget.settings.layoutRevision !== 1)
- return root.fail("tray settings did not react to host layout changes")
- const drawer = status.trayDrawerItem
- if (drawer.ownerWidget !== status
- || drawer.trayBackend !== status.trayWidget
- || drawer.anchorItem === null
- || drawer.bar !== fakeBar)
- return root.fail("tray drawer injection contract")
- status.trayWidget.togglePin("fixture-drawer")
- if (status.trayWidget.pinToggleCount !== 1
- || fakeState.writes.length !== 2
- || fakeState.writes[0].groupId !== "G3"
- || fakeState.writes[0].moduleId !== "omarchy.tray"
- || fakeState.writes[0].key !== "pinned"
- || fakeState.writes[0].value[0] !== "fixture-drawer"
- || fakeState.writes[1].key !== "hidden")
- return root.fail("embedded tray pin did not persist into G3: "
- + JSON.stringify(fakeState.writes))
- status.closeTrayDrawer()
- status.trayWidget.pinnedItems = []
- status.trayWidget.drawerItems = []
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 2) {
- if (root.phaseTicks < 3) return
- if (status.trayDrawerLoaded)
- return root.fail("closed tray drawer remained loaded")
- if (status.implicitWidth >= root.initialWidth)
- return root.fail("hidden tray did not release width: initial="
- + root.initialWidth + ", current=" + status.implicitWidth
- + ", anyPresented=" + status.hasVisibleChild
- + ", rowWidth=" + status.contentWidth)
- if (!status.open() || !status.opened
- || !status.notificationPanelOpen)
- return root.fail("local notification panel did not open")
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 3) {
- if (!status.notificationPanelLoaded || root.phaseTicks < 3) return
- const notificationPanel = status.notificationPanelItem
- if (notificationPanel.ownerWidget !== status
- || notificationPanel.notificationService
- !== status.notificationService
- || notificationPanel.anchorItem === null
- || notificationPanel.bar !== fakeBar
- || notificationPanel.pendingCount !== 3
- || fakeBar.activePopout !== status)
- return root.fail("notification panel injection/popout ownership")
- status.trayWidget.managePopupOpen = true
- status.close()
- root.phase++
- root.phaseTicks = 0
- } else if (root.phase === 4) {
- if (root.phaseTicks < 3) return
- if (status.opened || status.notificationPanelLoaded
- || fakeBar.activePopout !== null)
- return root.fail("nested close cleanup")
- statusLoader.active = false
- root.phase++
- root.phaseTicks = 0
- } else {
- if (root.phaseTicks < 3) return
- if (root.clickTargets.length !== 0 || fakeBar.activePopout !== null)
- return root.fail("destruction cleanup")
- stop()
- console.log("status widget smoke passed")
- Qt.quit()
- }
- }
- }
-}
diff --git a/tests/test_package_release.py b/tests/test_package_release.py
index 08f1d20..bcd82a3 100644
--- a/tests/test_package_release.py
+++ b/tests/test_package_release.py
@@ -317,6 +317,7 @@ def test_release_archive_is_reproducible_and_complete(self) -> None:
255,
"gzip OS header must be independent of the Python host",
)
+ extracted = output / "archive-root"
with tarfile.open(archive, "r:gz") as payload:
names = payload.getnames()
private_home = b"/home/" + b"hancore/"
@@ -330,6 +331,7 @@ def test_release_archive_is_reproducible_and_complete(self) -> None:
stream.read(),
f"release payload exposes a private path: {member.name}",
)
+ payload.extractall(extracted, filter="data")
roots = {name.split("/", 1)[0] for name in names}
self.assertEqual(roots, {f"shibumi-shell-{inventory['version']}"})
self.assertFalse(
@@ -344,6 +346,18 @@ def test_release_archive_is_reproducible_and_complete(self) -> None:
)
manifests = [name for name in names if name.endswith("/manifest.json")]
self.assertEqual(len(manifests), 24)
+ boundary = subprocess.run(
+ [
+ str(ROOT / "scripts/check-production-boundary"),
+ "--root",
+ str(extracted / next(iter(roots))),
+ ],
+ cwd=ROOT,
+ check=False,
+ capture_output=True,
+ text=True,
+ )
+ self.assertEqual(boundary.returncode, 0, boundary.stderr)
if __name__ == "__main__":
diff --git a/tests/v2-feature-evidence-regression.sh b/tests/v2-feature-evidence-regression.sh
index 78ebfd6..1fb435f 100755
--- a/tests/v2-feature-evidence-regression.sh
+++ b/tests/v2-feature-evidence-regression.sh
@@ -18,7 +18,8 @@ jq -e '
and (.features | length) == 36
and (.features | all(
(.implementation | type) == "array" and (.implementation | length) > 0
- and (.tests | type) == "array" and (.tests | length) > 0))
+ and (.tests | type) == "array" and (.tests | length) > 0
+ and (.tests | length) == (.tests | unique | length)))
' "$evidence_contract" >/dev/null || fail "evidence schema is incomplete"
port_baseline=$(jq -r '.referenceBaseline' "$port_contract")
diff --git a/tests/workspace-model-regression.qml b/tests/workspace-model-regression.qml
index 9ce6098..dcf35e9 100644
--- a/tests/workspace-model-regression.qml
+++ b/tests/workspace-model-regression.qml
@@ -1,6 +1,6 @@
import QtQuick
import QtTest
-import "../services/WorkspaceModel.js" as WorkspaceModel
+import "../hancore.shibumi.workspaces/WorkspaceModel.js" as WorkspaceModel
TestCase {
name: "WorkspaceModel"
diff --git a/tests/workspace-widget-smoke.qml b/tests/workspace-widget-smoke.qml
deleted file mode 100644
index 310e4e1..0000000
--- a/tests/workspace-widget-smoke.qml
+++ /dev/null
@@ -1,173 +0,0 @@
-import QtQuick
-import Quickshell
-import "adapters" as Adapters
-import "services" as Services
-import "widgets" as Widgets
-
-ShellRoot {
- id: root
-
- property int phase: 0
- property int geometryWaits: 0
-
- function fail(message) {
- console.error("workspace-widget-smoke:", message)
- Qt.exit(1)
- }
-
- QtObject {
- id: fakeBar
- property bool vertical: false
- property int barSize: 28
- property string position: "top"
- property string fontFamily: "monospace"
- property color foreground: "#e8e8e8"
- property color barForeground: foreground
- property color background: "#181818"
- property color urgent: "#88bbee"
- property var visualTokens: ({
- pillHeight: 24,
- pillRadius: 12,
- pill: "#332f2f",
- pillBorder: "#555050",
- pillBorderWidth: 1,
- pillShadow: "#000000",
- shadowEnabled: false,
- contentGap: 5,
- labelSize: 12,
- presentation: ({ radius: "large" }),
- workspacePillPadding: function(style) { return style === "numbers" ? 2 : 4 }
- })
- property bool foregroundAnimationEnabled: false
- property var activePopout: null
- property var workspaceConfig: ({ version: 1, mode: "5", style: "numbers" })
- property var workspaceService: workspaceState
- property var workspaceActions: actions
- property string lastCommand: ""
-
- function run(command) { lastCommand = String(command || "") }
- function mutateShibumiConfig(mutator) {
- const next = {
- version: 1,
- workspace: JSON.parse(JSON.stringify(workspaceConfig))
- }
- mutator(next)
- workspaceConfig = next.workspace
- return true
- }
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
- function requestPopout(owner) { activePopout = owner }
- function releasePopout(owner) { if (activePopout === owner) activePopout = null }
- function switchPanelFrom(_owner, _direction) { return false }
- }
-
- Adapters.WorkspaceActions {
- id: actions
- bar: fakeBar
- }
-
- Services.WorkspaceService {
- id: workspaceState
- bar: fakeBar
- actionAdapter: actions
- config: fakeBar.workspaceConfig
- focusedWorkspaceSource: ({ id: 8 })
- workspaceSource: [
- { id: 2, toplevels: { values: [{}] } },
- { id: 8, toplevels: { values: [{}, {}] } },
- { id: -1, toplevels: { values: [{}] } }
- ]
- }
-
- Services.WorkspaceService {
- id: occupiedOutsidePersistState
- actionAdapter: actions
- config: ({ version: 1, mode: "5", style: "numbers" })
- focusedWorkspaceSource: ({ id: 2 })
- workspaceSource: [
- { id: 2, toplevels: { values: [{}] } },
- { id: 7, toplevels: { values: [{}] } }
- ]
- }
-
- Widgets.WorkspaceWidget {
- id: widget
- bar: fakeBar
- settings: ({})
- panelSource: Qt.resolvedUrl("WorkspaceTestPanel.qml")
- }
-
- Timer {
- interval: 60
- repeat: true
- running: true
- onTriggered: {
- if (root.phase === 0) {
- if (workspaceState.entries.length !== 2
- || workspaceState.visibleWorkspaceIds.join(",") !== "1,2,3,4,5,8"
- || occupiedOutsidePersistState.visibleWorkspaceIds.join(",")
- !== "1,2,3,4,5,7"
- || widget.renderedWorkspaceCount !== 6 || widget.panelLoaded)
- return root.fail("initial service/widget contract")
- if (widget.workspacePadding !== 2 || Math.round(widget.implicitWidth) !== 161)
- return root.fail("V1 numbers presentation geometry")
- if (!widget.activateWorkspace(8)
- || fakeBar.lastCommand !== "hyprctl dispatch 'hl.dsp.focus({ workspace = \"8\" })'")
- return root.fail("validated workspace dispatch")
- if (widget.workspaceTooltip(8)
- !== "Workspace 8 · 2 windows · Right-click for workspace panel")
- return root.fail("workspace tooltip contract")
- if (!actions.focusWorkspace(9999)
- || fakeBar.lastCommand
- !== "hyprctl dispatch 'hl.dsp.focus({ workspace = \"9999\" })'")
- return root.fail("workspace dispatch upper bound")
- fakeBar.lastCommand = ""
- if (actions.focusWorkspace(-1) || actions.focusWorkspace(1.5)
- || actions.focusWorkspace(10000)
- || actions.focusWorkspace("1; reboot")
- || fakeBar.lastCommand !== "")
- return root.fail("invalid workspace dispatch was not rejected")
- if (!workspaceState.setPreference("style", "magic")
- || !workspaceState.setPreference("mode", "active")
- || fakeBar.workspaceConfig.style !== "magic"
- || fakeBar.workspaceConfig.mode !== "active"
- || workspaceState.setPreference("mode", "unsafe"))
- return root.fail("workspace preference persistence")
- widget.open()
- } else if (root.phase === 1) {
- if (widget.workspaceStyle !== "magic"
- || workspaceState.visibleWorkspaceIds.join(",") !== "2,8"
- || widget.renderedWorkspaceCount !== 2)
- return root.fail("reactive workspace presentation")
- if (widget.workspacePadding !== 4 || Math.round(widget.implicitWidth) !== 51) {
- root.geometryWaits++
- if (root.geometryWaits < 10) return
- return root.fail("V1 magic presentation geometry: " + widget.implicitWidth)
- }
- root.geometryWaits = 0
- if (!widget.opened || !widget.panelLoaded || !widget.panelLoaderReady)
- return root.fail("lazy workspace panel open")
- widget.close()
- if (!workspaceState.setPreference("style", "default")
- || !workspaceState.setPreference("mode", "10"))
- return root.fail("workspace presentation reset")
- } else {
- if (widget.opened || widget.panelLoaded
- || widget.workspaceStyle !== "default"
- || workspaceState.visibleWorkspaceIds.length !== 10
- || widget.renderedWorkspaceCount !== 10)
- return root.fail("lazy workspace panel close")
- if (widget.workspacePadding !== 4 || Math.round(widget.implicitWidth) !== 229) {
- root.geometryWaits++
- if (root.geometryWaits < 10) return
- return root.fail("V1 default presentation geometry: " + widget.implicitWidth)
- }
- stop()
- console.log("workspace widget smoke passed")
- Qt.quit()
- }
- root.phase++
- }
- }
-}
diff --git a/tests/workspaces-plugin-regression.sh b/tests/workspaces-plugin-regression.sh
index fcbc151..6afa9f2 100755
--- a/tests/workspaces-plugin-regression.sh
+++ b/tests/workspaces-plugin-regression.sh
@@ -17,10 +17,6 @@ fail() {
[[ -d $omarchy_path/shell ]] || fail "Omarchy shell not found: $omarchy_path/shell"
[[ -x $quickshell_bin ]] || fail "Quickshell not found: $quickshell_bin"
-cmp -s "$repo_root/services/WorkspaceModel.js" \
- "$repo_root/hancore.shibumi.workspaces/WorkspaceModel.js" \
- || fail "standalone and plugin workspace models drifted"
-
mkdir -p "$tmpdir/runtime" "$tmpdir/fixtures"
chmod 700 "$tmpdir/runtime"
cp -a -- "$repo_root/hancore.shibumi.workspaces" "$tmpdir/workspaces"
diff --git a/widgets/AiUsagePanel.qml b/widgets/AiUsagePanel.qml
deleted file mode 100644
index 0525919..0000000
--- a/widgets/AiUsagePanel.qml
+++ /dev/null
@@ -1,545 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var aiService
- readonly property var provider: aiService ? aiService.selectedProvider : null
- readonly property int renderedProviderCount: providerTabs.count
- readonly property bool primaryUsageVisible: primaryUsage.visible
- readonly property bool secondaryUsageVisible: secondaryUsage.visible
- readonly property var providerModels: provider
- && Array.isArray(provider.models) ? provider.models : []
- readonly property int renderedModelCount: modelRepeater.count
- readonly property bool providerReady: provider && provider.ready !== undefined
- ? provider.ready === true : true
- readonly property bool providerHasUsage: provider
- && (Number(provider.rateLimitPercent) >= 0
- || Number(provider.secondaryRateLimitPercent) >= 0)
- readonly property bool providerHasCurrentData: provider && aiService
- && typeof aiService.providerHasCurrentData === "function"
- ? aiService.providerHasCurrentData(provider)
- : provider && (providerHasUsage
- || Number(provider.todayTotalTokens) > 0
- || Number(provider.todayPrompts) > 0
- || Number(provider.todaySessions) > 0
- || providerModels.length > 0)
- readonly property string providerEmptyStateText: !provider
- ? "No supported AI usage data was found."
- : providerHasCurrentData ? ""
- : aiService && typeof aiService.providerCurrentDataMessage === "function"
- ? aiService.providerCurrentDataMessage(provider)
- : String(provider.authHelpText || "")
- || "No current usage or limit data."
-
- owner: ownerWidget
- open: ownerWidget.opened
- focusTarget: keyCatcher
- contentWidth: fittedContentWidth(Commons.Style.space(360))
- contentHeight: fittedContentHeight(contentColumn.implicitHeight,
- Commons.Style.space(560))
-
- function moveProvider(direction) {
- if (aiService) aiService.cycleTool(direction)
- }
-
- function resetText(timestamp) {
- return provider && aiService
- ? aiService.resetText(provider, timestamp) : ""
- }
-
- function providerTabLabel(providerValue) {
- if (!providerValue) return "AI"
- const id = String(providerValue.providerId || "")
- if (id === "claude") return "Claude"
- if (id === "codex") return "Codex"
- if (id === "opencode") return "OpenCode"
- return String(providerValue.providerName || id || "AI")
- }
-
- function providerHeading(providerValue) {
- if (!providerValue) return "AI"
- const id = String(providerValue.providerId || "")
- if (id === "claude") return "Claude Code"
- if (id === "codex") return "OpenAI Codex"
- if (id === "opencode") return "OpenCode"
- return String(providerValue.providerName || id || "AI")
- }
-
- function tierLabel(providerValue) {
- if (!providerValue) return ""
- return aiService && typeof aiService.displayTierLabel === "function"
- ? aiService.displayTierLabel(providerValue.tierLabel)
- : String(providerValue.tierLabel || "")
- }
-
- function displayPercent(providerValue, value) {
- return aiService && typeof aiService.displayPercent === "function"
- ? aiService.displayPercent(providerValue, value) : Number(value)
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
- onMoveRequested: function(dx, _dy) { if (dx !== 0) panel.moveProvider(dx) }
-
- Flickable {
- id: scroller
- anchors.fill: parent
- contentWidth: width
- contentHeight: contentColumn.implicitHeight
- clip: true
- boundsBehavior: Flickable.StopAtBounds
- interactive: contentHeight > height
-
- Column {
- id: contentColumn
- width: scroller.width
- spacing: Commons.Style.space(8)
-
- Item {
- id: header
- width: parent.width
- height: Commons.Style.space(28)
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "AI USAGE"
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.subtitle
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Row {
- id: actionRow
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(2)
-
- IconAction {
- icon: panel.aiService && panel.aiService.refreshing
- ? "sync" : "refresh"
- tooltip: panel.aiService && panel.aiService.refreshing
- ? "Refreshing usage" : "Refresh usage"
- action: function() { panel.aiService.refreshAll(true) }
- }
-
- IconAction {
- icon: "close"
- tooltip: "Close"
- action: function() { panel.ownerWidget.close() }
- }
- }
- }
-
- Row {
- id: providerRow
- width: parent.width
- height: Commons.Style.space(28)
- spacing: Commons.Style.space(6)
-
- Repeater {
- id: providerTabs
- model: panel.aiService ? panel.aiService.providers : []
-
- Rectangle {
- id: providerTab
- required property var modelData
- readonly property bool selected:
- panel.aiService.selectedTool === modelData.providerId
- readonly property bool hovered: tabMouse.containsMouse
- width: (providerRow.width - Math.max(0,
- (panel.aiService.providers.length - 1) * providerRow.spacing))
- / Math.max(1, panel.aiService.providers.length)
- height: parent.height
- radius: panel.controlRadius
- color: selected ? panel.controlActiveFillColor
- : hovered ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: selected || hovered ? panel.controlAccent
- : panel.controlBorderColor
-
- Behavior on color { ColorAnimation { duration: 100 } }
- Behavior on border.color { ColorAnimation { duration: 100 } }
-
- Text {
- anchors.centerIn: parent
- text: panel.providerTabLabel(providerTab.modelData)
- color: providerTab.selected || providerTab.hovered
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- font.weight: providerTab.selected ? Font.Medium : Font.Normal
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: tabMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.aiService.selectTool(providerTab.modelData.providerId)
- }
- }
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Item {
- visible: panel.provider !== null
- width: parent.width
- height: visible ? Commons.Style.space(16) : 0
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: panel.provider ? panel.providerHeading(panel.provider)
- + (panel.tierLabel(panel.provider) !== ""
- ? " · " + panel.tierLabel(panel.provider) : "") : ""
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: panel.providerReady ? "live" : "stale"
- color: panel.providerReady ? panel.controlMuted
- : panel.controlAccent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
-
- Text {
- visible: panel.providerEmptyStateText !== ""
- width: parent.width
- text: panel.providerEmptyStateText
- wrapMode: Text.WordWrap
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- renderType: Text.NativeRendering
- }
-
- UsageRow {
- id: primaryUsage
- visible: panel.provider && Number(panel.provider.rateLimitPercent) >= 0
- label: panel.provider ? String(panel.provider.rateLimitLabel || "Primary") : ""
- value: panel.provider ? panel.displayPercent(panel.provider,
- panel.provider.rateLimitPercent) : 0
- dimmed: !panel.providerReady
- }
-
- UsageRow {
- id: secondaryUsage
- visible: panel.provider && Number(panel.provider.secondaryRateLimitPercent) >= 0
- label: panel.provider
- ? String(panel.provider.secondaryRateLimitLabel || "Secondary") : ""
- value: panel.provider ? panel.displayPercent(panel.provider,
- panel.provider.secondaryRateLimitPercent) : 0
- dimmed: !panel.providerReady
- }
-
- DetailRow {
- visible: primaryUsage.visible && panel.resetText(
- panel.provider.rateLimitResetAt) !== ""
- label: primaryUsage.label + " resets in"
- value: visible ? panel.resetText(panel.provider.rateLimitResetAt) : ""
- }
- DetailRow {
- visible: secondaryUsage.visible && panel.resetText(
- panel.provider.secondaryRateLimitResetAt) !== ""
- label: secondaryUsage.label + " resets in"
- value: visible ? panel.resetText(
- panel.provider.secondaryRateLimitResetAt) : ""
- }
- DetailRow {
- visible: panel.provider && String(panel.provider.usageStatusText || "") !== ""
- label: panel.provider && String(panel.provider.providerId || "") === "codex"
- ? "General limit" : "Status"
- value: panel.provider ? String(panel.provider.usageStatusText || "") : ""
- }
- DetailRow {
- visible: panel.provider && Number(panel.provider.windowTokens) > 0
- label: panel.provider && String(panel.provider.providerId || "")
- === "opencode" ? "5h tokens" : "Tokens"
- value: visible && panel.aiService
- ? panel.aiService.formatTokens(panel.provider.windowTokens) : ""
- }
- DetailRow {
- visible: panel.provider && Number(panel.provider.hourlyTokens) > 0
- label: panel.provider && String(panel.provider.providerId || "")
- === "opencode" ? "1h rate" : "Rate"
- value: visible && panel.aiService
- ? panel.aiService.formatTokens(panel.provider.hourlyTokens) + "/h" : ""
- }
- DetailRow {
- visible: panel.provider && Number(panel.provider.todayTotalTokens) > 0
- label: "Today"
- value: visible && panel.aiService
- ? panel.aiService.formatTokens(panel.provider.todayTotalTokens) + " tokens" : ""
- }
- DetailRow {
- visible: panel.provider && Number(panel.provider.todayPrompts) > 0
- label: "Today prompts"
- value: visible ? String(Math.round(
- Number(panel.provider.todayPrompts) || 0)) : ""
- }
- DetailRow {
- visible: panel.provider && Number(panel.provider.todaySessions) > 0
- label: "Today sessions"
- value: visible ? String(Math.round(
- Number(panel.provider.todaySessions) || 0)) : ""
- }
- DetailRow {
- visible: panel.provider && String(panel.provider.latestModel || "") !== ""
- label: panel.provider && String(panel.provider.providerId || "")
- === "opencode" ? "Latest today" : "Latest"
- value: panel.provider ? String(panel.provider.latestModel || "") : ""
- }
-
- Item {
- visible: panel.providerModels.length > 0
- width: parent.width
- height: visible ? Commons.Style.space(16) : 0
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "MODELS"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 1
- renderType: Text.NativeRendering
- }
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: panel.provider && String(panel.provider.providerId || "")
- === "opencode" ? "today" : "recent"
- color: panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
-
- Repeater {
- id: modelRepeater
- model: panel.providerModels
- delegate: ModelUsageRow {
- required property var modelData
- width: contentColumn.width
- entry: modelData
- }
- }
- }
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: iconAction
- required property string icon
- required property string tooltip
- required property var action
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: iconAction.icon
- color: iconAction.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: iconAction.hasCursor = containsMouse
- onClicked: iconAction.action()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: iconAction.tooltip !== "" && actionMouse.containsMouse
- text: iconAction.tooltip
- }
- }
-
- component UsageRow: Item {
- id: usageRow
- required property string label
- required property real value
- property bool dimmed: false
- width: parent.width
- height: Commons.Style.space(16)
-
- Text {
- id: usageLabel
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: usageRow.label
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- font.letterSpacing: 1
- renderType: Text.NativeRendering
- }
-
- Text {
- id: usageValue
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: Math.round(usageRow.value) + "%"
- color: usageRow.dimmed ? panel.controlMuted : panel.controlAccent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Rectangle {
- anchors.left: usageLabel.right
- anchors.leftMargin: Commons.Style.space(8)
- anchors.right: usageValue.left
- anchors.rightMargin: Commons.Style.space(8)
- anchors.verticalCenter: parent.verticalCenter
- height: Commons.Style.space(8)
- radius: height / 2
- color: Commons.Util.alpha(panel.controlAccent, 0.15)
- Rectangle {
- width: parent.width * Math.max(0, Math.min(100, usageRow.value)) / 100
- height: parent.height
- radius: height / 2
- color: panel.controlAccent
- Behavior on width { NumberAnimation { duration: 300; easing.type: Easing.OutCubic } }
- }
- }
- }
-
- component DetailRow: Row {
- required property string label
- required property string value
- width: parent.width
- height: Commons.Style.space(16)
-
- Text {
- width: parent.width * 0.45
- text: parent.label
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- renderType: Text.NativeRendering
- }
- Text {
- width: parent.width * 0.55
- horizontalAlignment: Text.AlignRight
- elide: Text.ElideLeft
- text: parent.value
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- renderType: Text.NativeRendering
- }
- }
-
- component ModelUsageRow: Item {
- id: modelRow
- required property var entry
- height: Commons.Style.space(42)
- readonly property real percent: Math.max(0, Math.min(100,
- Number(entry && entry.pct) || 0))
-
- Text {
- id: modelName
- anchors.left: parent.left
- anchors.top: parent.top
- width: parent.width * 0.68
- text: String(modelRow.entry && modelRow.entry.name || "")
- elide: Text.ElideRight
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: parent.right
- anchors.top: parent.top
- text: String(modelRow.entry && modelRow.entry.totalLabel || "")
- color: panel.controlAccent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Rectangle {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: modelName.bottom
- anchors.topMargin: Commons.Style.space(5)
- height: Commons.Style.space(6)
- radius: height / 2
- color: Commons.Util.alpha(panel.controlAccent, 0.14)
-
- Rectangle {
- width: parent.width * modelRow.percent / 100
- height: parent.height
- radius: height / 2
- color: panel.controlAccent
- Behavior on width { NumberAnimation { duration: 300 } }
- }
- }
-
- Text {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- text: "I " + String(modelRow.entry && modelRow.entry.inputLabel || "0")
- + " O " + String(modelRow.entry && modelRow.entry.outputLabel || "0")
- + (String(modelRow.entry && modelRow.entry.reasoningLabel || "0") !== "0"
- ? " R " + String(modelRow.entry.reasoningLabel) : "")
- + (String(modelRow.entry && modelRow.entry.cacheReadLabel || "0") !== "0"
- ? " CR " + String(modelRow.entry.cacheReadLabel) : "")
- + (String(modelRow.entry && modelRow.entry.cacheWriteLabel || "0") !== "0"
- ? " CW " + String(modelRow.entry.cacheWriteLabel) : "")
- + (String(modelRow.entry && modelRow.entry.todayLabel || "0") !== "0"
- ? " today " + String(modelRow.entry.todayLabel) : "")
- elide: Text.ElideRight
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.space(9)
- renderType: Text.NativeRendering
- }
- }
-}
diff --git a/widgets/AiUsageWidget.qml b/widgets/AiUsageWidget.qml
deleted file mode 100644
index 16f9f8e..0000000
--- a/widgets/AiUsageWidget.qml
+++ /dev/null
@@ -1,211 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-import "../menu" as Menu
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.ai"
- manageIpc: false
- property url panelSource: Qt.resolvedUrl("AiUsagePanel.qml")
- readonly property var aiService: bar ? bar.aiUsageService : null
- readonly property var provider: aiService ? aiService.selectedProvider : null
- readonly property string providerId: provider
- ? String(provider.providerId || "") : ""
- readonly property int usagePercent: aiService
- ? aiService.usagePercent(provider) : -1
- readonly property int steppedPercent: usagePercent < 0
- ? 0 : Math.round(usagePercent / 5) * 5
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property var interactionTarget: actionButton
- readonly property bool panelLoaded: panelLoader.item !== null
- readonly property var panelItem: panelLoader.item
- readonly property string tooltipText: aiService
- ? aiService.tooltipText() : "No AI usage providers detected"
-
- visible: aiService && aiService.providers.length > 0
- implicitWidth: visible ? (bar && bar.vertical
- ? bar.barSize : aiSurface.implicitWidth) : 0
- implicitHeight: visible ? (bar ? bar.barSize : Commons.Style.space(35)) : 0
-
- function syncPanelLoader() {
- if (!opened) {
- panelLoader.source = ""
- return
- }
- panelLoader.setSource(panelSource, {
- anchorItem: aiSurface,
- bar: root.bar,
- ownerWidget: root,
- aiService: root.aiService
- })
- }
-
- function childPanelWidget(pluginId) {
- const id = String(pluginId || "")
- return id === moduleName || id === "omarchy.agents"
- || id === "omarchy.model-usage" ? root : null
- }
-
- onOpenedChanged: syncPanelLoader()
-
- Item {
- id: aiSurface
- anchors.centerIn: parent
- implicitWidth: root.bar && root.bar.vertical
- ? root.bar.barSize : contentRow.implicitWidth + 2 * (root.tokens ? root.tokens.pillPaddingX : 9)
- implicitHeight: root.tokens ? root.tokens.slotHeight : Commons.Style.space(28)
- width: implicitWidth
- height: implicitHeight
-
- PillSurface {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- bar: root.bar
- }
-
- Ui.WidgetButton {
- id: actionButton
- anchors.fill: parent
- bar: root.bar
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- fixedWidth: aiSurface.width
- fixedHeight: aiSurface.height
- tooltipText: root.tooltipText
- onPressed: function(button) {
- if (!root.aiService) return
- if (button === Qt.RightButton) root.aiService.refreshAll(true)
- else if (button === Qt.MiddleButton) root.aiService.cycleTool(1)
- else root.toggle()
- }
- onWheelMoved: function(delta) {
- if (root.aiService) root.aiService.cycleTool(delta > 0 ? -1 : 1)
- }
- }
-
- Row {
- id: contentRow
- anchors.centerIn: parent
- spacing: root.tokens ? root.tokens.compactGap : Commons.Style.space(5)
-
- Item {
- id: providerIcon
- anchors.verticalCenter: parent.verticalCenter
- width: root.providerId === "opencode" ? 20
- : root.providerId === "codex" ? 14 : 15
- height: root.providerId === "opencode" ? 12
- : root.providerId === "codex" ? 14 : 15
-
- Item {
- anchors.centerIn: parent
- width: 15
- height: 15
- visible: root.providerId === "claude"
-
- Text {
- anchors.centerIn: parent
- text: "\udb85\ude7a"
- color: root.bar ? Qt.rgba(root.bar.foreground.r,
- root.bar.foreground.g, root.bar.foreground.b, 0.25)
- : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 14
- renderType: Text.QtRendering
- }
-
- Item {
- width: parent.width
- height: root.steppedPercent > 0
- ? Math.min(parent.height, Math.max(parent.height * root.steppedPercent / 100,
- parent.height * 0.25)) : 0
- anchors.bottom: parent.bottom
- clip: true
- Behavior on height { NumberAnimation { duration: 600; easing.type: Easing.OutCubic } }
-
- Text {
- width: providerIcon.width
- height: providerIcon.height
- anchors.bottom: parent.bottom
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- text: "\udb85\ude7a"
- color: root.bar ? root.bar.urgent : Commons.Color.urgent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 14
- renderType: Text.QtRendering
- }
- }
- }
-
- Item {
- anchors.fill: parent
- visible: root.providerId === "codex" || root.providerId === "opencode"
-
- Menu.TintedImage {
- anchors.fill: parent
- source: root.providerId === "opencode"
- ? Qt.resolvedUrl("../assets/opencode-mark.svg")
- : Qt.resolvedUrl("../assets/codex.svg")
- sourceSize: root.providerId === "opencode"
- ? Qt.size(20, 12) : Qt.size(56, 56)
- smooth: root.providerId !== "opencode"
- mipmap: root.providerId !== "opencode"
- tint: root.bar ? Qt.rgba(root.bar.foreground.r,
- root.bar.foreground.g, root.bar.foreground.b,
- root.providerId === "codex" ? 0.65 : 0.5)
- : Commons.Color.foreground
- }
-
- Item {
- width: parent.width
- height: root.steppedPercent > 0
- ? Math.min(parent.height, Math.max(parent.height * root.steppedPercent / 100,
- parent.height * 0.22)) : 0
- anchors.bottom: parent.bottom
- clip: true
- Behavior on height { NumberAnimation { duration: 600; easing.type: Easing.OutCubic } }
-
- Menu.TintedImage {
- width: providerIcon.width
- height: providerIcon.height
- anchors.bottom: parent.bottom
- source: root.providerId === "opencode"
- ? Qt.resolvedUrl("../assets/opencode-mark.svg")
- : Qt.resolvedUrl("../assets/codex.svg")
- sourceSize: root.providerId === "opencode"
- ? Qt.size(20, 12) : Qt.size(56, 56)
- smooth: root.providerId !== "opencode"
- mipmap: root.providerId !== "opencode"
- tint: root.bar ? root.bar.urgent : Commons.Color.urgent
- }
- }
- }
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: root.usagePercent >= 0
- ? String(root.usagePercent).padStart(2, "0") + "%" : "··"
- color: root.bar ? Qt.rgba(root.bar.foreground.r,
- root.bar.foreground.g, root.bar.foreground.b, 0.88)
- : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
- }
- }
- }
-
- Loader {
- id: panelLoader
- }
-}
diff --git a/widgets/AudioPanel.qml b/widgets/AudioPanel.qml
deleted file mode 100644
index 741f568..0000000
--- a/widgets/AudioPanel.qml
+++ /dev/null
@@ -1,630 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell.Services.Pipewire
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var audioBackend
-
- property var displaySinks: []
- property var displaySources: []
- property var displayStreams: []
- readonly property real outputVolume: audioBackend
- ? Number(audioBackend.outputVolume || 0) : 0
- readonly property bool outputMuted: audioBackend
- ? audioBackend.outputMuted === true : false
- readonly property real inputVolume: audioBackend
- ? Number(audioBackend.inputVolume || 0) : 0
- readonly property bool inputMuted: audioBackend
- ? audioBackend.inputMuted === true : false
- readonly property real microphoneLevel: inputMuted || !audioBackend
- ? 0 : peakToMeter(inputPeakMonitor.peak)
- readonly property int renderedSinkCount: displaySinks.length
- readonly property int renderedSourceCount: displaySources.length
- readonly property int renderedStreamCount: displayStreams.length
-
- owner: ownerWidget
- open: ownerWidget.opened && audioBackend && audioBackend.ready
- focusTarget: keyCatcher
- gap: 8
- padding: 12
- contentWidth: fittedContentWidth(280)
- contentHeight: fittedContentHeight(contentColumn.implicitHeight,
- Commons.Style.space(580))
-
- function listSnapshot(values) {
- const result = []
- if (!values) return result
- for (let i = 0; i < values.length; i++) {
- if (values[i]) result.push(values[i])
- }
- return result
- }
-
- function refreshModels() {
- if (!open || !audioBackend) return
- displaySinks = listSnapshot(audioBackend.audioSinks)
- displaySources = listSnapshot(audioBackend.audioSources)
- displayStreams = listSnapshot(audioBackend.audioStreams)
- }
-
- function scheduleRefresh() {
- if (open) modelRefresh.restart()
- }
-
- function clearModels() {
- modelRefresh.stop()
- displaySinks = []
- displaySources = []
- displayStreams = []
- }
-
- function sameNode(left, right) {
- if (!left || !right) return false
- if (left === right) return true
- return left.id !== undefined && right.id !== undefined
- && String(left.id) === String(right.id)
- }
-
- function peakToMeter(peak) {
- const value = Number(peak || 0)
- if (!isFinite(value) || value <= 0) return 0
- const amplitude = value * value * value
- const db = 20 * Math.log(amplitude) / Math.LN10
- const noiseFloor = -55
- if (db <= noiseFloor) return 0
- return Math.max(0, Math.min(1, (db - noiseFloor) / -noiseFloor))
- }
-
- function adjustOutput(delta) {
- if (audioBackend) audioBackend.setOutputVolume(outputVolume + delta)
- }
-
- function sliderKnobColor(muted, sliderEnabled) {
- const stateOpacity = (muted ? 0.55 : 1)
- * (sliderEnabled ? 1 : 0.5)
- return Commons.Util.alpha(controlAccent, controlAccent.a * stateOpacity)
- }
-
- onOpenChanged: {
- if (open) {
- refreshModels()
- Qt.callLater(refreshModels)
- } else {
- clearModels()
- }
- }
-
- Item {
- width: 0
- height: 0
- visible: false
-
- Connections {
- target: panel.audioBackend
- function onAudioSinksChanged() { panel.scheduleRefresh() }
- function onAudioSourcesChanged() { panel.scheduleRefresh() }
- function onAudioStreamsChanged() { panel.scheduleRefresh() }
- }
-
- Timer {
- id: modelRefresh
- interval: 75
- repeat: false
- onTriggered: panel.refreshModels()
- }
-
- PwNodePeakMonitor {
- id: inputPeakMonitor
- node: panel.audioBackend ? panel.audioBackend.source : null
- enabled: panel.open && panel.audioBackend && panel.audioBackend.source !== null
- }
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
- onMoveRequested: function(dx, _dy) {
- if (dx !== 0) panel.adjustOutput(dx * 0.05)
- }
-
- Flickable {
- id: scroller
- anchors.fill: parent
- clip: true
- contentWidth: width
- contentHeight: contentColumn.implicitHeight
- boundsBehavior: Flickable.StopAtBounds
- interactive: contentHeight > height
-
- Column {
- id: contentColumn
- width: scroller.width
- spacing: 8
-
- Item {
- width: parent.width
- height: 24
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "Volume"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 13
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: "\u2715"
- color: closeMouse.containsMouse
- ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
-
- Behavior on color { ColorAnimation { duration: 120 } }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.ownerWidget.close()
- }
- }
- }
-
- PanelDivider {}
-
- Column {
- id: outputControl
- width: parent.width
- spacing: Commons.Style.space(2)
-
- SectionLabel { text: "OUTPUT" }
-
- Item {
- width: parent.width
- height: Commons.Style.space(30)
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- text: panel.outputMuted ? "Muted"
- : Math.round(panel.outputVolume * 100) + "%"
- color: panel.outputMuted
- ? Commons.Util.alpha(panel.controlAccent, 0.4)
- : panel.controlAccent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Ui.PanelSlider {
- id: outputSlider
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- bar: panel.bar
- maximum: 1
- value: panel.outputVolume
- trackColor: panel.controlActiveFillColor
- fillColor: panel.outputMuted
- ? Commons.Util.alpha(panel.controlAccent, 0.4)
- : panel.controlAccent
- knobColor: panel.sliderKnobColor(panel.outputMuted,
- outputSlider.enabled)
- enabled: panel.audioBackend && panel.audioBackend.sink !== null
- onMoved: function(value) {
- panel.audioBackend.setOutputVolume(value)
- }
- }
- }
- }
-
- Text {
- width: parent.width
- visible: panel.displaySinks.length > 0
- text: "OUTPUT DEVICE"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.52)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 10
- font.letterSpacing: 1
- renderType: Text.NativeRendering
- }
-
- Column {
- width: parent.width
- spacing: 4
- visible: panel.displaySinks.length > 0
- Repeater {
- model: panel.displaySinks
-
- DeviceRow {
- required property var modelData
- required property int index
- width: contentColumn.width
- node: modelData
- sourceDevice: false
- rowIndex: index
- }
- }
- }
-
- PanelDivider {}
-
- ActionButton {
- label: panel.outputMuted ? "Unmute volume" : "Mute volume"
- active: panel.outputMuted
- action: function() { panel.audioBackend.toggleOutputMute() }
- }
-
- PanelDivider { visible: panel.displayStreams.length > 0 }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(8)
- visible: panel.displayStreams.length > 0
-
- SectionLabel { text: "APPS" }
-
- Repeater {
- model: panel.displayStreams
-
- StreamRow {
- required property var modelData
- required property int index
- width: contentColumn.width
- node: modelData
- rowIndex: index
- }
- }
- }
-
- PanelDivider { visible: panel.audioBackend && panel.audioBackend.source !== null }
-
- Column {
- width: parent.width
- spacing: 8
- visible: panel.audioBackend && panel.audioBackend.source !== null
-
- Column {
- id: inputControl
- width: parent.width
- spacing: Commons.Style.space(2)
-
- SectionLabel { text: "INPUT" }
-
- Item {
- width: parent.width
- height: inputState.implicitHeight + Commons.Style.space(4)
- + inputSlider.implicitHeight + Commons.Style.space(8)
-
- Row {
- id: inputState
- width: parent.width
-
- Text {
- width: parent.width / 2
- text: "Microphone"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.68)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width / 2
- text: panel.inputMuted ? "Muted"
- : Math.round(panel.inputVolume * 100) + "%"
- color: panel.inputMuted
- ? Commons.Util.alpha(panel.controlAccent, 0.4)
- : panel.controlAccent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- font.weight: Font.Medium
- horizontalAlignment: Text.AlignRight
- renderType: Text.NativeRendering
- }
- }
-
- Ui.PanelSlider {
- id: inputSlider
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: inputState.bottom
- anchors.topMargin: Commons.Style.space(4)
- bar: panel.bar
- maximum: 1
- value: panel.inputVolume
- trackColor: panel.controlActiveFillColor
- fillColor: panel.inputMuted
- ? Commons.Util.alpha(panel.controlAccent, 0.4)
- : panel.controlAccent
- knobColor: panel.sliderKnobColor(panel.inputMuted,
- inputSlider.enabled)
- enabled: panel.audioBackend && panel.audioBackend.source !== null
- onMoved: function(value) {
- panel.audioBackend.setInputVolume(value)
- }
- }
-
- Rectangle {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: inputSlider.bottom
- anchors.topMargin: Commons.Style.space(4)
- height: Commons.Style.space(4)
- radius: height / 2
- color: panel.controlFillColor
- border.color: panel.controlBorderColor
- border.width: 1
-
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: parent.width * panel.microphoneLevel
- radius: parent.radius
- color: panel.inputMuted
- ? Commons.Util.alpha(panel.controlAccent, 0.25)
- : panel.controlAccent
- Behavior on width {
- NumberAnimation { duration: 70; easing.type: Easing.OutCubic }
- }
- }
- }
- }
- }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(4)
- visible: panel.displaySources.length > 1
-
- Text {
- text: "INPUT DEVICE"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.52)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 1
- renderType: Text.NativeRendering
- }
-
- Repeater {
- model: panel.displaySources
-
- DeviceRow {
- required property var modelData
- required property int index
- width: contentColumn.width
- node: modelData
- sourceDevice: true
- rowIndex: index
- }
- }
- }
-
- ActionButton {
- label: panel.inputMuted ? "Unmute mic" : "Mute mic"
- active: panel.inputMuted
- action: function() { panel.audioBackend.toggleInputMute() }
- }
- }
- }
- }
- }
-
- component SectionLabel: Text {
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 10
- font.letterSpacing: 1
- renderType: Text.NativeRendering
- }
-
- component PanelDivider: Rectangle {
- width: contentColumn.width
- height: 1
- color: panel.dividerColor
- }
-
- component ActionButton: Rectangle {
- id: actionButton
- required property string label
- required property var action
- property bool active: false
- width: contentColumn.width
- height: 28
- radius: panel.controlRadius
- color: active
- ? panel.controlActiveFillColor
- : actionMouse.containsMouse ? panel.controlHoverFillColor
- : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: active
- ? panel.bar ? panel.bar.urgent : Commons.Color.accent
- : actionMouse.containsMouse ? panel.controlHoverBorderColor
- : panel.controlBorderColor
-
- Text {
- anchors.centerIn: parent
- text: actionButton.label
- color: parent.active || actionMouse.containsMouse
- ? panel.bar ? panel.bar.urgent : Commons.Color.accent
- : panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: actionButton.action()
- }
- }
-
- component DeviceRow: Rectangle {
- id: deviceRow
- required property var node
- required property bool sourceDevice
- required property int rowIndex
- readonly property var activeNode: sourceDevice
- ? panel.audioBackend.source : panel.audioBackend.sink
- readonly property bool current: panel.sameNode(node, activeNode)
- width: contentColumn.width
- height: 26
- radius: panel.controlRadius
- color: current
- ? panel.controlActiveFillColor
- : deviceMouse.containsMouse ? panel.controlHoverFillColor
- : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: current
- ? panel.bar ? panel.bar.urgent : Commons.Color.accent
- : deviceMouse.containsMouse ? panel.controlHoverBorderColor
- : panel.controlBorderColor
-
- Row {
- anchors.fill: parent
- anchors.leftMargin: 8
- anchors.rightMargin: 8
- spacing: 6
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: deviceRow.current ? "●" : "○"
- color: deviceRow.current
- ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 10
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- width: parent.width - 22
- text: panel.audioBackend.nodeLabel(deviceRow.node)
- color: deviceRow.current || deviceMouse.containsMouse
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- font.weight: deviceRow.current ? Font.Medium : Font.Normal
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
-
- MouseArea {
- id: deviceMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- if (deviceRow.sourceDevice)
- panel.audioBackend.setDefaultSource(deviceRow.node)
- else panel.audioBackend.setDefaultSink(deviceRow.node)
- }
- }
- }
-
- component StreamRow: Item {
- id: streamRow
- required property var node
- required property int rowIndex
- readonly property var audio: node ? node.audio : null
- width: contentColumn.width
- height: Commons.Style.space(32)
-
- IconText {
- id: muteIcon
- anchors.left: parent.left
- anchors.top: parent.top
- width: Commons.Style.space(20)
- text: streamRow.audio && streamRow.audio.muted ? "volume_off" : "volume_up"
- color: streamRow.audio && streamRow.audio.muted && panel.bar
- ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.4)
- : panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.pixelSize: Commons.Style.font.body
- fill: 1
-
- MouseArea {
- anchors.fill: parent
- anchors.margins: -Commons.Style.space(3)
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.audioBackend.toggleStreamMute(streamRow.node)
- }
- }
-
- Text {
- anchors.left: muteIcon.right
- anchors.leftMargin: Commons.Style.space(5)
- anchors.right: streamPercent.left
- anchors.rightMargin: Commons.Style.space(5)
- anchors.verticalCenter: muteIcon.verticalCenter
- text: panel.audioBackend.streamLabel(streamRow.node)
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- opacity: streamRow.audio && streamRow.audio.muted ? 0.46 : 1
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- id: streamPercent
- anchors.right: parent.right
- anchors.verticalCenter: muteIcon.verticalCenter
- text: Math.round(Number(streamRow.audio ? streamRow.audio.volume : 0) * 100) + "%"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Ui.PanelSlider {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- bar: panel.bar
- maximum: 1
- value: streamRow.audio ? Number(streamRow.audio.volume || 0) : 0
- trackColor: panel.controlActiveFillColor
- fillColor: streamRow.audio && streamRow.audio.muted
- ? Commons.Util.alpha(panel.controlAccent, 0.4)
- : panel.controlAccent
- knobColor: streamRow.audio && streamRow.audio.muted
- ? Commons.Util.alpha(panel.controlAccent, 0.55)
- : panel.controlAccent
- onMoved: function(value) {
- panel.audioBackend.setStreamVolume(streamRow.node, value)
- }
- }
- }
-}
diff --git a/widgets/AudioWidget.qml b/widgets/AudioWidget.qml
deleted file mode 100644
index cae4123..0000000
--- a/widgets/AudioWidget.qml
+++ /dev/null
@@ -1,316 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-import "../adapters" as Adapters
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.audio"
- manageIpc: false
- property url popupSource: Qt.resolvedUrl("AudioPanel.qml")
- readonly property url backendPanelSource: registeredSource("omarchy.audio")
- property Component panelComponent: String(backendPanelSource) ? null
- : registeredComponent("omarchy.audio")
-
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool compact: setting("compact", false) === true
- readonly property var audioPanel: audioBridge.panel
- readonly property var panelItem: popupLoader.item
- readonly property bool panelLoaded: panelItem !== null
- readonly property bool audioReady: audioBridge.ready
- property bool wheelAdjustmentPending: false
- property real wheelTargetVolume: 0
- readonly property real displayedOutputVolume: wheelAdjustmentPending
- ? wheelTargetVolume : audioBridge.outputVolume
- readonly property int volume: Math.round(displayedOutputVolume * 100)
- readonly property bool muted: audioBridge.outputMuted
- readonly property string tooltipText: !audioReady ? "Audio unavailable"
- : muted ? "Muted · " + volume + "%" : "Audio " + volume + "%"
- readonly property var interactionTarget: actionButton
-
- visible: audioReady
- implicitWidth: visible ? (bar && bar.vertical ? bar.barSize : audioSurface.implicitWidth) : 0
- implicitHeight: visible
- ? (bar && bar.vertical ? audioSurface.implicitHeight : bar ? bar.barSize : 28) : 0
-
- function setting(name, fallback) {
- const value = settings ? settings[name] : undefined
- return value === undefined || value === null ? fallback : value
- }
-
- function registeredComponent(id) {
- if (bar && typeof bar.registeredWidgetComponent === "function")
- return bar.registeredWidgetComponent(id)
- const registry = bar ? bar.barWidgetRegistry : null
- if (registry) void(registry.revision)
- const widgets = registry && registry.widgets ? registry.widgets : ({})
- const entry = widgets[id]
- return entry ? entry.component : null
- }
-
- function registeredSource(id) {
- return bar && typeof bar.registeredWidgetSource === "function"
- ? bar.registeredWidgetSource(id) : ""
- }
-
- function officialSettings() {
- return bar && typeof bar.widgetSettings === "function"
- ? bar.widgetSettings("G6", "omarchy.audio") : ({})
- }
-
- function childPanelWidget(pluginId) {
- return String(pluginId || "") === "omarchy.audio" ? root : null
- }
-
- function ownsPanelWidget(owner) {
- return !!owner && (owner === root || owner === audioPanel)
- }
-
- function toggleOutputMute() { return audioBridge.toggleOutputMute() }
- function setOutputVolume(value) { return audioBridge.setOutputVolume(value) }
- function adjustVolume(delta) {
- if (!audioReady || Number(delta || 0) === 0) return false
- const base = wheelAdjustmentPending
- ? wheelTargetVolume : audioBridge.outputVolume
- wheelTargetVolume = Math.max(0, Math.min(1,
- Math.round((base + Number(delta)) * 100) / 100))
- wheelAdjustmentPending = true
- wheelCommitTimer.restart()
- return true
- }
-
- function syncPanelLoader() {
- popupLoader.source = ""
- if (!opened || !audioReady || !String(popupSource)) return
- popupLoader.setSource(popupSource, {
- anchorItem: audioSurface,
- bar: root.bar,
- ownerWidget: root,
- audioBackend: audioBridge
- })
- }
-
- onOpenedChanged: syncPanelLoader()
- onAudioReadyChanged: {
- if (opened) syncPanelLoader()
- if (!audioReady) {
- wheelCommitTimer.stop()
- wheelSettleTimer.stop()
- wheelAdjustmentPending = false
- }
- }
- onPopupSourceChanged: if (opened) syncPanelLoader()
- Component.onDestruction: close()
-
- Adapters.AudioPanelBridge {
- id: audioBridge
- anchors.fill: audioSurface
- bar: root.bar
- ownerWidget: root
- panelComponent: root.panelComponent
- panelSource: root.backendPanelSource
- panelSettings: root.officialSettings()
- }
-
- Loader { id: popupLoader }
-
- Timer {
- id: wheelCommitTimer
- interval: 70
- onTriggered: {
- if (!root.setOutputVolume(root.wheelTargetVolume)) {
- root.wheelAdjustmentPending = false
- return
- }
- wheelSettleTimer.restart()
- }
- }
-
- Timer {
- id: wheelSettleTimer
- interval: 300
- onTriggered: root.wheelAdjustmentPending = false
- }
-
- Item {
- id: audioSurface
- anchors.centerIn: parent
- implicitWidth: !root.bar || !root.tokens ? 0
- : root.bar.vertical
- ? root.bar.barSize : content.implicitWidth + 2 * root.tokens.pillPaddingX
- implicitHeight: !root.bar || !root.tokens ? 0
- : root.bar.vertical
- ? content.implicitHeight + Commons.Style.space(10)
- : root.tokens.slotHeight
- width: implicitWidth
- height: implicitHeight
-
- Loader {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- active: root.bar !== null && root.tokens !== null
- sourceComponent: Component {
- PillSurface {
- anchors.fill: parent
- bar: root.bar
- }
- }
- }
-
- Loader {
- id: content
- anchors.centerIn: parent
- sourceComponent: !root.bar || !root.tokens ? null
- : root.bar.vertical ? verticalContent
- : root.compact ? compactHorizontalContent : fullHorizontalContent
- }
-
- Ui.WidgetButton {
- id: actionButton
- anchors.fill: parent
- bar: root.audioReady ? root.bar : null
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- fixedWidth: audioSurface.width
- fixedHeight: audioSurface.height
- tooltipText: root.tooltipText
- onPressed: function(button) {
- if (button === Qt.RightButton) root.toggleOutputMute()
- else root.toggle()
- }
- onWheelMoved: function(delta) {
- if (delta !== 0) root.adjustVolume(delta > 0 ? 0.05 : -0.05)
- }
- }
- }
-
- Component {
- id: fullHorizontalContent
-
- Row {
- spacing: root.tokens.contentGap
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: "VOL"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, root.muted ? 0.25 : 0.6) : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: 0.5
- renderType: Text.NativeRendering
- }
-
- Item {
- anchors.verticalCenter: parent.verticalCenter
- width: Commons.Style.space(34)
- height: Commons.Style.space(14)
-
- readonly property real ratio: root.muted ? 0 : Math.min(root.volume / 100, 1)
-
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- width: parent.width
- height: Commons.Style.space(8)
- radius: height / 2
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.18) : Commons.Color.background
- }
-
- Rectangle {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- width: Math.max(parent.ratio > 0 ? Commons.Style.space(8) : 0,
- parent.width * parent.ratio)
- height: Commons.Style.space(8)
- radius: height / 2
- color: root.bar ? root.bar.urgent : Commons.Color.accent
-
- Behavior on width {
- NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
- }
- }
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- width: Commons.Style.space(30)
- text: String(root.volume).padStart(2, "0") + "%"
- color: root.bar ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, root.muted ? 0.35 : 1) : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- horizontalAlignment: Text.AlignRight
- renderType: Text.NativeRendering
-
- Behavior on color { ColorAnimation { duration: 160 } }
- }
- }
- }
-
- Component {
- id: compactHorizontalContent
-
- Row {
- spacing: root.tokens.compactGap
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: "graphic_eq"
- color: root.bar ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, root.muted ? 0.35 : 1) : Commons.Color.accent
- font.pixelSize: root.tokens.iconSize
- font.weight: Font.Medium
- fill: 1
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- width: Commons.Style.space(30)
- text: String(root.volume).padStart(2, "0") + "%"
- color: root.bar ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, root.muted ? 0.35 : 1) : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- horizontalAlignment: Text.AlignLeft
- renderType: Text.NativeRendering
-
- Behavior on color { ColorAnimation { duration: 160 } }
- }
- }
- }
-
- Component {
- id: verticalContent
-
- Column {
- spacing: Commons.Style.space(2)
-
- IconText {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "graphic_eq"
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.pixelSize: root.tokens.iconSize
- font.weight: Font.Medium
- fill: 1
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: String(root.volume).padStart(2, "0") + "%"
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
- }
-}
diff --git a/widgets/BatteryPanel.qml b/widgets/BatteryPanel.qml
deleted file mode 100644
index bd17995..0000000
--- a/widgets/BatteryPanel.qml
+++ /dev/null
@@ -1,208 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var powerService
-
- owner: ownerWidget
- open: ownerWidget.opened && powerService.hasBattery
- focusTarget: keyCatcher
- contentWidth: fittedContentWidth(Commons.Style.space(300))
- contentHeight: fittedContentHeight(column.implicitHeight)
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
-
- Column {
- id: column
- width: parent.width
- spacing: Commons.Style.space(8)
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(4)
- Text {
- width: parent.width - closeAction.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- text: "Battery"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.heading
- font.weight: Font.Medium
- }
- IconAction {
- id: closeAction
- anchors.verticalCenter: parent.verticalCenter
- icon: "close"
- tooltip: "Close"
- onClicked: panel.ownerWidget.close()
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.18) : Commons.Color.popups.border
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: panel.powerService.percent + "%"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.heading
- font.weight: Font.Medium
- }
-
- Rectangle {
- width: parent.width
- height: Commons.Style.space(8)
- radius: height / 2
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.12) : Commons.Color.background
- Rectangle {
- width: parent.width * panel.powerService.percent / 100
- height: parent.height
- radius: height / 2
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- Behavior on width { NumberAnimation { duration: 300 } }
- }
- }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(5)
- BatteryInfoRow { label: "Status"; value: panel.powerService.batteryStatus }
- BatteryInfoRow {
- visible: panel.powerService.timeText !== ""
- label: panel.powerService.charging ? "Time to full" : "Time left"
- value: panel.powerService.timeText
- }
- BatteryInfoRow {
- visible: panel.powerService.healthSupported
- label: "Health"
- value: panel.powerService.healthPercent + "%"
- }
- BatteryInfoRow {
- visible: panel.powerService.changeRate > 0
- label: panel.powerService.charging ? "Charge rate" : "Power draw"
- value: panel.powerService.changeRate.toFixed(1) + " W"
- }
- BatteryInfoRow {
- visible: panel.powerService.batteryInfo.size !== undefined
- label: "Battery size"
- value: String(panel.powerService.batteryInfo.size || "")
- }
- BatteryInfoRow {
- visible: panel.powerService.batteryInfo.cycles !== undefined
- label: "Charge cycles"
- value: String(panel.powerService.batteryInfo.cycles || "")
- }
- BatteryInfoRow {
- visible: panel.powerService.batteryInfo.threshold !== undefined
- label: "Charge threshold"
- value: String(panel.powerService.batteryInfo.threshold || "")
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.18) : Commons.Color.popups.border
- }
-
- Rectangle {
- width: parent.width
- height: Commons.Style.space(30)
- radius: panel.controlRadius
- color: btopMouse.containsMouse && panel.bar
- ? Qt.lighter(panel.bar.urgent, 1.08)
- : panel.bar ? panel.bar.urgent : Commons.Color.accent
- Text {
- anchors.centerIn: parent
- text: "Open btop"
- color: panel.bar ? panel.bar.background : Commons.Color.background
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- }
- MouseArea {
- id: btopMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- panel.ownerWidget.close()
- panel.ownerWidget.openSystemMonitor()
- }
- }
- }
- }
- }
-
- component BatteryInfoRow: Row {
- required property string label
- required property string value
- height: Commons.Style.space(16)
- Text {
- width: parent.width * 0.42
- text: parent.label
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.65) : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- }
- Text {
- width: parent.width * 0.58
- text: parent.value
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- elide: Text.ElideRight
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-}
diff --git a/widgets/BatteryWidget.qml b/widgets/BatteryWidget.qml
deleted file mode 100644
index f1293b6..0000000
--- a/widgets/BatteryWidget.qml
+++ /dev/null
@@ -1,302 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.battery"
- manageIpc: false
- property url panelSource: Qt.resolvedUrl("BatteryPanel.qml")
-
- readonly property var powerService: bar ? bar.powerService : null
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool compact: setting("compact", false) === true
- readonly property bool hasBattery: !!(powerService && powerService.hasBattery)
- readonly property int percent: powerService ? powerService.percent : 0
- readonly property bool charging: !!(powerService && powerService.charging)
- readonly property bool full: !!(powerService && powerService.fullyCharged)
- readonly property bool low: hasBattery && !charging && !full && percent <= 20
- readonly property string tooltipText: powerService
- ? powerService.batteryStatus + " · " + percent + "%"
- + (powerService.timeText ? " · " + powerService.timeText : "")
- : "Battery unavailable"
- property var detailOwner: null
- readonly property var interactionTarget: interaction
- readonly property var panelItem: panelLoader.item
-
- visible: hasBattery
- implicitWidth: visible ? (bar && bar.vertical ? bar.barSize : surface.implicitWidth) : 0
- implicitHeight: visible
- ? (bar && bar.vertical ? surface.implicitHeight : bar ? bar.barSize : 28) : 0
-
- function syncDetailLease() {
- var wanted = opened && hasBattery ? powerService : null
- if (wanted === detailOwner) return
- if (detailOwner) detailOwner.releaseBatteryDetails()
- detailOwner = wanted
- if (detailOwner) detailOwner.acquireBatteryDetails()
- }
-
- function syncPanelLoader() {
- if (!opened || !hasBattery) {
- panelLoader.source = ""
- return
- }
- panelLoader.setSource(panelSource, {
- anchorItem: surface,
- bar: root.bar,
- ownerWidget: root,
- powerService: root.powerService
- })
- }
-
- function activate() { toggle() }
-
- function openSystemMonitor() {
- if (!bar || typeof bar.run !== "function") return false
- bar.run("omarchy-launch-or-focus-tui btop")
- return true
- }
-
- onOpenedChanged: {
- syncDetailLease()
- syncPanelLoader()
- }
- onHasBatteryChanged: {
- if (!hasBattery && opened) close()
- syncDetailLease()
- }
- onPowerServiceChanged: syncDetailLease()
- Component.onDestruction: {
- if (detailOwner) detailOwner.releaseBatteryDetails()
- detailOwner = null
- }
-
- Item {
- id: surface
- anchors.centerIn: parent
- implicitWidth: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? root.bar.barSize
- : content.implicitWidth + 2 * root.tokens.pillPaddingX
- implicitHeight: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? content.implicitHeight + Commons.Style.space(10)
- : root.tokens.slotHeight
- width: implicitWidth
- height: implicitHeight
-
- PillSurface {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- bar: root.bar
- }
-
- Loader {
- id: content
- anchors.centerIn: parent
- sourceComponent: !root.bar || !root.tokens ? null
- : root.compact || root.bar.vertical ? compactContent : fullContent
- }
-
- MouseArea {
- id: interaction
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar) root.bar.showTooltip(surface, root.tooltipText)
- onExited: if (root.bar) root.bar.hideTooltip(surface)
- onClicked: {
- if (root.bar) root.bar.hideTooltip(surface)
- root.activate()
- }
- }
- }
-
- Loader { id: panelLoader }
-
- Component {
- id: fullContent
- Row {
- spacing: root.tokens.contentGap
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: "BAT"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.6) : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: 0.5
- renderType: Text.NativeRendering
- }
- BatteryGauge {
- anchors.verticalCenter: parent.verticalCenter
- ratio: root.percent / 100
- charging: root.charging
- full: root.full
- low: root.low
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- paper: root.bar ? root.bar.background : Commons.Color.background
- }
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: root.percent + "%"
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-
- Component {
- id: compactContent
- Row {
- spacing: root.tokens.compactGap
- BatteryGauge {
- anchors.verticalCenter: parent.verticalCenter
- ratio: root.percent / 100
- charging: root.charging
- full: root.full
- low: root.low
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- paper: root.bar ? root.bar.background : Commons.Color.background
- }
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: !root.bar.vertical
- text: root.percent + "%"
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-
- component BatteryGauge: Item {
- id: gauge
- required property real ratio
- required property bool charging
- required property bool full
- required property bool low
- required property color color
- required property color paper
-
- width: Commons.Style.space(19)
- height: Commons.Style.space(10)
- opacity: low ? pulse : 1
- property real pulse: 1
-
- SequentialAnimation on pulse {
- running: gauge.visible && gauge.low
- loops: Animation.Infinite
- NumberAnimation { from: 1; to: 0.35; duration: 1100; easing.type: Easing.InOutSine }
- NumberAnimation { from: 0.35; to: 1; duration: 1100; easing.type: Easing.InOutSine }
- }
-
- Item {
- id: batteryVisual
- anchors.centerIn: parent
- width: Commons.Style.space(19)
- height: Commons.Style.space(10)
-
- Rectangle {
- id: batteryBody
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- width: Commons.Style.space(16)
- height: Commons.Style.space(9)
- radius: Commons.Style.space(2.5)
- color: "transparent"
- border.width: Commons.Style.space(1.2)
- border.color: gauge.color
-
- Rectangle {
- visible: gauge.charging
- anchors.fill: parent
- anchors.margins: Commons.Style.space(1.8)
- radius: Commons.Style.space(1.2)
- color: Qt.rgba(gauge.color.r, gauge.color.g, gauge.color.b, 0.28)
- }
-
- Rectangle {
- id: batteryFill
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.margins: Commons.Style.space(1.8)
- width: Math.max(gauge.ratio > 0 ? Commons.Style.space(1.5) : 0,
- (parent.width - Commons.Style.space(3.6)) * Math.max(0, Math.min(1, gauge.ratio)))
- radius: Commons.Style.space(1.2)
- clip: true
- color: gauge.color
- Behavior on width { NumberAnimation { duration: 320; easing.type: Easing.OutCubic } }
-
- Rectangle {
- visible: gauge.charging && !gauge.full
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: Commons.Style.space(6)
- radius: parent.radius
- color: Qt.rgba(1, 1, 1, 0.18)
- property real pos: 0
- x: (parent.width + width) * pos - width
-
- NumberAnimation on pos {
- running: gauge.visible && gauge.charging && !gauge.full
- from: 0
- to: 1
- duration: 1100
- easing.type: Easing.InOutSine
- }
- }
- }
-
- Canvas {
- id: chargingBolt
- visible: gauge.charging || gauge.full
- anchors.centerIn: parent
- width: Commons.Style.space(6)
- height: Commons.Style.space(8)
-
- onPaint: {
- var ctx = getContext("2d")
- ctx.clearRect(0, 0, width, height)
- ctx.beginPath()
- ctx.moveTo(width * 0.55, 0)
- ctx.lineTo(width * 0.12, height * 0.55)
- ctx.lineTo(width * 0.45, height * 0.55)
- ctx.lineTo(width * 0.38, height)
- ctx.lineTo(width * 0.88, height * 0.45)
- ctx.lineTo(width * 0.55, height * 0.45)
- ctx.closePath()
- ctx.fillStyle = gauge.paper
- ctx.fill()
- }
-
- Component.onCompleted: requestPaint()
- Connections {
- target: gauge
- function onPaperChanged() { chargingBolt.requestPaint() }
- }
- }
- }
-
- Rectangle {
- anchors.left: batteryBody.right
- anchors.leftMargin: -Commons.Style.space(0.5)
- anchors.verticalCenter: parent.verticalCenter
- width: Commons.Style.space(2.5)
- height: Commons.Style.space(5)
- radius: Commons.Style.space(1.2)
- color: gauge.color
- }
- }
- }
-}
diff --git a/widgets/BluetoothPanel.qml b/widgets/BluetoothPanel.qml
deleted file mode 100644
index 6c57cb7..0000000
--- a/widgets/BluetoothPanel.qml
+++ /dev/null
@@ -1,551 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var bluetoothService
- property int selectedIndex: 0
- property bool cursorActive: false
- property bool actionFocused: false
- property string selectedAddress: ""
-
- readonly property var rows: {
- const result = []
- const append = function(section, values) {
- const list = Array.isArray(values) ? values : []
- for (let i = 0; i < list.length; i++)
- if (list[i]) result.push({ section: section, device: list[i] })
- }
- append("connected", bluetoothService.connectedDevices)
- append("paired", bluetoothService.knownDevices)
- if (bluetoothService.discovering)
- append("available", bluetoothService.discoveredDevices)
- return result
- }
-
- owner: ownerWidget
- open: ownerWidget.opened && bluetoothService && bluetoothService.ready
- focusTarget: keyCatcher
- contentWidth: fittedContentWidth(Commons.Style.space(380))
- contentHeight: fittedContentHeight(contentColumn.implicitHeight,
- Commons.Style.space(620))
-
- function sectionLabel(section) {
- if (section === "connected") return "CONNECTED"
- if (section === "paired") return "PAIRED"
- return "AVAILABLE"
- }
-
- function sectionStarts(index) {
- const current = rowAt(index)
- if (!current) return false
- const previous = rowAt(index - 1)
- return !previous || previous.section !== current.section
- }
-
- function rowAt(index) {
- return index >= 0 && index < rows.length ? rows[index] : null
- }
-
- function deviceAddress(device) {
- return device ? String(device.address || "") : ""
- }
-
- function canForget(row) {
- return row && (row.section === "connected" || row.section === "paired")
- }
-
- function primaryAction(row) {
- if (!row || !row.device) return false
- return row.device.connected
- ? bluetoothService.disconnectDevice(row.device)
- : bluetoothService.connectDevice(row.device)
- }
-
- function forget(row) {
- return canForget(row) && bluetoothService.forgetDevice(row.device)
- }
-
- function batteryStatusText(device) {
- if (!device || !device.batteryAvailable) return ""
- const icon = String(device.icon || "").toLowerCase()
- // BlueZ may expose coarse or stale phone charge without provenance, and
- // Quickshell does not expose Battery1.Source or an update timestamp.
- if (icon === "phone" || icon === "smartphone") return ""
- const battery = Number(device.battery)
- if (!Number.isFinite(battery) || battery < 0 || battery > 1) return ""
- return Math.round(battery * 100) + "%"
- }
-
- function statusText(row) {
- if (!row || !row.device) return ""
- void(bluetoothService.pendingActions)
- const device = row.device
- const pending = bluetoothService.pendingAction(deviceAddress(device))
- if (pending === "forgetting") return "Forgetting..."
- if (pending === "disconnecting" || Number(device.state) === 2)
- return "Disconnecting..."
- if (pending === "connecting" || Number(device.state) === 3
- || device.pairing === true) return "Connecting..."
- if (device.connected) {
- const batteryText = batteryStatusText(device)
- if (batteryText !== "") return "Connected · " + batteryText
- return "Connected"
- }
- return row.section === "paired" ? "Paired" : "Available"
- }
-
- function resetCursor() {
- selectedIndex = 0
- selectedAddress = rows.length > 0 ? deviceAddress(rows[0].device) : ""
- cursorActive = false
- actionFocused = false
- }
-
- function moveCursor(delta) {
- if (rows.length === 0) return
- selectedIndex = Math.max(0, Math.min(rows.length - 1, selectedIndex + delta))
- selectedAddress = deviceAddress(rows[selectedIndex].device)
- actionFocused = false
- ensureVisibleForIndex(selectedIndex)
- }
-
- function moveCursorHorizontal(delta) {
- const row = rowAt(selectedIndex)
- if (!canForget(row)) return
- actionFocused = delta > 0
- }
-
- function activateCursor() {
- const row = rowAt(selectedIndex)
- if (!row) return
- if (actionFocused) forget(row)
- else primaryAction(row)
- }
-
- function reselectAddress() {
- if (rows.length === 0) {
- selectedIndex = 0
- selectedAddress = ""
- actionFocused = false
- return
- }
- for (let i = 0; i < rows.length; i++) {
- if (deviceAddress(rows[i].device) === selectedAddress) {
- selectedIndex = i
- if (!canForget(rows[i])) actionFocused = false
- return
- }
- }
- selectedIndex = Math.max(0, Math.min(rows.length - 1, selectedIndex))
- selectedAddress = deviceAddress(rows[selectedIndex].device)
- actionFocused = false
- }
-
- function ensureVisibleForIndex(index) {
- if (!deviceRepeater || index < 0 || index >= deviceRepeater.count) return
- const item = deviceRepeater.itemAt(index)
- if (!item || !deviceFlick) return
- const point = item.mapToItem(deviceFlick.contentItem, 0, 0)
- const top = point.y
- const bottom = top + item.height
- if (top < deviceFlick.contentY)
- deviceFlick.contentY = Math.max(0, top - Commons.Style.space(4))
- else if (bottom > deviceFlick.contentY + deviceFlick.height)
- deviceFlick.contentY = Math.max(0,
- bottom - deviceFlick.height + Commons.Style.space(4))
- }
-
- function heroStatus() {
- if (!bluetoothService.adapterAvailable) return "NO ADAPTER"
- if (!bluetoothService.radioEnabled) return "TURNED OFF"
- if (bluetoothService.discovering) return "SCANNING FOR DEVICES"
- if (bluetoothService.connectedCount > 0)
- return bluetoothService.connectedCount + " CONNECTED"
- return "READY"
- }
-
- onOpenChanged: if (open) resetCursor()
- onRowsChanged: reselectAddress()
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
- onMoveRequested: function(dx, dy) {
- if (!panel.cursorActive) {
- panel.cursorActive = true
- return
- }
- if (dy !== 0) panel.moveCursor(dy)
- else if (dx !== 0) panel.moveCursorHorizontal(dx)
- }
- onActivateRequested: if (panel.cursorActive) panel.activateCursor()
- onDeleteRequested: if (panel.cursorActive)
- panel.forget(panel.rowAt(panel.selectedIndex))
-
- Column {
- id: contentColumn
- width: parent.width
- spacing: Commons.Style.space(10)
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(4)
-
- Text {
- width: parent.width - headerActions.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- text: "Bluetooth"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.heading
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Row {
- id: headerActions
- spacing: Commons.Style.space(4)
-
- IconAction {
- icon: panel.bluetoothService.discovering ? "sync" : "refresh"
- tooltip: "Scan for devices"
- enabled: panel.bluetoothService.radioEnabled
- onClicked: panel.bluetoothService.restartDiscovery()
- }
-
- IconAction {
- icon: "close"
- tooltip: "Close"
- onClicked: panel.ownerWidget.close()
- }
- }
- }
-
- Ui.PanelSeparator { width: parent.width }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(10)
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: !panel.bluetoothService.radioEnabled ? "\uE1A9"
- : panel.bluetoothService.connectedCount > 0
- ? "\uE1A8" : "\uE1A7"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- opacity: panel.bluetoothService.radioEnabled ? 1 : 0.45
- font.pixelSize: Commons.Style.font.display
- fill: 1
- }
-
- Column {
- width: parent.width - x - heroPowerToggle.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(2)
-
- Text {
- width: parent.width
- text: "Bluetooth"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.title
- font.weight: Font.DemiBold
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- text: panel.heroStatus()
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.56)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 1.1
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
-
- PowerToggle {
- id: heroPowerToggle
- anchors.verticalCenter: parent.verticalCenter
- }
- }
-
- Ui.PanelSeparator { width: parent.width }
-
- Text {
- width: parent.width
- visible: !panel.bluetoothService.radioEnabled
- text: "Bluetooth is off"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.48)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- horizontalAlignment: Text.AlignHCenter
- renderType: Text.NativeRendering
- }
-
- Flickable {
- id: deviceFlick
- width: parent.width
- height: panel.bluetoothService.radioEnabled
- ? Math.min(deviceColumn.implicitHeight, Commons.Style.space(400)) : 0
- visible: panel.bluetoothService.radioEnabled
- contentWidth: width
- contentHeight: deviceColumn.implicitHeight
- clip: true
- boundsBehavior: Flickable.StopAtBounds
- interactive: contentHeight > height
-
- Column {
- id: deviceColumn
- width: deviceFlick.width
- spacing: Commons.Style.space(6)
-
- Repeater {
- id: deviceRepeater
- model: panel.rows
-
- DeviceEntry {
- required property var modelData
- required property int index
- width: deviceColumn.width
- entry: modelData
- rowIndex: index
- sectionStart: panel.sectionStarts(index)
- }
- }
-
- Text {
- width: parent.width
- visible: panel.rows.length === 0
- text: panel.bluetoothService.discovering
- ? "Scanning for devices..." : "No Bluetooth devices found"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.48)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- horizontalAlignment: Text.AlignHCenter
- wrapMode: Text.WordWrap
- renderType: Text.NativeRendering
- }
- }
- }
-
- }
- }
-
- component SectionLabel: Text {
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 1.2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
- opacity: enabled ? 1 : 0.35
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- enabled: action.enabled
- hoverEnabled: true
- cursorShape: action.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-
- component PowerToggle: Item {
- id: powerToggle
- implicitWidth: Commons.Style.space(46)
- implicitHeight: Commons.Style.space(28)
-
- Rectangle {
- anchors.centerIn: parent
- width: Commons.Style.space(42)
- height: Commons.Style.space(20)
- radius: height / 2
- color: panel.bluetoothService.radioEnabled && panel.bar
- ? Qt.rgba(panel.bar.urgent.r, panel.bar.urgent.g,
- panel.bar.urgent.b, 0.18)
- : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: panel.bluetoothService.radioEnabled && panel.bar
- ? panel.bar.urgent : panel.controlBorderColor
-
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- x: panel.bluetoothService.radioEnabled
- ? parent.width - width - Commons.Style.space(3) : Commons.Style.space(3)
- width: Commons.Style.space(14)
- height: width
- radius: width / 2
- color: panel.bluetoothService.radioEnabled && panel.bar
- ? panel.bar.urgent : panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.55)
- : Commons.Color.foreground
- Behavior on x {
- NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
- }
- }
- }
-
- MouseArea {
- id: powerMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.bluetoothService.toggleBluetooth()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: powerMouse.containsMouse
- text: panel.bluetoothService.radioEnabled
- ? "Turn Bluetooth off" : "Turn Bluetooth on"
- }
- }
-
- component DeviceEntry: Column {
- id: entryItem
- required property var entry
- required property int rowIndex
- required property bool sectionStart
- readonly property var device: entry ? entry.device : null
- readonly property bool connected: device && device.connected === true
- readonly property bool forgettable: panel.canForget(entry)
- readonly property bool selected: panel.cursorActive
- && panel.selectedIndex === rowIndex
- spacing: Commons.Style.space(4)
-
- SectionLabel {
- visible: entryItem.sectionStart
- width: parent.width
- text: panel.sectionLabel(entryItem.entry.section)
- }
-
- Ui.CursorSurface {
- id: deviceSurface
- width: parent.width
- implicitHeight: Commons.Style.space(42)
- radius: panel.controlRadius
- bordered: true
- current: entryItem.connected
- hasCursor: entryItem.selected && !panel.actionFocused
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- Row {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: Commons.Style.space(8)
- anchors.rightMargin: Commons.Style.space(8)
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(8)
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: entryItem.connected ? "\uE1A8" : "\uE1A7"
- color: entryItem.connected && panel.bar
- ? panel.bar.urgent : deviceSurface.foreground
- font.pixelSize: Commons.Style.font.heading
- fill: 1
- }
-
- Column {
- width: parent.width - x - forgetAction.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- spacing: 0
-
- Text {
- width: parent.width
- text: panel.bluetoothService.deviceLabel(entryItem.device) || "Device"
- color: deviceSurface.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: entryItem.connected ? Font.DemiBold : Font.Normal
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- text: panel.statusText(entryItem.entry)
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.56)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
-
- IconAction {
- id: forgetAction
- visible: entryItem.forgettable
- && (deviceMouse.containsMouse || entryItem.selected)
- icon: "delete"
- tooltip: "Forget device"
- onClicked: panel.forget(entryItem.entry)
- }
- }
-
- MouseArea {
- id: deviceMouse
- anchors.fill: parent
- anchors.rightMargin: forgetAction.visible ? forgetAction.width : 0
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: if (containsMouse) {
- panel.cursorActive = true
- panel.selectedIndex = entryItem.rowIndex
- panel.selectedAddress = panel.deviceAddress(entryItem.device)
- panel.actionFocused = false
- }
- onClicked: panel.primaryAction(entryItem.entry)
- }
- }
- }
-}
diff --git a/widgets/BluetoothWidget.qml b/widgets/BluetoothWidget.qml
deleted file mode 100644
index c777d32..0000000
--- a/widgets/BluetoothWidget.qml
+++ /dev/null
@@ -1,209 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.bluetooth"
- manageIpc: false
- property url popupSource: Qt.resolvedUrl("BluetoothPanel.qml")
- property var bluetoothServiceOverride: null
- property var sessionService: null
-
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool compact: setting("compact", false) === true
- readonly property var bluetoothService: bluetoothServiceOverride
- || (bar ? bar.bluetoothService : null)
- readonly property bool bluetoothReady: bluetoothService && bluetoothService.ready
- readonly property bool adapterAvailable: bluetoothReady
- && bluetoothService.adapterAvailable
- readonly property bool radioEnabled: adapterAvailable
- && bluetoothService.radioEnabled
- readonly property int connectedCount: bluetoothReady
- ? bluetoothService.connectedCount : 0
- readonly property bool connected: connectedCount > 0
- readonly property bool showConnectedCount: connected
- && bar && !bar.vertical
- readonly property string stateIcon: !radioEnabled ? "\uE1A9"
- : connected ? "\uE1A8" : "\uE1A7"
- readonly property string tooltipText: !bluetoothReady ? "Bluetooth unavailable"
- : !adapterAvailable ? "No Bluetooth adapter"
- : connected ? "Bluetooth · " + connectedCount + " connected"
- : radioEnabled ? "Bluetooth on" : "Bluetooth off"
- readonly property var panelItem: popupLoader.item
- readonly property bool panelLoaded: panelItem !== null
- readonly property var interactionTarget: actionButton
-
- visible: bluetoothReady && adapterAvailable
- implicitWidth: visible ? (bar && bar.vertical ? bar.barSize : surface.implicitWidth) : 0
- implicitHeight: visible
- ? (bar && bar.vertical ? surface.implicitHeight : bar ? bar.barSize : 28) : 0
-
- function childPanelWidget(pluginId) {
- const id = String(pluginId || "")
- return id === moduleName || id === "omarchy.bluetooth" ? root : null
- }
-
- function ownsPanelWidget(owner) { return owner === root }
-
- function releaseSession() {
- if (sessionService && typeof sessionService.endSession === "function")
- sessionService.endSession(root)
- sessionService = null
- }
-
- function syncPanelLoader() {
- popupLoader.source = ""
- if (!opened || !bluetoothReady || !adapterAvailable || !String(popupSource)) {
- releaseSession()
- return
- }
- if (sessionService !== bluetoothService) {
- releaseSession()
- sessionService = bluetoothService
- sessionService.beginSession(root)
- }
- popupLoader.setSource(popupSource, {
- anchorItem: surface,
- bar: root.bar,
- ownerWidget: root,
- bluetoothService: bluetoothService
- })
- }
-
- function toggleBluetooth() {
- return bluetoothService && bluetoothService.toggleBluetooth()
- }
-
- onOpenedChanged: syncPanelLoader()
- onBluetoothReadyChanged: syncPanelLoader()
- onAdapterAvailableChanged: syncPanelLoader()
- onPopupSourceChanged: syncPanelLoader()
- Component.onDestruction: {
- close()
- releaseSession()
- }
-
- Loader { id: popupLoader }
-
- Item {
- id: surface
- anchors.centerIn: parent
- implicitWidth: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? root.bar.barSize
- : content.implicitWidth + 2 * root.tokens.pillPaddingX
- implicitHeight: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? content.implicitHeight + Commons.Style.space(10)
- : root.tokens.slotHeight
- width: implicitWidth
- height: implicitHeight
-
- Loader {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- active: root.bar !== null && root.tokens !== null
- sourceComponent: Component {
- PillSurface { anchors.fill: parent; bar: root.bar }
- }
- }
-
- Loader {
- id: content
- anchors.centerIn: parent
- sourceComponent: !root.bar || !root.tokens ? null
- : root.compact || root.bar.vertical ? compactContent : fullContent
- }
-
- Ui.WidgetButton {
- id: actionButton
- anchors.fill: parent
- bar: root.visible ? root.bar : null
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- fixedWidth: surface.width
- fixedHeight: surface.height
- tooltipText: root.tooltipText
- onPressed: function(button) {
- if (button === Qt.RightButton) root.toggleBluetooth()
- else root.toggle()
- }
- }
- }
-
- Component {
- id: fullContent
-
- Row {
- spacing: root.tokens.contentGap
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: "BT"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.6) : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: 0.5
- renderType: Text.NativeRendering
- }
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: root.stateIcon
- color: root.bar ? (root.connected ? root.bar.urgent
- : root.bar.foreground) : Commons.Color.accent
- opacity: root.radioEnabled ? (root.connected ? 1 : 0.7) : 0.35
- font.pixelSize: 14
- font.weight: Font.Medium
- fill: 1
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: root.showConnectedCount
- text: String(root.connectedCount)
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-
- Component {
- id: compactContent
-
- Row {
- spacing: root.tokens.compactGap
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: root.stateIcon
- color: root.bar ? (root.connected ? root.bar.urgent
- : root.bar.foreground) : Commons.Color.accent
- opacity: root.radioEnabled ? (root.connected ? 1 : 0.7) : 0.35
- font.pixelSize: 14
- font.weight: Font.Medium
- fill: 1
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: root.showConnectedCount
- text: String(root.connectedCount)
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-}
diff --git a/widgets/BrightnessPanel.qml b/widgets/BrightnessPanel.qml
deleted file mode 100644
index ef607b6..0000000
--- a/widgets/BrightnessPanel.qml
+++ /dev/null
@@ -1,720 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var monitorService
-
- property string cursorSection: ""
- property int cursorIndex: 0
- property bool cursorActive: false
- readonly property var visibleSections: {
- const result = []
- if (monitorService.brightnessAvailable) result.push("brightness")
- if (monitorService.textSizeAvailable) result.push("textsize")
- result.push("scale")
- if (monitorService.displays.length > 1) result.push("displays")
- return result
- }
-
- owner: ownerWidget
- open: ownerWidget.opened && monitorService && monitorService.ready
- focusTarget: keyCatcher
- contentWidth: fittedContentWidth(Commons.Style.space(380))
- contentHeight: fittedContentHeight(contentColumn.implicitHeight,
- Commons.Style.space(620))
-
- function resetCursor() {
- cursorSection = monitorService.brightnessAvailable ? "brightness" : "scale"
- cursorIndex = cursorSection === "brightness" ? -1 : 0
- cursorActive = false
- }
-
- function refreshDisplayState() {
- return monitorService && typeof monitorService.refresh === "function"
- ? monitorService.refresh() : false
- }
-
- function sectionIndex() { return visibleSections.indexOf(cursorSection) }
-
- function moveVertical(delta) {
- const sections = visibleSections
- if (sections.length === 0) return
- let current = sectionIndex()
- if (current < 0) {
- resetCursor()
- return
- }
-
- if (cursorSection === "displays") {
- const count = monitorService.displays.length
- const next = cursorIndex + delta
- if (next >= 0 && next < count) {
- cursorIndex = next
- return
- }
- }
-
- current = Math.max(0, Math.min(sections.length - 1, current + delta))
- cursorSection = sections[current]
- cursorIndex = cursorSection === "brightness"
- || cursorSection === "textsize" ? -1 : 0
- }
-
- function moveHorizontal(delta) {
- if (cursorSection === "brightness") {
- monitorService.setBrightness(monitorService.brightnessPercent + delta * 5)
- return
- }
- if (cursorSection === "textsize") {
- monitorService.adjustTextSize(delta)
- return
- }
- if (cursorSection !== "scale") return
- cursorIndex = Math.max(0, Math.min(monitorService.scaleValues.length - 1,
- cursorIndex + delta))
- }
-
- function activateCursor() {
- if (cursorSection === "scale" && cursorIndex >= 0
- && cursorIndex < monitorService.scaleValues.length) {
- monitorService.setScale(monitorService.scaleValues[cursorIndex])
- return
- }
- if (cursorSection !== "displays" || cursorIndex < 0
- || cursorIndex >= monitorService.displays.length) return
- const display = monitorService.displays[cursorIndex]
- if (!display || (display.enabled && monitorService.enabledDisplayCount <= 1)) return
- monitorService.toggleDisplay(display.name, display.enabled)
- }
-
- function ensureVisible(item) {
- if (!item || !scroller) return
- const point = item.mapToItem(contentColumn, 0, 0)
- const top = point.y
- const bottom = top + item.height
- if (top < scroller.contentY) scroller.contentY = Math.max(0, top - 6)
- else if (bottom > scroller.contentY + scroller.height)
- scroller.contentY = Math.max(0, bottom - scroller.height + 6)
- }
-
- onOpenChanged: {
- if (!open) return
- resetCursor()
- }
-
- onVisibleSectionsChanged: {
- if (visibleSections.indexOf(cursorSection) < 0) resetCursor()
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
- onMoveRequested: function(dx, dy) {
- if (!panel.cursorActive) {
- panel.cursorActive = true
- return
- }
- if (dy !== 0) panel.moveVertical(dy)
- else if (dx !== 0) panel.moveHorizontal(dx)
- }
- onActivateRequested: if (panel.cursorActive) panel.activateCursor()
-
- Flickable {
- id: scroller
- anchors.fill: parent
- clip: true
- contentWidth: width
- contentHeight: contentColumn.implicitHeight
- boundsBehavior: Flickable.StopAtBounds
- interactive: contentHeight > height
-
- Column {
- id: contentColumn
- width: scroller.width
- spacing: Commons.Style.space(10)
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(4)
-
- Text {
- width: parent.width - headerActions.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- text: "Display"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.heading
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Row {
- id: headerActions
- spacing: Commons.Style.space(2)
-
- IconAction {
- icon: "refresh"
- tooltip: "Refresh display state"
- onClicked: panel.refreshDisplayState()
- }
-
- IconAction {
- icon: "close"
- tooltip: "Close"
- onClicked: panel.ownerWidget.close()
- }
- }
- }
-
- Ui.PanelSeparator { width: parent.width }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(10)
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: panel.monitorService.displays.length > 1
- ? "desktop_windows" : "monitor"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.pixelSize: Commons.Style.font.display
- fill: 1
- }
-
- Column {
- width: parent.width - x
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(2)
-
- Text {
- width: parent.width
- text: panel.monitorService.focusedMonitor || "Display controls"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: Font.Medium
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- text: panel.monitorService.brightnessAvailable
- ? panel.monitorService.brightnessName(
- panel.monitorService.brightnessPercent).toUpperCase()
- + " · " + panel.monitorService.brightnessPercent + "%"
- : "NO CONTROLLABLE BACKLIGHT"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 1
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
- }
-
- Ui.PanelSeparator {
- width: parent.width
- visible: panel.monitorService.brightnessAvailable
- }
-
- Column {
- width: parent.width
- visible: panel.monitorService.brightnessAvailable
- spacing: Commons.Style.space(5)
-
- SectionLabel { text: "BRIGHTNESS" }
-
- Item {
- id: brightnessRow
- width: parent.width
- height: Commons.Style.space(30)
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- text: panel.monitorService.brightnessPercent + "%"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Ui.PanelSlider {
- id: brightnessSlider
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- anchors.leftMargin: Commons.Style.space(6)
- anchors.rightMargin: Commons.Style.space(6)
- bar: panel.bar
- minimum: 1
- maximum: 100
- step: 1
- integer: true
- trackColor: panel.controlActiveFillColor
- fillColor: panel.controlAccent
- knobColor: panel.controlAccent
- value: panel.monitorService.brightnessPercent
- onMoved: function(value) {
- panel.monitorService.previewBrightness(value)
- }
- onReleased: function(value) {
- panel.monitorService.setBrightness(value)
- }
- }
-
- HoverHandler {
- onHoveredChanged: if (hovered) {
- panel.cursorActive = true
- panel.cursorSection = "brightness"
- panel.cursorIndex = -1
- panel.ensureVisible(brightnessRow)
- }
- }
- }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(6)
-
- PanelButton {
- width: (parent.width - parent.spacing) / 2
- label: "− 5%"
- onClicked: panel.monitorService.setBrightness(
- panel.monitorService.brightnessPercent - 5)
- }
- PanelButton {
- width: (parent.width - parent.spacing) / 2
- label: "+ 5%"
- onClicked: panel.monitorService.setBrightness(
- panel.monitorService.brightnessPercent + 5)
- }
- }
- }
-
- Ui.PanelSeparator {
- width: parent.width
- visible: panel.monitorService.textSizeAvailable
- }
-
- Column {
- width: parent.width
- visible: panel.monitorService.textSizeAvailable
- spacing: Commons.Style.space(5)
-
- Item {
- width: parent.width
- implicitHeight: Math.max(textSizeLabel.implicitHeight,
- textSizeValue.implicitHeight)
-
- SectionLabel {
- id: textSizeLabel
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "TEXT SIZE"
- }
-
- Text {
- id: textSizeValue
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: (textSizeSlider.dragging
- ? panel.monitorService.textSizeStops[
- Math.round(textSizeSlider.liveValue)]
- : panel.monitorService.textSizePx) + "px"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
- }
-
- Ui.CursorSurface {
- id: textSizeRow
- width: parent.width
- height: textSizeSlider.implicitHeight
- + Commons.Style.spacing.controlGap
- hasCursor: panel.cursorActive
- && panel.cursorSection === "textsize" && panel.cursorIndex === -1
- foreground: panel.controlForeground
- accent: panel.controlAccent
- outline: true
- onHasCursorChanged: if (hasCursor) panel.ensureVisible(textSizeRow)
-
- Row {
- id: v2TextSizeSegments
- visible: panel.shellStyle !== "shibumi"
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: Commons.Style.space(6)
- anchors.rightMargin: Commons.Style.space(6)
- anchors.verticalCenter: textSizeSlider.verticalCenter
- height: textSizeSlider.trackHeight
- spacing: Commons.Style.space(2)
- readonly property int segmentCount: Math.max(0,
- panel.monitorService.textSizeStops.length - 1)
-
- Repeater {
- model: Math.max(0,
- panel.monitorService.textSizeStops.length - 1)
-
- Rectangle {
- required property int index
- width: (v2TextSizeSegments.width
- - v2TextSizeSegments.spacing
- * Math.max(0, v2TextSizeSegments.segmentCount - 1))
- / Math.max(1, v2TextSizeSegments.segmentCount)
- height: v2TextSizeSegments.height
- radius: height / 2
- color: panel.controlActiveFillColor
-
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: parent.width * Math.max(0, Math.min(1,
- textSizeSlider.progress
- * v2TextSizeSegments.segmentCount - parent.index))
- radius: parent.radius
- color: panel.controlAccent
- }
- }
- }
- }
-
- Ui.PanelSlider {
- id: textSizeSlider
- anchors.fill: parent
- anchors.leftMargin: Commons.Style.space(6)
- anchors.rightMargin: Commons.Style.space(6)
- bar: panel.bar
- minimum: 0
- maximum: Math.max(0,
- panel.monitorService.textSizeStops.length - 1)
- step: 1
- integer: true
- tickCount: panel.shellStyle === "shibumi"
- ? panel.monitorService.textSizeStops.length : 0
- trackColor: panel.shellStyle === "shibumi"
- ? panel.controlActiveFillColor : "transparent"
- fillColor: panel.shellStyle === "shibumi"
- ? panel.controlAccent : "transparent"
- knobColor: panel.controlAccent
- tickColor: panel.renderedSurfaceColor
- value: panel.monitorService.textSizeIndex
- onReleased: function(value) {
- const index = Math.max(0, Math.min(
- panel.monitorService.textSizeStops.length - 1,
- Math.round(value)))
- panel.monitorService.setTextSize(
- panel.monitorService.textSizeStops[index])
- }
- }
-
- HoverHandler {
- onHoveredChanged: if (hovered) {
- panel.cursorActive = true
- panel.cursorSection = "textsize"
- panel.cursorIndex = -1
- }
- }
- }
- }
-
- Ui.PanelSeparator { width: parent.width }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(6)
-
- SectionLabel { text: "SCALE" }
-
- Grid {
- id: scaleGrid
- width: parent.width
- columns: panel.monitorService.scaleValues.length
- spacing: Commons.Style.space(3)
- readonly property real cellWidth: columns > 0
- ? (width - spacing * (columns - 1)) / columns : 0
-
- Repeater {
- model: panel.monitorService.scaleValues
- delegate: ScaleButton {
- required property string modelData
- required property int index
- width: scaleGrid.cellWidth
- scaleValue: modelData
- scaleIndex: index
- }
- }
- }
- }
-
- Ui.PanelSeparator {
- width: parent.width
- visible: panel.monitorService.displays.length > 1
- }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(5)
- visible: panel.monitorService.displays.length > 1
-
- SectionLabel { text: "DISPLAYS" }
-
- Repeater {
- model: panel.monitorService.displays
- delegate: DisplayRow {
- required property var modelData
- required property int index
- width: contentColumn.width
- display: modelData
- rowIndex: index
- }
- }
- }
- }
- }
- }
-
- component SectionLabel: Text {
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 1.2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- component PanelButton: Rectangle {
- id: button
- property string label: ""
- signal clicked()
- readonly property bool hovered: buttonMouse.containsMouse
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- color: hovered ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: hovered ? panel.controlHoverBorderColor
- : panel.controlBorderColor
-
- Behavior on color { ColorAnimation { duration: 100 } }
- Behavior on border.color { ColorAnimation { duration: 100 } }
-
- Text {
- anchors.centerIn: parent
- text: button.label
- color: button.hovered ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: buttonMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: button.clicked()
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-
- component ScaleButton: Rectangle {
- id: scaleButton
- required property string scaleValue
- required property int scaleIndex
- readonly property bool activeScale: panel.monitorService.normalizeScale(
- panel.monitorService.monitorScale) === panel.monitorService.normalizeScale(scaleValue)
- readonly property bool keyboardFocused: panel.cursorActive
- && panel.cursorSection === "scale" && panel.cursorIndex === scaleIndex
- readonly property bool hovered: scaleMouse.containsMouse
- readonly property bool highlighted: keyboardFocused || hovered
- implicitHeight: Commons.Style.space(30)
- radius: panel.controlRadius
- color: activeScale ? panel.controlActiveFillColor
- : highlighted ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: activeScale || highlighted ? panel.controlAccent
- : panel.controlBorderColor
-
- Behavior on color { ColorAnimation { duration: 100 } }
- Behavior on border.color { ColorAnimation { duration: 100 } }
-
- Text {
- anchors.centerIn: parent
- text: scaleButton.scaleValue + "x"
- color: scaleButton.activeScale || scaleButton.highlighted
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.weight: scaleButton.activeScale ? Font.DemiBold : Font.Normal
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: scaleMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: if (containsMouse) {
- panel.cursorActive = true
- panel.cursorSection = "scale"
- panel.cursorIndex = scaleButton.scaleIndex
- }
- onClicked: panel.monitorService.setScale(scaleButton.scaleValue)
- }
- }
-
- component DisplayRow: Rectangle {
- id: displayRow
- required property var display
- required property int rowIndex
- readonly property bool enabledDisplay: display && display.enabled === true
- readonly property bool focusedDisplay: display && display.focused === true
- readonly property bool canToggle: display
- && (!enabledDisplay || panel.monitorService.enabledDisplayCount > 1)
- readonly property bool keyboardFocused: panel.cursorActive
- && panel.cursorSection === "displays" && panel.cursorIndex === rowIndex
- readonly property bool hovered: displayMouse.containsMouse && canToggle
- readonly property bool highlighted: keyboardFocused || hovered
- implicitHeight: Commons.Style.space(36)
- radius: panel.controlRadius
- color: focusedDisplay ? panel.controlActiveFillColor
- : highlighted ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: focusedDisplay || highlighted ? panel.controlAccent
- : panel.controlBorderColor
- opacity: canToggle ? 1 : 0.45
-
- Behavior on color { ColorAnimation { duration: 100 } }
- Behavior on border.color { ColorAnimation { duration: 100 } }
- onKeyboardFocusedChanged: if (keyboardFocused) panel.ensureVisible(displayRow)
-
- Row {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: Commons.Style.space(8)
- anchors.rightMargin: Commons.Style.space(8)
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(7)
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: "monitor"
- color: displayRow.focusedDisplay || displayRow.highlighted
- ? panel.controlAccent : panel.controlForeground
- font.pixelSize: Commons.Style.font.body
- fill: 1
- }
-
- Column {
- width: parent.width - x - stateIcon.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- spacing: 0
-
- Text {
- width: parent.width
- text: String(displayRow.display.name || "Display")
- color: displayRow.focusedDisplay || displayRow.highlighted
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: displayRow.focusedDisplay ? Font.DemiBold : Font.Normal
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- text: displayRow.focusedDisplay ? "Focused"
- : displayRow.enabledDisplay ? "Enabled" : "Disabled"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.55)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
-
- IconText {
- id: stateIcon
- anchors.verticalCenter: parent.verticalCenter
- text: displayRow.enabledDisplay ? "check_circle" : "radio_button_unchecked"
- color: displayRow.enabledDisplay
- ? panel.controlAccent : panel.controlForeground
- font.pixelSize: Commons.Style.font.body
- fill: displayRow.enabledDisplay ? 1 : 0
- }
- }
-
- MouseArea {
- id: displayMouse
- anchors.fill: parent
- hoverEnabled: true
- enabled: displayRow.canToggle
- cursorShape: displayRow.canToggle ? Qt.PointingHandCursor : Qt.ArrowCursor
- onContainsMouseChanged: if (containsMouse) {
- panel.cursorActive = true
- panel.cursorSection = "displays"
- panel.cursorIndex = displayRow.rowIndex
- }
- onClicked: panel.monitorService.toggleDisplay(
- displayRow.display.name, displayRow.enabledDisplay)
- }
- }
-}
diff --git a/widgets/BrightnessWidget.qml b/widgets/BrightnessWidget.qml
deleted file mode 100644
index 3ad5df2..0000000
--- a/widgets/BrightnessWidget.qml
+++ /dev/null
@@ -1,250 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.brightness"
- manageIpc: false
- property url popupSource: Qt.resolvedUrl("BrightnessPanel.qml")
- property var monitorServiceOverride: null
-
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool compact: setting("compact", false) === true
- readonly property var monitorService: monitorServiceOverride
- || (bar ? bar.monitorService : null)
- readonly property bool monitorReady: monitorService && monitorService.ready
- readonly property bool brightnessAvailable: monitorReady
- && monitorService.brightnessAvailable
- readonly property bool internalDisplay: monitorReady
- && hasInternalDisplay(monitorService.displays)
- readonly property int percent: monitorReady
- ? monitorService.brightnessPercent : 0
- readonly property int displayCount: monitorReady
- && Array.isArray(monitorService.displays) ? monitorService.displays.length : 0
- readonly property string displayGlyph: Quickshell.screens.length > 1
- ? "" : ""
- readonly property string tooltipText: !monitorReady ? "Display unavailable"
- : brightnessAvailable ? "Brightness · " + percent + "%"
- : "Display controls"
- readonly property var panelItem: popupLoader.item
- readonly property bool panelLoaded: panelItem !== null
- readonly property var interactionTarget: actionButton
-
- visible: monitorReady
- implicitWidth: visible ? (bar && bar.vertical ? bar.barSize : surface.implicitWidth) : 0
- implicitHeight: visible
- ? (bar && bar.vertical ? surface.implicitHeight : bar ? bar.barSize : 28) : 0
-
- function setting(name, fallback) {
- const value = settings ? settings[name] : undefined
- return value === undefined || value === null ? fallback : value
- }
-
- function hasInternalDisplay(displays) {
- const rows = Array.isArray(displays) ? displays : []
- for (let i = 0; i < rows.length; i++) {
- if (/^(eDP|LVDS|DSI)-/.test(String(rows[i] && rows[i].name || "")))
- return true
- }
- return false
- }
-
- function childPanelWidget(pluginId) {
- const id = String(pluginId || "")
- return id === moduleName || id === "omarchy.monitor" ? root : null
- }
-
- function ownsPanelWidget(owner) { return owner === root }
-
- function adjustBrightness(delta) {
- return monitorService && monitorService.setBrightness(percent + Number(delta || 0))
- }
-
- function syncPanelLoader() {
- popupLoader.source = ""
- if (!opened || !monitorReady || !String(popupSource)) return
- monitorService.refresh()
- popupLoader.setSource(popupSource, {
- anchorItem: surface,
- bar: root.bar,
- ownerWidget: root,
- monitorService: monitorService
- })
- }
-
- onOpenedChanged: syncPanelLoader()
- onMonitorReadyChanged: syncPanelLoader()
- onPopupSourceChanged: syncPanelLoader()
- Component.onDestruction: close()
-
- Loader { id: popupLoader }
-
- Item {
- id: surface
- anchors.centerIn: parent
- implicitWidth: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? root.bar.barSize
- : content.implicitWidth + 2 * root.tokens.pillPaddingX
- implicitHeight: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? content.implicitHeight + Commons.Style.space(10)
- : root.tokens.slotHeight
- width: implicitWidth
- height: implicitHeight
-
- Loader {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- active: root.bar !== null && root.tokens !== null
- sourceComponent: Component {
- PillSurface { anchors.fill: parent; bar: root.bar }
- }
- }
-
- Loader {
- id: content
- anchors.centerIn: parent
- sourceComponent: !root.bar || !root.tokens ? null
- : root.internalDisplay
- ? (root.compact ? compactBrightnessContent : fullBrightnessContent)
- : displayContent
- }
-
- Ui.WidgetButton {
- id: actionButton
- anchors.fill: parent
- bar: root.monitorReady ? root.bar : null
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- fixedWidth: surface.width
- fixedHeight: surface.height
- tooltipText: root.tooltipText
- onPressed: function(_button) { root.toggle() }
- onWheelMoved: function(delta) {
- if (root.brightnessAvailable)
- root.adjustBrightness(delta > 0 ? 5 : -5)
- }
- }
- }
-
- Component {
- id: fullBrightnessContent
-
- Row {
- spacing: root.tokens.contentGap
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: "BRI"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.6) : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: 0.5
- renderType: Text.NativeRendering
- }
-
- SunIndicator {
- anchors.verticalCenter: parent.verticalCenter
- ratio: root.percent / 100
- color: root.bar ? (root.percent >= 100 ? root.bar.urgent
- : root.bar.foreground) : Commons.Color.accent
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: root.percent + "%"
- color: root.bar ? (root.percent >= 100 ? root.bar.urgent
- : root.bar.foreground) : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-
- Component {
- id: compactBrightnessContent
-
- Row {
- spacing: root.tokens.compactGap
-
- SunIndicator {
- anchors.verticalCenter: parent.verticalCenter
- ratio: root.percent / 100
- color: root.bar ? (root.percent >= 100 ? root.bar.urgent
- : root.bar.foreground) : Commons.Color.accent
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: !root.bar.vertical
- text: root.percent + "%"
- color: root.bar ? (root.percent >= 100 ? root.bar.urgent
- : root.bar.foreground) : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-
- Component {
- id: displayContent
-
- Text {
- text: root.displayGlyph
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.iconSize
- renderType: Text.NativeRendering
- }
- }
-
- component SunIndicator: Item {
- id: sun
-
- required property real ratio
- required property color color
-
- width: Commons.Style.space(13)
- height: Commons.Style.space(13)
-
- Rectangle {
- anchors.centerIn: parent
- width: Commons.Style.space(6.5)
- height: width
- radius: width / 2
- color: sun.color
- Behavior on color { ColorAnimation { duration: 200 } }
- }
-
- Repeater {
- model: 8
- delegate: Item {
- required property int index
- anchors.fill: parent
- rotation: index * 45
-
- Rectangle {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- width: Commons.Style.space(1.5)
- height: Commons.Style.space(2 + 1.4 * sun.ratio)
- radius: width / 2
- color: sun.color
- opacity: 0.35 + 0.65 * sun.ratio
- }
- }
- }
- }
-}
diff --git a/widgets/CalendarModel.js b/widgets/CalendarModel.js
deleted file mode 100644
index d764f1c..0000000
--- a/widgets/CalendarModel.js
+++ /dev/null
@@ -1,52 +0,0 @@
-.pragma library
-
-var MonthNames = [
- "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE",
- "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER"
-]
-
-function validDate(value) {
- return value instanceof Date && !isNaN(value.getTime())
-}
-
-function normalizedDate(value) {
- return validDate(value) ? value : new Date()
-}
-
-function monthDate(value, offset) {
- var date = normalizedDate(value)
- return new Date(date.getFullYear(), date.getMonth() + Number(offset || 0), 1)
-}
-
-function monthName(value, offset) {
- return MonthNames[monthDate(value, offset).getMonth()]
-}
-
-function year(value, offset) {
- return String(monthDate(value, offset).getFullYear())
-}
-
-function dateLabel(value) {
- var date = normalizedDate(value)
- var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
- return days[date.getDay()] + " " + date.getDate()
-}
-
-function cells(value, offset) {
- var today = normalizedDate(value)
- var first = monthDate(today, offset)
- var currentYear = first.getFullYear()
- var currentMonth = first.getMonth()
- var lastDay = new Date(currentYear, currentMonth + 1, 0).getDate()
- var startDay = (first.getDay() + 6) % 7
- var isCurrentMonth = currentYear === today.getFullYear()
- && currentMonth === today.getMonth()
- var result = []
-
- for (var i = 0; i < startDay; i++)
- result.push({ day: 0, today: false })
- for (var day = 1; day <= lastDay; day++)
- result.push({ day: day, today: isCurrentMonth && day === today.getDate() })
- while (result.length < 42) result.push({ day: 0, today: false })
- return result
-}
diff --git a/widgets/CalendarPanel.qml b/widgets/CalendarPanel.qml
deleted file mode 100644
index 218b6f6..0000000
--- a/widgets/CalendarPanel.qml
+++ /dev/null
@@ -1,241 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-import "CalendarModel.js" as CalendarModel
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var clockService
- property int monthOffset: 0
- property int selectedDay: 0
- readonly property date displayDate: clockService ? clockService.date : new Date()
- readonly property var calendarCells: CalendarModel.cells(displayDate, monthOffset)
- readonly property string monthName: CalendarModel.monthName(displayDate, monthOffset)
- readonly property string calendarYear: CalendarModel.year(displayDate, monthOffset)
- readonly property int renderedDayCount: dayRepeater.count
- readonly property var stateService: bar && bar.shell
- && typeof bar.shell.serviceFor === "function"
- ? bar.shell.serviceFor("hancore.shibumi.state") : null
- readonly property color deepInk: stateService
- && typeof stateService.paletteColor === "function"
- ? stateService.paletteColor("color07") : controlForeground
-
- owner: ownerWidget
- open: ownerWidget.opened
- focusTarget: keyCatcher
- centerOnBar: true
- gap: 8
- padding: 12
- contentWidth: fittedContentWidth(280)
- contentHeight: fittedContentHeight(content.implicitHeight)
-
- function previousMonth() { monthOffset-- }
- function nextMonth() { monthOffset++ }
- function resetToToday() {
- monthOffset = 0
- selectedDay = displayDate.getDate()
- }
-
- Component.onCompleted: resetToToday()
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: ownerWidget.close()
- onMoveRequested: function(dx, _dy) {
- if (dx < 0) panel.previousMonth()
- else if (dx > 0) panel.nextMonth()
- }
- onTabRequested: function(direction) {
- panel.ownerWidget.switchPanel(direction)
- }
-
- Column {
- id: content
- width: parent.width
- spacing: 10
-
- Item {
- width: parent.width
- height: 24
-
- CalendarAction {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- iconText: "‹"
- tooltipText: "Previous month"
- onClicked: panel.previousMonth()
- }
-
- Text {
- anchors.centerIn: parent
- text: panel.monthName + " " + panel.calendarYear
- color: monthMouse.containsMouse && panel.monthOffset !== 0
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 12
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
-
- MouseArea {
- id: monthMouse
- anchors.fill: parent
- anchors.margins: -6
- hoverEnabled: true
- enabled: panel.monthOffset !== 0
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: panel.resetToToday()
- }
- }
-
- CalendarAction {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- iconText: "›"
- tooltipText: "Next month"
- onClicked: panel.nextMonth()
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Row {
- width: parent.width
-
- Repeater {
- model: ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]
-
- delegate: Item {
- required property string modelData
- required property int index
- width: content.width / 7
- height: 20
-
- Text {
- anchors.centerIn: parent
- text: modelData
- color: index >= 5 ? panel.controlAccent : panel.deepInk
- opacity: index >= 5 ? 0.85 : 0.7
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 10
- font.letterSpacing: 2
- renderType: Text.NativeRendering
- }
- }
- }
- }
-
- Grid {
- columns: 7
- rowSpacing: 2
- columnSpacing: 0
- width: parent.width
-
- Repeater {
- id: dayRepeater
- model: panel.calendarCells
-
- delegate: Item {
- id: dayCell
- required property var modelData
- required property int index
- readonly property bool hasDay: modelData.day > 0
- readonly property bool today: modelData.today === true
- readonly property bool selected: hasDay && !today
- && panel.selectedDay === modelData.day && panel.monthOffset === 0
- readonly property bool weekend: index % 7 >= 5
- width: content.width / 7
- height: 28
-
- Rectangle {
- anchors.centerIn: parent
- width: 24
- height: width
- radius: 12
- visible: dayCell.today
- color: panel.controlAccent
- }
-
- Rectangle {
- anchors.centerIn: parent
- width: 24
- height: width
- radius: 12
- visible: dayCell.selected
- color: "transparent"
- border.width: 1
- border.color: panel.controlAccent
- }
-
- Text {
- anchors.centerIn: parent
- text: dayCell.hasDay ? dayCell.modelData.day : ""
- color: dayCell.today
- ? panel.controlAccent.hsvValue < 0.5
- ? panel.controlForeground : Commons.Color.background
- : dayCell.weekend ? panel.controlAccent : panel.controlForeground
- opacity: dayCell.hasDay ? 1 : 0.35
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 12
- font.weight: dayCell.today ? Font.Medium : Font.Light
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- anchors.fill: parent
- enabled: dayCell.hasDay
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: panel.selectedDay = dayCell.modelData.day
- }
- }
- }
- }
- }
- }
-
- component CalendarAction: Rectangle {
- id: action
- property string iconText: ""
- property string tooltipText: ""
- signal clicked()
- readonly property bool hovered: actionMouse.containsMouse
- width: 24
- height: 24
- radius: panel.controlRadius
- color: "transparent"
-
- Text {
- anchors.centerIn: parent
- text: action.iconText
- color: action.hovered ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 16
- renderType: Text.NativeRendering
- Behavior on color { ColorAnimation { duration: 120 } }
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: panel.shellStyle !== "shibumi"
- && action.tooltipText !== "" && actionMouse.containsMouse
- text: action.tooltipText
- }
- }
-}
diff --git a/widgets/CavaThemeModel.js b/widgets/CavaThemeModel.js
deleted file mode 100644
index 46360b3..0000000
--- a/widgets/CavaThemeModel.js
+++ /dev/null
@@ -1,50 +0,0 @@
-.pragma library
-
-var ColorPattern = /^#(?:[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/
-var MaxInputLength = 65536
-var MaxLines = 1024
-var MaxGradientColors = 8
-
-function parse(raw) {
- var gradientEnabled = false
- var gradientCount = 0
- var foreground = ""
- var indexedColors = {}
- var lines = String(raw || "").slice(0, MaxInputLength).split("\n")
-
- for (var i = 0; i < lines.length && i < MaxLines; i++) {
- var assignment = lines[i].match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/)
- if (!assignment) continue
- var key = String(assignment[1]).toLowerCase()
- var value = String(assignment[2]).replace(/^\s*["']?|["']?\s*$/g, "")
-
- if (key === "gradient") {
- gradientEnabled = value === "1" || value.toLowerCase() === "true"
- } else if (key === "gradient_count") {
- var count = parseInt(value)
- gradientCount = isNaN(count) ? 0
- : Math.max(0, Math.min(MaxGradientColors, count))
- } else if (key === "foreground" && ColorPattern.test(value)) {
- foreground = value
- } else {
- var colorMatch = key.match(/^gradient_color_([1-8])$/)
- if (colorMatch && ColorPattern.test(value))
- indexedColors[parseInt(colorMatch[1])] = value
- }
- }
-
- if (!gradientEnabled) return foreground ? [foreground] : []
-
- var count = gradientCount
- if (count === 0) {
- while (count < MaxGradientColors && indexedColors[count + 1]) count++
- }
- if (count < 2) return foreground ? [foreground] : []
-
- var colors = []
- for (var index = 1; index <= count; index++) {
- if (!indexedColors[index]) return foreground ? [foreground] : []
- colors.push(indexedColors[index])
- }
- return colors
-}
diff --git a/widgets/CenterLayout.js b/widgets/CenterLayout.js
deleted file mode 100644
index 726c14b..0000000
--- a/widgets/CenterLayout.js
+++ /dev/null
@@ -1,17 +0,0 @@
-.pragma library
-
-function nextStage(currentStage, availableWidth, needNormal, needCompact,
- centered) {
- if (!centered || Number(availableWidth) <= 0) return 0
-
- var stage = Math.max(0, Math.min(2, Number(currentStage) || 0))
- var available = Math.max(0, Number(availableWidth) || 0)
- var normal = Math.max(0, Number(needNormal) || 0)
- var compact = Math.max(0, Number(needCompact) || 0)
-
- if (stage === 0 && normal + 24 > available) stage = 1
- if (stage === 1 && compact + 24 > available) stage = 2
- if (stage === 2 && compact + 48 <= available) stage = 1
- if (stage === 1 && normal + 48 <= available) stage = 0
- return stage
-}
diff --git a/widgets/CenterWidget.qml b/widgets/CenterWidget.qml
deleted file mode 100644
index d980242..0000000
--- a/widgets/CenterWidget.qml
+++ /dev/null
@@ -1,279 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-import "CenterLayout.js" as CenterLayout
-import "CalendarModel.js" as CalendarModel
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.center"
- manageIpc: false
- property url calendarSource: Qt.resolvedUrl("CalendarPanel.qml")
- property real availableWidth: 0
- property int stage: 0
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property var clock: bar ? bar.clockService : null
- readonly property date displayDate: clock ? clock.date : new Date()
- readonly property string dateText: CalendarModel.dateLabel(displayDate)
- readonly property bool centered: availableWidth > 0
- readonly property bool showWeather: stage <= 1
- readonly property bool showDate: stage === 0
- readonly property real weatherNaturalWidth: weatherView.implicitWidth
- readonly property real indicatorNaturalWidth: statusIndicators.implicitWidth
- readonly property real updateNaturalWidth: updateView.implicitWidth
- readonly property bool showIndicators: indicatorNaturalWidth > 0.5
- || updateNaturalWidth > 0.5
- readonly property real statusNaturalWidth: indicatorNaturalWidth
- + updateNaturalWidth
- + (indicatorNaturalWidth > 0.5 && updateNaturalWidth > 0.5 ? Commons.Style.space(8) : 0)
- readonly property real centerPadding: tokens ? tokens.pillPaddingX : 9
- readonly property real centerSpacing: Commons.Style.space(8)
- readonly property real needMinimal: 2 * centerPadding + clockWidget.implicitWidth
- + (showIndicators ? centerSpacing + statusNaturalWidth : 0)
- readonly property real minimumResponsiveWidth: needMinimal
- readonly property real needCompact: needMinimal
- + (weatherNaturalWidth > 0.5 ? centerSpacing + weatherNaturalWidth : 0)
- readonly property real needNormal: needCompact
- + (dateButton.implicitWidth > 0.5 ? centerSpacing + dateButton.implicitWidth : 0)
- readonly property var weatherWidget: weatherView
- readonly property var indicatorWidget: statusIndicators
- readonly property var updateWidget: updateView
- readonly property var updateBackend: updateLoader.item
- readonly property bool calendarLoaded: calendarLoader.item !== null
- readonly property bool calendarLoaderReady: calendarLoader.item
- ? calendarLoader.item.ready === true : false
- readonly property var weatherSettings: childSettings("omarchy.weather")
- readonly property var clockSettings: childSettings("omarchy.clock")
- readonly property var updateSettings: childSettings("omarchy.system-update")
- readonly property url updateSource: registeredSource("omarchy.system-update")
- property Component updateComponent: String(updateSource) ? null
- : registeredComponent("omarchy.system-update")
-
- implicitWidth: bar && bar.vertical ? bar.barSize
- : Math.round(centerRow.implicitWidth) + 2 * centerPadding
- implicitHeight: bar ? bar.barSize : Commons.Style.space(35)
-
- function childSettings(id) {
- if (bar && typeof bar.widgetSettings === "function")
- return bar.widgetSettings("G8", id)
- return settings && settings[id] && typeof settings[id] === "object"
- ? settings[id] : ({})
- }
-
- function registeredComponent(id) {
- if (bar && typeof bar.registeredWidgetComponent === "function")
- return bar.registeredWidgetComponent(id)
- const registry = bar ? bar.barWidgetRegistry : null
- if (registry) void(registry.revision)
- const widgets = registry && registry.widgets ? registry.widgets : ({})
- const entry = widgets[id]
- return entry ? entry.component : null
- }
-
- function registeredSource(id) {
- return bar && typeof bar.registeredWidgetSource === "function"
- ? bar.registeredWidgetSource(id) : ""
- }
-
- function syncOfficialLoader(loader, component, source, id, childSettingsValue) {
- loader.sourceComponent = null
- loader.source = ""
- if (component !== null) {
- loader.sourceComponent = component
- } else if (String(source)) {
- loader.setSource(source, {
- bar: root.bar,
- moduleName: id,
- settings: childSettingsValue
- })
- }
- }
-
- function syncUpdateLoader() {
- syncOfficialLoader(updateLoader, updateComponent, updateSource,
- "omarchy.system-update", updateSettings)
- }
-
- function injectChild(item, id, childSettingsValue) {
- if (!item) return
- if ("bar" in item) item.bar = bar
- if ("moduleName" in item) item.moduleName = id
- if ("settings" in item) item.settings = childSettingsValue
- }
-
- function injectChildren() {
- injectChild(updateLoader.item, "omarchy.system-update", updateSettings)
- }
-
- function childPanelWidget(pluginId) {
- const id = String(pluginId || "")
- if (id === "omarchy.clock") return root
- if (id === "omarchy.weather") return weatherView
- return null
- }
-
- function updateStage() {
- stage = CenterLayout.nextStage(stage, availableWidth,
- needNormal, needCompact, centered)
- }
-
- function syncCalendarLoader() {
- if (!opened) {
- calendarLoader.source = ""
- return
- }
- calendarLoader.setSource(calendarSource, {
- anchorItem: dateButton,
- bar: root.bar,
- ownerWidget: root,
- clockService: root.clock
- })
- }
-
- onOpenedChanged: syncCalendarLoader()
- onBarChanged: Qt.callLater(injectChildren)
- onUpdateSettingsChanged: injectChildren()
- onUpdateComponentChanged: Qt.callLater(syncUpdateLoader)
- onUpdateSourceChanged: Qt.callLater(syncUpdateLoader)
- onAvailableWidthChanged: restageTimer.restart()
- onNeedNormalChanged: restageTimer.restart()
- onNeedCompactChanged: restageTimer.restart()
- Component.onCompleted: {
- restageTimer.restart()
- Qt.callLater(syncUpdateLoader)
- }
-
- Timer {
- id: restageTimer
- interval: 80
- onTriggered: root.updateStage()
- }
-
- PillSurface {
- bar: root.bar
- anchors.fill: parent
- anchors.topMargin: Math.round((parent.height - root.tokens.pillHeight) / 2)
- anchors.bottomMargin: Math.round((parent.height - root.tokens.pillHeight) / 2)
- }
-
- Row {
- id: centerRow
- anchors.centerIn: parent
- spacing: root.centerSpacing
-
- Item {
- id: weatherHolder
- visible: width > 0.5
- width: root.showWeather ? root.weatherNaturalWidth : 0
- height: root.implicitHeight
- clip: true
- opacity: root.showWeather ? 1 : 0
-
- Behavior on width {
- NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
- }
- Behavior on opacity {
- NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
- }
-
- WeatherWidget {
- id: weatherView
- anchors.centerIn: parent
- bar: root.bar
- settings: root.weatherSettings
- weatherService: root.bar ? root.bar.weatherService : null
- }
- }
-
- ClockWidget {
- id: clockWidget
- bar: root.bar
- settings: root.clockSettings
- }
-
- Item {
- id: dateHolder
- visible: width > 0.5
- width: root.showDate ? dateButton.implicitWidth : 0
- height: root.implicitHeight
- clip: true
- opacity: root.showDate ? 1 : 0
-
- Behavior on width {
- NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
- }
- Behavior on opacity {
- NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
- }
-
- Ui.WidgetButton {
- id: dateButton
- anchors.centerIn: parent
- bar: root.bar
- text: root.dateText
- fontFamily: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- fontSize: root.tokens ? root.tokens.captionSize : 10
- foreground: root.tokens
- ? Commons.Util.alpha(root.tokens.ink, 0.5)
- : Commons.Util.alpha(Commons.Color.foreground, 0.5)
- horizontalMargin: 0
- verticalPadding: 0
- fixedHeight: root.implicitHeight
- tooltipText: Qt.formatDate(root.displayDate, "dddd, d MMMM yyyy")
- onPressed: function(button) {
- if (button === Qt.RightButton && root.bar) root.bar.run("omarchy-menu-timezone")
- else root.toggle()
- }
- }
- }
-
- Item {
- id: indicatorHolder
- visible: root.showIndicators || width > 0.5
- width: root.showIndicators ? root.statusNaturalWidth : 0
- height: root.implicitHeight
- clip: true
- opacity: root.showIndicators ? 1 : 0
-
- Behavior on width {
- NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
- }
- Behavior on opacity {
- NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
- }
-
- Row {
- anchors.centerIn: parent
- spacing: Commons.Style.space(8)
-
- StatusIndicators {
- id: statusIndicators
- width: implicitWidth
- height: root.implicitHeight
- bar: root.bar
- statusService: root.bar ? root.bar.statusService : null
- }
-
- SystemUpdateWidget {
- id: updateView
- bar: root.bar
- settings: root.updateSettings
- backendWidget: updateLoader.item
- }
- }
- }
- }
-
- Loader {
- id: updateLoader
- visible: false
- onLoaded: {
- root.injectChild(item, "omarchy.system-update", root.updateSettings)
- }
- }
-
- Loader { id: calendarLoader }
-}
diff --git a/widgets/ClockWidget.qml b/widgets/ClockWidget.qml
deleted file mode 100644
index c321de1..0000000
--- a/widgets/ClockWidget.qml
+++ /dev/null
@@ -1,76 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.BarWidget {
- id: root
-
- moduleName: "omarchy.clock"
-
- readonly property var clock: bar ? bar.clockService : null
- readonly property date displayDate: clock ? clock.date : new Date()
- readonly property bool clock12h: setting("clock12h", false) === true
- readonly property string timeText: formatTime(displayDate)
- readonly property string tooltipText: Qt.formatDate(displayDate, "ddd, d MMMM yyyy")
-
- implicitWidth: bar && bar.vertical ? bar.barSize : label.implicitWidth
- implicitHeight: bar ? bar.barSize : 35
-
- function pad(value) {
- return Number(value) < 10 ? "0" + Number(value) : String(Number(value))
- }
-
- function formatTime(value) {
- if (!(value instanceof Date) || isNaN(value.getTime())) return "--:--"
- const hours = value.getHours()
- if (clock12h) {
- let displayHours = hours % 12
- if (displayHours === 0) displayHours = 12
- return displayHours + ":" + pad(value.getMinutes())
- + (hours < 12 ? " AM" : " PM")
- }
- return pad(hours) + ":" + pad(value.getMinutes())
- }
-
- function toggleFormat() {
- return bar && typeof bar.setWidgetSetting === "function"
- ? bar.setWidgetSetting("G8", moduleName, "clock12h", !clock12h)
- : false
- }
-
- function openTimezoneMenu() {
- if (!bar || typeof bar.run !== "function") return false
- bar.run("omarchy-menu-timezone")
- return true
- }
-
- Text {
- id: label
- anchors.centerIn: parent
- text: root.bar && root.bar.vertical
- ? Qt.formatTime(root.displayDate, "HH\nmm") : root.timeText
- horizontalAlignment: Text.AlignHCenter
- color: root.bar ? root.bar.foreground : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.bar && root.bar.visualTokens
- ? root.bar.visualTokens.labelSize : 12
- font.letterSpacing: root.bar && root.bar.vertical ? 0 : 1
- renderType: Text.NativeRendering
- }
-
- Ui.WidgetButton {
- anchors.fill: parent
- bar: root.bar
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- tooltipText: root.tooltipText
- onPressed: function(button) {
- if (button === Qt.RightButton) root.openTimezoneMenu()
- else root.toggleFormat()
- }
- }
-}
diff --git a/widgets/CpuPanel.qml b/widgets/CpuPanel.qml
deleted file mode 100644
index b57c080..0000000
--- a/widgets/CpuPanel.qml
+++ /dev/null
@@ -1,238 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var systemTelemetry
- required property var gpuTelemetry
-
- owner: ownerWidget
- open: ownerWidget.opened
- focusTarget: keyCatcher
- gap: 8
- padding: 12
- contentWidth: fittedContentWidth(320)
- contentHeight: fittedContentHeight(panelColumn.implicitHeight)
-
- Component.onCompleted: if (gpuTelemetry) gpuTelemetry.acquire()
- Component.onDestruction: if (gpuTelemetry) gpuTelemetry.release()
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
-
- Column {
- id: panelColumn
- width: parent.width
- spacing: 8
-
- Item {
- width: parent.width
- height: 24
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "CPU · GPU"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 13
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: "\u2715"
- color: closeMouse.containsMouse
- ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
-
- Behavior on color { ColorAnimation { duration: 120 } }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.ownerWidget.close()
- }
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.18) : Commons.Color.popups.border
- }
-
- UsageRow {
- width: parent.width
- label: "CPU"
- value: panel.systemTelemetry.cpuPercent
- bar: panel.bar
- }
-
- UsageRow {
- width: parent.width
- visible: panel.gpuTelemetry && panel.gpuTelemetry.available
- label: "GPU"
- value: panel.gpuTelemetry ? panel.gpuTelemetry.utilization : 0
- bar: panel.bar
- }
-
- Row {
- width: parent.width
- visible: panel.gpuTelemetry && panel.gpuTelemetry.available
- && panel.gpuTelemetry.temperatureC > 0
-
- Text {
- width: parent.width * 0.4
- text: "Temperature"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.65) : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- Text {
- width: parent.width * 0.3
- text: panel.gpuTelemetry ? panel.gpuTelemetry.temperatureC + "°C" : ""
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- }
-
- Row {
- width: parent.width
- visible: panel.gpuTelemetry && panel.gpuTelemetry.memoryTotalMiB > 0
-
- Text {
- width: parent.width * 0.4
- text: "VRAM"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.65) : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- Text {
- width: parent.width * 0.3
- text: panel.gpuTelemetry
- ? panel.gpuTelemetry.memoryUsedMiB + " / " + panel.gpuTelemetry.memoryTotalMiB + " MiB"
- : ""
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Rectangle {
- width: parent.width
- height: 28
- radius: panel.controlRadius
- color: monitorMouse.containsMouse
- ? panel.controlPrimaryHoverColor
- : panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- Behavior on color { ColorAnimation { duration: 120 } }
-
- Text {
- anchors.centerIn: parent
- text: "Open btop"
- color: panel.bar ? panel.bar.background : Commons.Color.background
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: monitorMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- panel.ownerWidget.close()
- panel.ownerWidget.openSystemMonitor()
- }
- }
- }
- }
- }
-
- component UsageRow: Item {
- id: usageRow
-
- required property string label
- required property int value
- required property var bar
-
- height: 16
-
- Text {
- id: usageLabel
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: parent.label
- color: parent.bar ? Qt.rgba(parent.bar.foreground.r, parent.bar.foreground.g,
- parent.bar.foreground.b, 0.65) : Commons.Color.foreground
- font.family: parent.bar ? parent.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- font.letterSpacing: 1
- renderType: Text.NativeRendering
- }
-
- Text {
- id: usageValue
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: parent.value + "%"
- color: parent.bar ? parent.bar.urgent : Commons.Color.accent
- font.family: parent.bar ? parent.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Rectangle {
- anchors.left: usageLabel.right
- anchors.leftMargin: 8
- anchors.right: usageValue.left
- anchors.rightMargin: 8
- anchors.verticalCenter: parent.verticalCenter
- height: 8
- radius: height / 2
- color: panel.controlActiveFillColor
-
- Rectangle {
- width: parent.width * Math.max(0, Math.min(100, usageRow.value)) / 100
- height: parent.height
- radius: height / 2
- color: usageRow.bar ? usageRow.bar.urgent : Commons.Color.accent
- Behavior on width { NumberAnimation { duration: 300 } }
- }
- }
- }
-}
diff --git a/widgets/CpuWave.qml b/widgets/CpuWave.qml
deleted file mode 100644
index e652bac..0000000
--- a/widgets/CpuWave.qml
+++ /dev/null
@@ -1,62 +0,0 @@
-import QtQuick
-import qs.Commons as Commons
-
-Canvas {
- id: root
-
- required property var history
- required property color accent
- property int maxSamples: 30
-
- width: Commons.Style.space(36)
- height: Commons.Style.space(14)
-
- onHistoryChanged: requestPaint()
- onAccentChanged: requestPaint()
- Component.onCompleted: requestPaint()
-
- onPaint: {
- const context = getContext("2d")
- context.clearRect(0, 0, width, height)
- const values = root.history || []
- if (values.length < 2) return
-
- let maximum = 0.25
- for (let i = 0; i < values.length; i++) maximum = Math.max(maximum, values[i])
- maximum = Math.min(1, Math.max(0.25, maximum * 1.15))
-
- const points = []
- for (let j = 0; j < values.length; j++) {
- points.push({
- x: (j / Math.max(1, maxSamples - 1)) * width,
- y: height - (values[j] / maximum) * height
- })
- }
-
- context.beginPath()
- context.moveTo(points[0].x, height)
- context.lineTo(points[0].x, points[0].y)
- for (let k = 1; k < points.length; k++) {
- const controlX = (points[k - 1].x + points[k].x) / 2
- context.bezierCurveTo(controlX, points[k - 1].y, controlX, points[k].y,
- points[k].x, points[k].y)
- }
- context.lineTo(points[points.length - 1].x, height)
- context.closePath()
- context.fillStyle = Qt.rgba(accent.r, accent.g, accent.b, 0.12)
- context.fill()
-
- context.beginPath()
- context.moveTo(points[0].x, points[0].y)
- for (let n = 1; n < points.length; n++) {
- const controlX = (points[n - 1].x + points[n].x) / 2
- context.bezierCurveTo(controlX, points[n - 1].y, controlX, points[n].y,
- points[n].x, points[n].y)
- }
- context.strokeStyle = accent
- context.lineWidth = 1.5
- context.lineCap = "round"
- context.lineJoin = "round"
- context.stroke()
- }
-}
diff --git a/widgets/CpuWidget.qml b/widgets/CpuWidget.qml
deleted file mode 100644
index 9b4b31c..0000000
--- a/widgets/CpuWidget.qml
+++ /dev/null
@@ -1,172 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.cpu"
- manageIpc: false
-
- readonly property var telemetry: bar ? bar.systemTelemetry : null
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property var gpuTelemetry: bar ? bar.gpuTelemetry : null
- readonly property bool compact: setting("compact", false) === true
- readonly property int percent: telemetry ? telemetry.cpuPercent : 0
- readonly property var history: telemetry ? telemetry.cpuHistory : []
- property var acquiredTelemetry: null
-
- implicitWidth: bar && bar.vertical ? bar.barSize : cpuSurface.implicitWidth
- implicitHeight: bar && bar.vertical ? cpuSurface.implicitHeight : bar ? bar.barSize : 28
-
- function syncTelemetryOwner() {
- if (acquiredTelemetry === telemetry) return
- if (acquiredTelemetry) acquiredTelemetry.release("cpu")
- acquiredTelemetry = telemetry
- if (acquiredTelemetry) acquiredTelemetry.acquire("cpu")
- }
-
- function syncPanelLoader() {
- if (!opened) {
- panelLoader.source = ""
- return
- }
- panelLoader.setSource(Qt.resolvedUrl("CpuPanel.qml"), {
- anchorItem: cpuSurface,
- bar: root.bar,
- ownerWidget: root,
- systemTelemetry: root.telemetry,
- gpuTelemetry: root.gpuTelemetry
- })
- }
-
- function openSystemMonitor() {
- if (!bar || typeof bar.run !== "function") return false
- bar.run("omarchy-launch-or-focus-tui btop")
- return true
- }
-
- onTelemetryChanged: syncTelemetryOwner()
- onOpenedChanged: syncPanelLoader()
- Component.onCompleted: syncTelemetryOwner()
- Component.onDestruction: if (acquiredTelemetry) acquiredTelemetry.release("cpu")
-
- Item {
- id: cpuSurface
-
- anchors.centerIn: parent
- implicitWidth: root.bar && root.bar.vertical
- ? root.bar.barSize
- : content.implicitWidth + 2 * root.tokens.pillPaddingX
- implicitHeight: root.bar && root.bar.vertical
- ? content.implicitHeight + Commons.Style.space(10)
- : root.tokens ? root.tokens.slotHeight : 28
- width: implicitWidth
- height: implicitHeight
-
- PillSurface {
- bar: root.bar
- anchors.fill: parent
- anchors.topMargin: Math.round((parent.height - root.tokens.pillHeight) / 2)
- anchors.bottomMargin: Math.round((parent.height - root.tokens.pillHeight) / 2)
- }
-
- Loader {
- id: content
- anchors.centerIn: parent
- sourceComponent: root.bar && root.bar.vertical ? verticalContent : horizontalContent
- }
-
- MouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar) root.bar.showTooltip(cpuSurface, root.percent + "%")
- onExited: if (root.bar) root.bar.hideTooltip(cpuSurface)
- onClicked: {
- if (root.bar) root.bar.hideTooltip(cpuSurface)
- root.toggle()
- }
- }
- }
-
- Loader {
- id: panelLoader
- }
-
- Component {
- id: horizontalContent
-
- Row {
- spacing: root.compact ? root.tokens.compactGap : root.tokens.contentGap
-
- Text {
- visible: !root.compact
- anchors.verticalCenter: parent.verticalCenter
- text: "CPU"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.6) : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: 0.5
- renderType: Text.NativeRendering
- }
-
- CpuWave {
- visible: !root.compact
- anchors.verticalCenter: parent.verticalCenter
- history: root.history
- accent: root.bar ? root.bar.urgent : Commons.Color.accent
- }
-
- IconText {
- visible: root.compact
- anchors.verticalCenter: parent.verticalCenter
- text: "planner_review"
- color: root.bar.urgent
- font.pixelSize: root.tokens.iconSize
- font.weight: Font.DemiBold
- fill: 1
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: String(root.percent).padStart(2, "0") + "%"
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-
- Component {
- id: verticalContent
-
- Column {
- spacing: Commons.Style.space(2)
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "CPU"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.62) : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: String(root.percent).padStart(2, "0") + "%"
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
- }
-}
diff --git a/widgets/HearthstonePickerView.qml b/widgets/HearthstonePickerView.qml
deleted file mode 100644
index 46719c4..0000000
--- a/widgets/HearthstonePickerView.qml
+++ /dev/null
@@ -1,255 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import QtQuick.Shapes
-import qs.Commons as Commons
-import "PickerModel.js" as PickerModel
-
-Item {
- id: root
- required property var bar
- required property var controller
-
- readonly property real cardWidth: Commons.Style.space(232)
- readonly property real cardHeight: Commons.Style.space(330)
- readonly property real stepX: Commons.Style.space(128)
- readonly property real focusLift: Commons.Style.space(54)
- readonly property real focusScale: 1.24
- readonly property real spreadDegrees: 6
- readonly property int maxVisible: 5
- readonly property color frameColor: "#16161c"
- readonly property color textColor: "#ececee"
- readonly property color dimTextColor: Qt.rgba(0.92, 0.92, 0.94, 0.55)
- property real dealProgress: 0
- property bool dealStarted: false
- readonly property bool dealSettled: dealProgress >= 0.999
-
- function startDeal() {
- if (dealStarted || controller.filteredEntries.length === 0) return
- dealStarted = true
- dealProgress = 0
- Qt.callLater(function() { root.dealProgress = 1 })
- }
-
- Component.onCompleted: startDeal()
-
- Connections {
- target: root.controller
- function onFilteredEntriesChanged() { root.startDeal() }
- }
-
- Behavior on dealProgress {
- NumberAnimation {
- duration: root.controller.mode === "theme" ? 260 : 180
- easing.type: Easing.OutCubic
- }
- }
-
- Text {
- visible: root.controller.filteredEntries.length > 0
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- anchors.topMargin: Commons.Style.space(40)
- text: root.controller.mode.toUpperCase() + " "
- + (root.controller.selectedIndex + 1) + " / "
- + root.controller.filteredEntries.length
- color: root.dimTextColor
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 2
- renderType: Text.NativeRendering
- }
-
- Item {
- id: hand
- anchors.centerIn: parent
- anchors.verticalCenterOffset: -Commons.Style.space(16)
- width: parent.width
- height: root.cardHeight + root.focusLift + Commons.Style.space(40)
-
- Repeater {
- model: root.controller.filteredEntries
-
- delegate: Item {
- id: card
- required property int index
- required property var modelData
- readonly property int relative: index - root.controller.selectedIndex
- readonly property bool focused: relative === 0
- readonly property bool nearby: Math.abs(relative) <= root.maxVisible
- readonly property real shade: focused ? 0
- : Math.min(0.62, 0.30 + Math.abs(relative) * 0.05)
- readonly property bool current: root.controller.mode === "theme"
- ? String(modelData.label || "") === String(root.controller.currentSelection || "")
- : String(modelData.sourcePath || "") === String(root.controller.currentSelection || "")
-
- visible: nearby
- width: root.cardWidth
- height: root.cardHeight
- transformOrigin: Item.Bottom
- x: (hand.width - width) / 2
- + relative * root.stepX * root.dealProgress
- y: hand.height - height
- - (focused ? root.focusLift * root.dealProgress : 0)
- z: focused ? 1000 : 500 - Math.min(Math.abs(relative), 40)
- rotation: relative * root.spreadDegrees * root.dealProgress
- scale: focused
- ? 1 + (root.focusScale - 1) * root.dealProgress : 1
- opacity: root.dealProgress
-
- Behavior on x {
- enabled: root.dealSettled
- NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
- }
- Behavior on y {
- enabled: root.dealSettled
- NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
- }
- Behavior on rotation {
- enabled: root.dealSettled
- NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
- }
- Behavior on scale {
- enabled: root.dealSettled
- NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
- }
-
- Item {
- anchors.fill: parent
- anchors.margins: Commons.Style.space(6)
- clip: true
-
- Image {
- anchors.fill: parent
- source: root.controller.thumbnailUrl(card.modelData)
- fillMode: Image.PreserveAspectCrop
- asynchronous: true
- cache: false
- retainWhileLoading: true
- sourceSize.width: Math.round(root.cardWidth * 2)
- sourceSize.height: Math.round(root.cardHeight * 2)
- }
-
- IconText {
- anchors.centerIn: parent
- visible: !card.modelData || !card.modelData.thumbnailReady
- text: root.controller.videoMode ? "movie" : "image"
- color: Qt.rgba(root.textColor.r, root.textColor.g,
- root.textColor.b, 0.34)
- font.pixelSize: Commons.Style.font.displayLarge
- fill: 0
- }
-
- Rectangle {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- height: Commons.Style.space(70)
- gradient: Gradient {
- GradientStop { position: 0; color: "transparent" }
- GradientStop { position: 1; color: Qt.rgba(0, 0, 0, 0.72) }
- }
- }
-
- Text {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- anchors.margins: Commons.Style.space(12)
- text: !card.modelData ? ""
- : root.controller.mediaMode
- ? PickerModel.mediaLabel(card.modelData.sourcePath)
- : String(card.modelData.label || "")
- color: root.textColor
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.space(13)
- font.weight: Font.DemiBold
- horizontalAlignment: Text.AlignHCenter
- elide: Text.ElideRight
- maximumLineCount: 1
- renderType: Text.NativeRendering
- }
-
- Rectangle {
- anchors.fill: parent
- color: "black"
- opacity: card.shade
- Behavior on opacity { NumberAnimation { duration: 180 } }
- }
- }
-
- Shape {
- id: frame
- anchors.fill: parent
- preferredRendererType: Shape.CurveRenderer
- readonly property real outerRadius: Commons.Style.space(18)
- readonly property real matWidth: Commons.Style.space(8)
- readonly property real innerRadius: Commons.Style.space(10)
-
- ShapePath {
- fillRule: ShapePath.OddEvenFill
- fillColor: root.frameColor
- strokeColor: "transparent"
- strokeWidth: 0
- startX: frame.outerRadius
- startY: 0
- PathLine { x: frame.width - frame.outerRadius; y: 0 }
- PathArc { x: frame.width; y: frame.outerRadius; radiusX: frame.outerRadius; radiusY: frame.outerRadius }
- PathLine { x: frame.width; y: frame.height - frame.outerRadius }
- PathArc { x: frame.width - frame.outerRadius; y: frame.height; radiusX: frame.outerRadius; radiusY: frame.outerRadius }
- PathLine { x: frame.outerRadius; y: frame.height }
- PathArc { x: 0; y: frame.height - frame.outerRadius; radiusX: frame.outerRadius; radiusY: frame.outerRadius }
- PathLine { x: 0; y: frame.outerRadius }
- PathArc { x: frame.outerRadius; y: 0; radiusX: frame.outerRadius; radiusY: frame.outerRadius }
- PathMove { x: frame.matWidth + frame.innerRadius; y: frame.matWidth }
- PathLine { x: frame.width - frame.matWidth - frame.innerRadius; y: frame.matWidth }
- PathArc { x: frame.width - frame.matWidth; y: frame.matWidth + frame.innerRadius; radiusX: frame.innerRadius; radiusY: frame.innerRadius }
- PathLine { x: frame.width - frame.matWidth; y: frame.height - frame.matWidth - frame.innerRadius }
- PathArc { x: frame.width - frame.matWidth - frame.innerRadius; y: frame.height - frame.matWidth; radiusX: frame.innerRadius; radiusY: frame.innerRadius }
- PathLine { x: frame.matWidth + frame.innerRadius; y: frame.height - frame.matWidth }
- PathArc { x: frame.matWidth; y: frame.height - frame.matWidth - frame.innerRadius; radiusX: frame.innerRadius; radiusY: frame.innerRadius }
- PathLine { x: frame.matWidth; y: frame.matWidth + frame.innerRadius }
- PathArc { x: frame.matWidth + frame.innerRadius; y: frame.matWidth; radiusX: frame.innerRadius; radiusY: frame.innerRadius }
- }
-
- ShapePath {
- fillColor: "transparent"
- strokeColor: card.focused ? root.bar.urgent : "transparent"
- strokeWidth: card.focused ? Commons.Style.space(2) : 0
- startX: frame.outerRadius
- startY: 0
- PathLine { x: frame.width - frame.outerRadius; y: 0 }
- PathArc { x: frame.width; y: frame.outerRadius; radiusX: frame.outerRadius; radiusY: frame.outerRadius }
- PathLine { x: frame.width; y: frame.height - frame.outerRadius }
- PathArc { x: frame.width - frame.outerRadius; y: frame.height; radiusX: frame.outerRadius; radiusY: frame.outerRadius }
- PathLine { x: frame.outerRadius; y: frame.height }
- PathArc { x: 0; y: frame.height - frame.outerRadius; radiusX: frame.outerRadius; radiusY: frame.outerRadius }
- PathLine { x: 0; y: frame.outerRadius }
- PathArc { x: frame.outerRadius; y: 0; radiusX: frame.outerRadius; radiusY: frame.outerRadius }
- }
- }
-
- Rectangle {
- visible: card.current
- width: Commons.Style.space(9)
- height: width
- radius: width / 2
- x: Commons.Style.space(14)
- y: Commons.Style.space(14)
- z: 5
- color: root.bar.urgent
- border.color: Qt.rgba(0, 0, 0, 0.35)
- border.width: 1
- }
-
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: card.focused
- ? root.controller.activateSelected()
- : root.controller.selectIndex(card.index)
- }
- }
- }
- }
-}
diff --git a/widgets/MediaMuse.qml b/widgets/MediaMuse.qml
deleted file mode 100644
index fbd0922..0000000
--- a/widgets/MediaMuse.qml
+++ /dev/null
@@ -1,142 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-
-Item {
- id: root
-
- property var levels: []
- property color tint: "white"
- property bool playing: false
- readonly property int bandCount: 24
- readonly property int renderedBandCount: bandCount
-
- implicitWidth: 144
- implicitHeight: 28
- width: implicitWidth
- height: implicitHeight
-
- Row {
- anchors.centerIn: parent
- spacing: 7
-
- Item {
- id: vinylMark
- width: 18
- height: 18
- anchors.verticalCenter: parent.verticalCenter
- transformOrigin: Item.Center
-
- Canvas {
- anchors.fill: parent
- antialiasing: true
- property color canvasTint: root.tint
- onCanvasTintChanged: requestPaint()
- onPaint: {
- const ctx = getContext("2d")
- ctx.clearRect(0, 0, width, height)
- ctx.strokeStyle = canvasTint
- ctx.fillStyle = canvasTint
- ctx.lineWidth = 1.4
-
- ctx.beginPath()
- ctx.arc(width / 2, height / 2, 7.2, 0, Math.PI * 2)
- ctx.stroke()
- ctx.globalAlpha = 0.55
- ctx.beginPath()
- ctx.arc(width / 2, height / 2, 4.6, -0.35, 2.35)
- ctx.stroke()
- ctx.beginPath()
- ctx.arc(width / 2, height / 2, 4.6, 2.8, 5.5)
- ctx.stroke()
- ctx.globalAlpha = 1
- ctx.beginPath()
- ctx.arc(width / 2, height / 2, 1.45, 0, Math.PI * 2)
- ctx.fill()
- }
- Component.onCompleted: requestPaint()
- }
-
- NumberAnimation on rotation {
- from: 0
- to: 360
- duration: 3200
- loops: Animation.Infinite
- running: root.visible && root.playing
- }
- }
-
- Item {
- width: 96
- height: 22
- anchors.verticalCenter: parent.verticalCenter
-
- Repeater {
- model: root.bandCount
-
- Rectangle {
- required property int index
- readonly property real level: {
- const values = root.levels || []
- return values[index] === undefined
- ? 0.04 : Math.max(0, Math.min(1, Number(values[index]) || 0))
- }
- readonly property real halfHeight: 1 + level * 9
-
- x: index * ((96 - 2) / (root.bandCount - 1))
- anchors.verticalCenter: parent.verticalCenter
- width: 2
- height: halfHeight * 2
- radius: 1
- color: root.tint
- antialiasing: true
- }
- }
- }
-
- Item {
- width: 16
- height: 18
- anchors.verticalCenter: parent.verticalCenter
-
- Row {
- anchors.centerIn: parent
- spacing: 3
- visible: root.playing
-
- Repeater {
- model: 2
- Rectangle {
- required property int index
- width: 3
- height: 10
- radius: 1
- color: root.tint
- }
- }
- }
-
- Canvas {
- anchors.centerIn: parent
- width: 11
- height: 12
- visible: !root.playing
- antialiasing: true
- property color canvasTint: root.tint
- onCanvasTintChanged: requestPaint()
- onPaint: {
- const ctx = getContext("2d")
- ctx.clearRect(0, 0, width, height)
- ctx.fillStyle = canvasTint
- ctx.beginPath()
- ctx.moveTo(2, 1)
- ctx.lineTo(width - 1, height / 2)
- ctx.lineTo(2, height - 1)
- ctx.closePath()
- ctx.fill()
- }
- Component.onCompleted: requestPaint()
- }
- }
- }
-}
diff --git a/widgets/MediaPanel.qml b/widgets/MediaPanel.qml
deleted file mode 100644
index 8e53fb0..0000000
--- a/widgets/MediaPanel.qml
+++ /dev/null
@@ -1,585 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var mediaService
- property var spectrumService: null
- property bool spectrumEnabled: true
- property string focusSection: "controls"
- property int cursorIndex: 1
- property real currentPosition: 0
- property real currentLength: 0
- property real lastReportedPosition: -1
- property var leasedSpectrumService: null
-
- readonly property var player: mediaService ? mediaService.activePlayer : null
- readonly property var sourcePlayers: mediaService && mediaService.sourcePlayers
- ? mediaService.sourcePlayers : []
- readonly property bool active: mediaService
- ? mediaService.hasMedia === true && player !== null : false
- readonly property bool playing: active && player.isPlaying === true
- readonly property bool spectrumRequested: open && active && spectrumEnabled
- readonly property var levels: spectrumService && spectrumService.levels
- ? spectrumService.levels : flatLevels(active ? 0.04 : 0.02)
- readonly property var spectrumThemeColors: spectrumService
- && spectrumService.themeColors ? spectrumService.themeColors : []
- readonly property int renderedSourceCount: sourceRepeater.count
- readonly property bool spectrumWorkerRunning: spectrumService
- ? spectrumService.workerRunning === true : false
- readonly property string spectrumState: spectrumService
- ? String(spectrumService.state || "inactive") : "unavailable"
- readonly property string playerName: player
- ? String(player.identity || player.desktopEntry || "") : ""
-
- owner: ownerWidget
- open: ownerWidget.opened
- focusTarget: keyCatcher
- contentWidth: fittedContentWidth(Commons.Style.space(320))
- contentHeight: fittedContentHeight(content.implicitHeight)
-
- function flatLevels(value) {
- const result = []
- for (let i = 0; i < 24; i++) result.push(Number(value) || 0)
- return result
- }
-
- function playerKey(target) {
- return mediaService && typeof mediaService.playerKey === "function"
- ? String(mediaService.playerKey(target) || "") : ""
- }
-
- function runAction(action) {
- return mediaService && typeof mediaService.runAction === "function"
- ? mediaService.runAction(String(action || ""), false,
- playerKey(player)) === true : false
- }
-
- function selectSource(index) {
- if (index < 0 || index >= sourcePlayers.length || !mediaService
- || typeof mediaService.selectPlayer !== "function") return false
- return mediaService.selectPlayer(playerKey(sourcePlayers[index])) === true
- }
-
- function activateCursor() {
- if (focusSection === "sources") return selectSource(cursorIndex)
- if (cursorIndex === 0) return runAction("previous")
- if (cursorIndex === 2) return runAction("next")
- return runAction("playPause")
- }
-
- function moveCursor(dx, dy) {
- if (focusSection === "controls") {
- if (dy > 0 && sourcePlayers.length > 1) {
- focusSection = "sources"
- cursorIndex = 0
- } else if (dx !== 0) {
- cursorIndex = Math.max(0, Math.min(2, cursorIndex + dx))
- }
- return
- }
-
- if (dy < 0 && cursorIndex === 0) {
- focusSection = "controls"
- cursorIndex = 1
- } else if (dy !== 0 && sourcePlayers.length > 0) {
- cursorIndex = (cursorIndex + dy + sourcePlayers.length) % sourcePlayers.length
- }
- }
-
- function syncPosition() {
- if (!player) return
- const reported = Number(player.position || 0)
- currentLength = Math.max(0, Number(player.length || 0))
- if (Math.abs(reported - lastReportedPosition) > 0.05) {
- currentPosition = reported
- lastReportedPosition = reported
- } else if (playing) {
- const cap = currentLength > 0 ? currentLength : reported + 1e9
- currentPosition = Math.min(cap, currentPosition + 0.5)
- }
- }
-
- function resetPosition() {
- currentPosition = player ? Number(player.position || 0) : 0
- currentLength = player ? Math.max(0, Number(player.length || 0)) : 0
- lastReportedPosition = -1
- }
-
- function formatTime(seconds) {
- const value = Math.max(0, Math.floor(Number(seconds) || 0))
- const minutes = Math.floor(value / 60)
- const rest = value % 60
- return minutes + ":" + String(rest).padStart(2, "0")
- }
-
- function syncSpectrumLease() {
- const next = spectrumRequested ? spectrumService : null
- if (leasedSpectrumService === next) return
- if (leasedSpectrumService
- && typeof leasedSpectrumService.endSpectrum === "function")
- leasedSpectrumService.endSpectrum(panel)
- leasedSpectrumService = next
- if (leasedSpectrumService
- && typeof leasedSpectrumService.beginSpectrum === "function")
- leasedSpectrumService.beginSpectrum(panel)
- }
-
- function releaseSpectrumLease() {
- if (leasedSpectrumService
- && typeof leasedSpectrumService.endSpectrum === "function")
- leasedSpectrumService.endSpectrum(panel)
- leasedSpectrumService = null
- }
-
- onPlayerChanged: resetPosition()
- onPlayingChanged: {
- lastReportedPosition = -1
- }
- onSpectrumRequestedChanged: syncSpectrumLease()
- onSpectrumServiceChanged: syncSpectrumLease()
- onSourcePlayersChanged: {
- if (focusSection === "sources" && sourcePlayers.length <= 1) {
- focusSection = "controls"
- cursorIndex = 1
- } else if (focusSection === "sources") {
- cursorIndex = Math.max(0, Math.min(cursorIndex, sourcePlayers.length - 1))
- }
- }
- Component.onCompleted: {
- resetPosition()
- syncSpectrumLease()
- }
- Component.onDestruction: releaseSpectrumLease()
-
- property Timer positionTimer: Timer {
- interval: 500
- repeat: true
- running: panel.open && panel.active
- triggeredOnStart: true
- onTriggered: panel.syncPosition()
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
- onMoveRequested: function(dx, dy) { panel.moveCursor(dx, dy) }
- onActivateRequested: panel.activateCursor()
-
- Column {
- id: content
- width: parent.width
- spacing: Commons.Style.space(9)
-
- Item {
- width: parent.width
- height: Commons.Style.space(24)
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "NOW PLAYING"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Row {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(8)
-
- Text {
- visible: panel.active && panel.playerName !== ""
- anchors.verticalCenter: parent.verticalCenter
- text: panel.playerName
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.45)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
-
- IconAction {
- anchors.verticalCenter: parent.verticalCenter
- icon: "close"
- tooltip: "Close"
- onClicked: panel.ownerWidget.close()
- }
- }
- }
-
- Ui.PanelSeparator {
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(10)
- visible: panel.active
-
- Rectangle {
- width: Commons.Style.space(56)
- height: width
- radius: Commons.Style.space(5)
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.10)
- : Commons.Color.background
- clip: true
-
- Image {
- anchors.fill: parent
- source: panel.player ? String(panel.player.trackArtUrl || "") : ""
- fillMode: Image.PreserveAspectCrop
- asynchronous: true
- cache: true
- retainWhileLoading: true
- }
-
- IconText {
- anchors.centerIn: parent
- visible: !panel.player || !panel.player.trackArtUrl
- text: "music_note"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.pixelSize: Commons.Style.font.displayLarge
- fill: 1
- }
- }
-
- Column {
- width: parent.width - Commons.Style.space(66)
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(3)
-
- Text {
- width: parent.width
- text: panel.player ? String(panel.player.trackTitle || "Unknown") : ""
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: Font.Medium
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- visible: text !== ""
- text: panel.player ? String(panel.player.trackArtist || "") : ""
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.68)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- visible: text !== ""
- text: panel.player ? String(panel.player.trackAlbum || "") : ""
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.45)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
- }
-
- Item {
- width: parent.width
- height: Commons.Style.space(18)
- visible: panel.active && panel.currentLength > 0
-
- Rectangle {
- id: progressTrack
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- height: Commons.Style.space(4)
- radius: height / 2
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.12)
- : Commons.Color.background
-
- Rectangle {
- height: parent.height
- width: parent.width * Math.min(1,
- panel.currentPosition / Math.max(1, panel.currentLength))
- radius: height / 2
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- Behavior on width { NumberAnimation { duration: 450 } }
- }
- }
-
- Text {
- anchors.left: parent.left
- anchors.top: progressTrack.bottom
- anchors.topMargin: 2
- text: panel.formatTime(panel.currentPosition)
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: parent.right
- anchors.top: progressTrack.bottom
- anchors.topMargin: 2
- text: panel.formatTime(panel.currentLength)
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
-
- Item {
- width: parent.width
- height: Commons.Style.space(40)
-
- MediaSpectrum {
- anchors.fill: parent
- visible: panel.active
- levels: panel.levels
- tint: panel.bar ? panel.bar.urgent : Commons.Color.accent
- themeColors: panel.spectrumThemeColors
- opacity: panel.playing ? 1 : 0.5
- }
-
- Column {
- anchors.centerIn: parent
- visible: !panel.active
- spacing: 1
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "No song playing"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "no active player"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.32)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
- }
-
- Row {
- visible: panel.active
- anchors.horizontalCenter: parent.horizontalCenter
- spacing: Commons.Style.space(12)
-
- MediaPanelButton {
- icon: "skip_previous"
- action: "previous"
- controlIndex: 0
- enabled: panel.player && panel.player.canGoPrevious === true
- }
- MediaPanelButton {
- icon: panel.playing ? "pause" : "play_arrow"
- action: "playPause"
- controlIndex: 1
- accent: true
- enabled: panel.player && (panel.player.canTogglePlaying === true
- || panel.player.canPlay === true || panel.player.canPause === true)
- }
- MediaPanelButton {
- icon: "skip_next"
- action: "next"
- controlIndex: 2
- enabled: panel.player && panel.player.canGoNext === true
- }
- }
-
- Ui.PanelSeparator {
- visible: panel.sourcePlayers.length > 1
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- }
-
- Column {
- width: parent.width
- visible: panel.sourcePlayers.length > 1
- spacing: Commons.Style.space(2)
-
- Repeater {
- id: sourceRepeater
- model: panel.sourcePlayers
-
- delegate: Rectangle {
- id: sourceRow
- required property var modelData
- required property int index
- readonly property bool selected: panel.playerKey(panel.player)
- === panel.playerKey(modelData)
- readonly property bool cursor: panel.focusSection === "sources"
- && panel.cursorIndex === index
-
- width: parent.width
- height: Commons.Style.space(34)
- radius: panel.controlRadius
- color: cursor || sourceMouse.containsMouse
- ? panel.bar ? Qt.rgba(panel.bar.urgent.r, panel.bar.urgent.g,
- panel.bar.urgent.b, 0.14) : Commons.Color.background
- : "transparent"
-
- Row {
- anchors.fill: parent
- anchors.leftMargin: Commons.Style.space(6)
- anchors.rightMargin: Commons.Style.space(6)
- spacing: Commons.Style.space(7)
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: sourceRow.modelData && sourceRow.modelData.isPlaying
- ? "pause" : "play_arrow"
- color: sourceRow.selected && panel.bar
- ? panel.bar.urgent : panel.bar
- ? panel.bar.foreground : Commons.Color.foreground
- font.pixelSize: Commons.Style.font.icon
- fill: sourceRow.selected ? 1 : 0
- }
-
- Column {
- width: parent.width - Commons.Style.space(30)
- anchors.verticalCenter: parent.verticalCenter
- spacing: 1
-
- Text {
- width: parent.width
- text: sourceRow.modelData
- ? String(sourceRow.modelData.trackTitle
- || sourceRow.modelData.identity || "Media source")
- : "Media source"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- font.weight: sourceRow.selected ? Font.Medium : Font.Normal
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- visible: text !== ""
- text: sourceRow.modelData
- ? String(sourceRow.modelData.trackArtist
- || sourceRow.modelData.identity || "") : ""
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.52)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
- }
-
- MouseArea {
- id: sourceMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: {
- panel.focusSection = "sources"
- panel.cursorIndex = sourceRow.index
- }
- onClicked: panel.selectSource(sourceRow.index)
- }
- }
- }
- }
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-
- component MediaPanelButton: Ui.PanelActionButton {
- required property string icon
- required property string action
- required property int controlIndex
- property bool accent: false
-
- iconText: icon
- foreground: accent && panel.bar
- ? panel.bar.urgent : panel.bar ? panel.bar.foreground : Commons.Color.foreground
- hoverColor: panel.bar ? panel.bar.urgent : Commons.Color.accent
- fontFamily: "Material Symbols Rounded"
- fontSize: accent ? Commons.Style.font.iconLarge : Commons.Style.font.icon
- size: accent ? Commons.Style.space(34) : Commons.Style.space(28)
- radius: panel.controlRadius
- hasCursor: panel.focusSection === "controls" && panel.cursorIndex === controlIndex
- onHovered: function(isHovered) {
- if (isHovered) {
- panel.focusSection = "controls"
- panel.cursorIndex = controlIndex
- }
- }
- onClicked: panel.runAction(action)
- }
-}
diff --git a/widgets/MediaPulse.qml b/widgets/MediaPulse.qml
deleted file mode 100644
index f913500..0000000
--- a/widgets/MediaPulse.qml
+++ /dev/null
@@ -1,80 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-
-Item {
- id: root
-
- required property bool playing
- property color tint: "white"
- property real barH1: 0.08
- property real barH2: 0.08
- property real barH3: 0.08
-
- implicitWidth: 16
- implicitHeight: 14
-
- function reset() { restAnimation.restart() }
-
- onPlayingChanged: if (!playing) reset()
-
- SequentialAnimation {
- running: root.visible && root.playing
- loops: Animation.Infinite
- NumberAnimation { target: root; property: "barH1"; to: 0.85; duration: 220; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH1"; to: 0.18; duration: 300; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH1"; to: 0.70; duration: 260; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH1"; to: 0.10; duration: 280; easing.type: Easing.InOutSine }
- }
-
- SequentialAnimation {
- running: root.visible && root.playing
- loops: Animation.Infinite
- NumberAnimation { target: root; property: "barH2"; to: 0.45; duration: 310; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH2"; to: 0.92; duration: 280; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH2"; to: 0.28; duration: 340; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH2"; to: 0.65; duration: 290; easing.type: Easing.InOutSine }
- }
-
- SequentialAnimation {
- running: root.visible && root.playing
- loops: Animation.Infinite
- NumberAnimation { target: root; property: "barH3"; to: 0.60; duration: 380; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH3"; to: 0.12; duration: 320; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH3"; to: 0.95; duration: 350; easing.type: Easing.InOutSine }
- NumberAnimation { target: root; property: "barH3"; to: 0.32; duration: 400; easing.type: Easing.InOutSine }
- }
-
- ParallelAnimation {
- id: restAnimation
- NumberAnimation { target: root; property: "barH1"; to: 0.08; duration: 380; easing.type: Easing.OutCubic }
- NumberAnimation { target: root; property: "barH2"; to: 0.08; duration: 430; easing.type: Easing.OutCubic }
- NumberAnimation { target: root; property: "barH3"; to: 0.08; duration: 480; easing.type: Easing.OutCubic }
- }
-
- Row {
- anchors.fill: parent
- spacing: 2
-
- Repeater {
- model: 3
-
- delegate: Item {
- required property int index
- readonly property real level: index === 0 ? root.barH1
- : index === 1 ? root.barH2 : root.barH3
- width: 4
- height: parent.height
-
- Rectangle {
- anchors.bottom: parent.bottom
- anchors.horizontalCenter: parent.horizontalCenter
- width: 3
- height: Math.max(width, parent.height * parent.level)
- radius: width / 2
- color: root.tint
- }
- }
- }
- }
-}
diff --git a/widgets/MediaSpectrum.qml b/widgets/MediaSpectrum.qml
deleted file mode 100644
index 329b958..0000000
--- a/widgets/MediaSpectrum.qml
+++ /dev/null
@@ -1,57 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-
-Canvas {
- id: root
-
- property var levels: []
- property color tint: "white"
- property var themeColors: []
-
- onLevelsChanged: requestPaint()
- onTintChanged: requestPaint()
- onThemeColorsChanged: requestPaint()
- onWidthChanged: requestPaint()
- onHeightChanged: requestPaint()
- Component.onCompleted: requestPaint()
-
- onPaint: {
- const ctx = getContext("2d")
- ctx.clearRect(0, 0, width, height)
- const values = levels || []
- if (values.length === 0) return
-
- const barWidth = 4
- const gap = Math.max(1, (width - values.length * barWidth) / (values.length + 1))
- const maxHeight = Math.max(barWidth, height - 2)
- const palette = themeColors || []
- if (palette.length > 1) {
- const gradient = ctx.createLinearGradient(0, height, 0, 0)
- for (let index = 0; index < palette.length; index++)
- gradient.addColorStop(index / (palette.length - 1), palette[index])
- ctx.fillStyle = gradient
- } else {
- ctx.fillStyle = palette.length === 1 ? palette[0] : tint
- }
-
- for (let i = 0; i < values.length; i++) {
- const barHeight = Math.max(barWidth,
- Math.min(1, Number(values[i]) || 0) * maxHeight)
- const x = gap + i * (barWidth + gap)
- const y = height - barHeight
- const radius = barWidth / 2
-
- ctx.beginPath()
- ctx.moveTo(x + radius, y)
- ctx.lineTo(x + barWidth - radius, y)
- ctx.arcTo(x + barWidth, y, x + barWidth, y + radius, radius)
- ctx.lineTo(x + barWidth, y + barHeight)
- ctx.lineTo(x, y + barHeight)
- ctx.lineTo(x, y + radius)
- ctx.arcTo(x, y, x + radius, y, radius)
- ctx.closePath()
- ctx.fill()
- }
- }
-}
diff --git a/widgets/MediaWidget.qml b/widgets/MediaWidget.qml
deleted file mode 100644
index d1358f0..0000000
--- a/widgets/MediaWidget.qml
+++ /dev/null
@@ -1,349 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import QtQuick.Effects
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.media"
- manageIpc: false
- property url panelSource: Qt.resolvedUrl("MediaPanel.qml")
- readonly property var mediaService: bar && bar.shell
- && typeof bar.shell.firstPartyServiceFor === "function"
- ? bar.shell.firstPartyServiceFor("omarchy.media") : null
- readonly property var spectrumService: bar && bar.shell
- && typeof bar.shell.serviceFor === "function"
- ? bar.shell.serviceFor("hancore.shibumi.media") : null
- readonly property var activePlayer: mediaService ? mediaService.activePlayer : null
- readonly property bool active: mediaService
- ? mediaService.hasMedia === true && activePlayer !== null : false
- readonly property bool playing: active && activePlayer.isPlaying === true
- readonly property string title: activePlayer ? String(activePlayer.trackTitle || "") : ""
- readonly property string artist: activePlayer ? String(activePlayer.trackArtist || "") : ""
- readonly property string trackLabel: title && artist
- ? title + " · " + artist : title || artist
- readonly property string tooltipText: active
- ? (artist ? artist + " — " + title : title) : "No active media player"
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool fullMode: setting("compact", false) === true
- readonly property bool spectrumEnabled: setting("spectrum", true) !== false
- readonly property bool spectrumRequested: visible && active && fullMode
- && (!bar || !bar.vertical)
- readonly property var museLevels: spectrumService && spectrumService.levels
- ? spectrumService.levels : []
- readonly property string spectrumState: spectrumService
- ? String(spectrumService.state || "inactive") : "unavailable"
- property var leasedSpectrumService: null
- readonly property bool panelLoaded: panelLoader.item !== null
- readonly property var panelItem: panelLoader.item
- readonly property var interactionTarget: actionButton
- readonly property var museInteractionTarget: museMouse
- readonly property bool museVisible: muse.visible
- readonly property int museBandCount: muse.renderedBandCount
-
- visible: mediaService !== null
- implicitWidth: visible ? (bar && bar.vertical
- ? bar.barSize : mediaSurface.implicitWidth) : 0
- implicitHeight: visible ? (bar && bar.vertical
- ? mediaSurface.implicitHeight : bar ? bar.barSize : 28) : 0
-
- function runAction(action) {
- return mediaService && typeof mediaService.runAction === "function"
- ? mediaService.runAction(String(action || ""), false) === true : false
- }
-
- function runMusePrimaryAction() {
- return runAction("playPause")
- }
-
- function runMuseWheel(delta) {
- if (Number(delta) === 0) return false
- return runAction(Number(delta) > 0 ? "next" : "previous")
- }
-
- function syncSpectrumLease() {
- const next = spectrumRequested ? spectrumService : null
- if (leasedSpectrumService === next) return
- if (leasedSpectrumService
- && typeof leasedSpectrumService.endSpectrum === "function")
- leasedSpectrumService.endSpectrum(root)
- leasedSpectrumService = next
- if (leasedSpectrumService
- && typeof leasedSpectrumService.beginSpectrum === "function")
- leasedSpectrumService.beginSpectrum(root)
- }
-
- function releaseSpectrumLease() {
- if (leasedSpectrumService
- && typeof leasedSpectrumService.endSpectrum === "function")
- leasedSpectrumService.endSpectrum(root)
- leasedSpectrumService = null
- }
-
- function syncPanelLoader() {
- if (!opened) {
- panelLoader.source = ""
- return
- }
- panelLoader.setSource(panelSource, {
- anchorItem: mediaSurface,
- bar: root.bar,
- ownerWidget: root,
- mediaService: root.mediaService,
- spectrumService: root.spectrumService,
- spectrumEnabled: root.spectrumEnabled
- })
- }
-
- onOpenedChanged: syncPanelLoader()
- onSpectrumRequestedChanged: syncSpectrumLease()
- onSpectrumServiceChanged: syncSpectrumLease()
- Component.onCompleted: syncSpectrumLease()
- Component.onDestruction: releaseSpectrumLease()
-
- Item {
- id: mediaSurface
- anchors.centerIn: parent
- implicitWidth: !root.tokens ? 0 : root.bar && root.bar.vertical
- ? root.bar.barSize
- : (root.active
- ? (root.fullMode ? muse.implicitWidth : activeRow.implicitWidth)
- : idleIcon.implicitWidth)
- + 2 * root.tokens.pillPaddingX
- implicitHeight: root.bar && root.bar.vertical
- ? Commons.Style.space(34) : root.tokens ? root.tokens.slotHeight : 28
- width: implicitWidth
- height: implicitHeight
-
- PillSurface {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- bar: root.bar
- }
-
- // This is the one registered surface used by KeyboardPanel's bar-click
- // forwarding. Transport controls above it retain their dedicated actions.
- Ui.WidgetButton {
- id: actionButton
- anchors.fill: parent
- bar: root.mediaService ? root.bar : null
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- fixedWidth: mediaSurface.width
- fixedHeight: mediaSurface.height
- tooltipText: root.tooltipText
- onPressed: function(button) {
- if (button === Qt.RightButton || !root.active) root.toggle()
- else if (button === Qt.MiddleButton) root.runAction("next")
- else root.runAction("playPause")
- }
- onWheelMoved: function(delta) {
- root.runAction(delta > 0 ? "previous" : "next")
- }
- }
-
- IconText {
- id: idleIcon
- anchors.centerIn: parent
- visible: !root.active
- text: "music_note"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.45) : Commons.Color.foreground
- font.pixelSize: 15
- fill: 0
- }
-
- Row {
- id: activeRow
- visible: root.active && !root.fullMode
- && (!root.bar || !root.bar.vertical)
- anchors.centerIn: parent
- spacing: root.tokens ? root.tokens.compactGap : Commons.Style.space(4)
-
- MediaControl {
- icon: "skip_previous"
- enabled: root.activePlayer && root.activePlayer.canGoPrevious === true
- action: "previous"
- }
-
- MediaControl {
- icon: root.playing ? "pause" : "play_arrow"
- enabled: root.activePlayer && (root.activePlayer.canTogglePlaying === true
- || root.activePlayer.canPlay === true || root.activePlayer.canPause === true)
- action: "playPause"
- accent: true
- }
-
- MediaControl {
- icon: "skip_next"
- enabled: root.activePlayer && root.activePlayer.canGoNext === true
- action: "next"
- }
-
- Item {
- id: marqueeMask
- width: Commons.Style.space(88)
- height: mediaSurface.height
- visible: false
- layer.enabled: true
-
- Rectangle {
- anchors.fill: parent
- gradient: Gradient {
- orientation: Gradient.Horizontal
- GradientStop { position: 0.0; color: "white" }
- GradientStop { position: 0.92; color: "white" }
- GradientStop { position: 1.0; color: "transparent" }
- }
- }
- }
-
- Item {
- id: marqueeClip
- width: Commons.Style.space(88)
- height: mediaSurface.height
- anchors.verticalCenter: parent.verticalCenter
- layer.enabled: true
- layer.effect: MultiEffect {
- maskEnabled: true
- maskSource: marqueeMask
- maskThresholdMin: 0.5
- maskSpreadAtMin: 0.5
- }
-
- function reset() {
- marqueeAnimation.stop()
- marqueeText.x = 0
- Qt.callLater(function() {
- if (root.visible && root.playing && !root.opened
- && marqueeText.implicitWidth > marqueeClip.width)
- marqueeAnimation.start()
- })
- }
-
- Text {
- id: marqueeText
- anchors.verticalCenter: parent.verticalCenter
- text: root.trackLabel
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, root.playing ? 0.85 : 0.42)
- : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens ? root.tokens.labelSize : Commons.Style.font.body
- renderType: Text.NativeRendering
- onTextChanged: marqueeClip.reset()
- }
-
- Connections {
- target: root
- function onPlayingChanged() { marqueeClip.reset() }
- function onOpenedChanged() { marqueeClip.reset() }
- function onVisibleChanged() { marqueeClip.reset() }
- }
-
- SequentialAnimation {
- id: marqueeAnimation
- loops: Animation.Infinite
- PauseAnimation { duration: 2000 }
- NumberAnimation {
- target: marqueeText
- property: "x"
- to: -(marqueeText.implicitWidth - marqueeClip.width + 4)
- duration: Math.max(100,
- marqueeText.implicitWidth - marqueeClip.width + 4) * 20
- easing.type: Easing.Linear
- }
- PauseAnimation { duration: 900 }
- PropertyAction { target: marqueeText; property: "x"; value: 0 }
- }
- }
-
- MediaPulse {
- anchors.verticalCenter: parent.verticalCenter
- playing: root.playing
- tint: root.bar ? root.bar.urgent : Commons.Color.accent
- }
- }
-
- MediaMuse {
- id: muse
- visible: root.active && root.fullMode
- && (!root.bar || !root.bar.vertical)
- anchors.centerIn: parent
- levels: root.museLevels
- playing: root.playing
- tint: root.bar ? root.bar.urgent : Commons.Color.accent
-
- MouseArea {
- id: museMouse
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.LeftButton
- cursorShape: Qt.PointingHandCursor
- onClicked: root.runMusePrimaryAction()
- onWheel: function(wheel) {
- if (wheel.angleDelta.y === 0) return
- root.runMuseWheel(wheel.angleDelta.y)
- wheel.accepted = true
- }
- }
- }
-
- Column {
- visible: root.active && root.bar && root.bar.vertical
- anchors.centerIn: parent
- spacing: Commons.Style.space(2)
-
- IconText {
- anchors.horizontalCenter: parent.horizontalCenter
- text: root.playing ? "pause" : "play_arrow"
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.pixelSize: root.tokens ? root.tokens.iconSize : Commons.Style.font.body
- fill: 1
- }
-
- MediaPulse {
- anchors.horizontalCenter: parent.horizontalCenter
- playing: root.playing
- tint: root.bar ? root.bar.urgent : Commons.Color.accent
- scale: 0.75
- }
- }
- }
-
- Loader { id: panelLoader }
-
- component MediaControl: Item {
- required property string icon
- required property string action
- property bool accent: false
-
- implicitWidth: Commons.Style.space(18)
- implicitHeight: mediaSurface.height
- opacity: enabled ? 1 : 0.28
-
- IconText {
- anchors.centerIn: parent
- text: parent.icon
- color: parent.accent && root.bar
- ? root.bar.urgent : root.bar ? root.bar.foreground : Commons.Color.foreground
- font.pixelSize: 13
- fill: parent.accent ? 1 : 0
- }
-
- MouseArea {
- anchors.fill: parent
- enabled: parent.enabled
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: root.runAction(parent.action)
- }
- }
-}
diff --git a/widgets/MemoryPanel.qml b/widgets/MemoryPanel.qml
deleted file mode 100644
index de50505..0000000
--- a/widgets/MemoryPanel.qml
+++ /dev/null
@@ -1,213 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var telemetry
-
- owner: ownerWidget
- open: ownerWidget.opened
- focusTarget: keyCatcher
- gap: 8
- padding: 12
- contentWidth: fittedContentWidth(320)
- contentHeight: fittedContentHeight(panelColumn.implicitHeight)
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
-
- Column {
- id: panelColumn
- width: parent.width
- spacing: 8
-
- Item {
- width: parent.width
- height: 24
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "Memory"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 13
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: "\u2715"
- color: closeMouse.containsMouse
- ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
-
- Behavior on color { ColorAnimation { duration: 120 } }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.ownerWidget.close()
- }
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.18) : Commons.Color.popups.border
- }
-
- Item {
- width: parent.width
- height: 30
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- text: panel.telemetry.memPercent + "%"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Rectangle {
- anchors.bottom: parent.bottom
- width: parent.width
- height: 8
- radius: height / 2
- color: panel.controlActiveFillColor
-
- Rectangle {
- width: parent.width * panel.telemetry.memPercent / 100
- height: parent.height
- radius: height / 2
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- Behavior on width { NumberAnimation { duration: 300 } }
- }
- }
- }
-
- Column {
- width: parent.width
- spacing: 4
-
- MemoryStatRow {
- width: parent.width
- label: "Used"
- value: panel.telemetry.memUsedGiB.toFixed(1) + " GiB"
- detail: panel.telemetry.memUsedMiB + " MiB"
- bar: panel.bar
- }
- MemoryStatRow {
- width: parent.width
- label: "Available"
- value: (panel.telemetry.memAvailableMiB / 1024).toFixed(1) + " GiB"
- detail: panel.telemetry.memAvailableMiB + " MiB"
- bar: panel.bar
- }
- MemoryStatRow {
- width: parent.width
- label: "Total"
- value: panel.telemetry.memTotalGiB.toFixed(1) + " GiB"
- detail: panel.telemetry.memTotalMiB + " MiB"
- bar: panel.bar
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Rectangle {
- width: parent.width
- height: 28
- radius: panel.controlRadius
- color: monitorMouse.containsMouse
- ? panel.controlPrimaryHoverColor
- : panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- Behavior on color { ColorAnimation { duration: 120 } }
-
- Text {
- anchors.centerIn: parent
- text: "Open btop"
- color: panel.bar ? panel.bar.background : Commons.Color.background
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: monitorMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- panel.ownerWidget.close()
- panel.ownerWidget.openSystemMonitor()
- }
- }
- }
- }
- }
-
- component MemoryStatRow: Row {
- required property string label
- required property string value
- required property string detail
- required property var bar
-
- Text {
- id: labelText
- width: parent.width * 0.4
- text: parent.label
- color: parent.bar ? Qt.rgba(parent.bar.foreground.r, parent.bar.foreground.g,
- parent.bar.foreground.b, 0.65) : Commons.Color.foreground
- font.family: parent.bar ? parent.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- Text {
- id: valueText
- width: parent.width * 0.3
- text: parent.value
- color: parent.bar ? parent.bar.foreground : Commons.Color.foreground
- font.family: parent.bar ? parent.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- Text {
- id: detailText
- width: parent.width * 0.3
- text: parent.detail
- color: parent.bar ? Qt.rgba(parent.bar.foreground.r, parent.bar.foreground.g,
- parent.bar.foreground.b, 0.58) : Commons.Color.foreground
- font.family: parent.bar ? parent.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- }
-}
diff --git a/widgets/MemoryRing.qml b/widgets/MemoryRing.qml
deleted file mode 100644
index c3567db..0000000
--- a/widgets/MemoryRing.qml
+++ /dev/null
@@ -1,41 +0,0 @@
-import QtQuick
-import qs.Commons as Commons
-
-Canvas {
- id: root
-
- required property int percent
- required property color foreground
- required property color accent
-
- width: Commons.Style.space(16)
- height: width
-
- onPercentChanged: requestPaint()
- onForegroundChanged: requestPaint()
- onAccentChanged: requestPaint()
- Component.onCompleted: requestPaint()
-
- onPaint: {
- const context = getContext("2d")
- context.clearRect(0, 0, width, height)
- const center = width / 2
- const radius = width / 2 - 1.5
- const ratio = Math.max(0, Math.min(1, percent / 100))
- const start = -Math.PI / 2
-
- context.lineWidth = 1.8
- context.lineCap = "round"
- context.beginPath()
- context.arc(center, center, radius, 0, Math.PI * 2)
- context.strokeStyle = Qt.rgba(foreground.r, foreground.g, foreground.b, 0.2)
- context.stroke()
-
- if (ratio > 0) {
- context.beginPath()
- context.arc(center, center, radius, start, start + Math.PI * 2 * ratio)
- context.strokeStyle = accent
- context.stroke()
- }
- }
-}
diff --git a/widgets/MemoryWidget.qml b/widgets/MemoryWidget.qml
deleted file mode 100644
index d211880..0000000
--- a/widgets/MemoryWidget.qml
+++ /dev/null
@@ -1,160 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.memory"
- manageIpc: false
-
- readonly property var telemetry: bar ? bar.systemTelemetry : null
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool compact: setting("compact", false) === true
- readonly property int percent: telemetry ? telemetry.memPercent : 0
- readonly property real usedGiB: telemetry ? telemetry.memUsedGiB : 0
- readonly property real totalGiB: telemetry ? telemetry.memTotalGiB : 0
- readonly property string usedLabel: String(Math.round(usedGiB)).padStart(2, "0") + "G"
- readonly property string usageLabel: usedGiB.toFixed(1) + "/" + totalGiB.toFixed(1) + " GiB"
- property var acquiredTelemetry: null
-
- implicitWidth: bar && bar.vertical ? bar.barSize : memorySurface.implicitWidth
- implicitHeight: bar && bar.vertical ? memorySurface.implicitHeight : bar ? bar.barSize : 28
-
- function syncTelemetryOwner() {
- if (acquiredTelemetry === telemetry) return
- if (acquiredTelemetry) acquiredTelemetry.release("memory")
- acquiredTelemetry = telemetry
- if (acquiredTelemetry) acquiredTelemetry.acquire("memory")
- }
-
- function syncPanelLoader() {
- if (!opened) {
- panelLoader.source = ""
- return
- }
- panelLoader.setSource(Qt.resolvedUrl("MemoryPanel.qml"), {
- anchorItem: memorySurface,
- bar: root.bar,
- ownerWidget: root,
- telemetry: root.telemetry
- })
- }
-
- function openSystemMonitor() {
- if (!bar || typeof bar.run !== "function") return false
- bar.run("omarchy-launch-or-focus-tui btop")
- return true
- }
-
- onTelemetryChanged: syncTelemetryOwner()
- onOpenedChanged: syncPanelLoader()
- Component.onCompleted: syncTelemetryOwner()
- Component.onDestruction: if (acquiredTelemetry) acquiredTelemetry.release("memory")
-
- Item {
- id: memorySurface
-
- anchors.centerIn: parent
- implicitWidth: root.bar && root.bar.vertical
- ? root.bar.barSize
- : content.implicitWidth + 2 * root.tokens.pillPaddingX
- implicitHeight: root.bar && root.bar.vertical
- ? content.implicitHeight + Commons.Style.space(10)
- : root.tokens ? root.tokens.slotHeight : 28
- width: implicitWidth
- height: implicitHeight
-
- PillSurface {
- bar: root.bar
- anchors.fill: parent
- anchors.topMargin: Math.round((parent.height - root.tokens.pillHeight) / 2)
- anchors.bottomMargin: Math.round((parent.height - root.tokens.pillHeight) / 2)
- }
-
- Loader {
- id: content
- anchors.centerIn: parent
- sourceComponent: root.bar && root.bar.vertical ? verticalContent : horizontalContent
- }
-
- MouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar) root.bar.showTooltip(memorySurface, root.usageLabel)
- onExited: if (root.bar) root.bar.hideTooltip(memorySurface)
- onClicked: {
- if (root.bar) root.bar.hideTooltip(memorySurface)
- root.toggle()
- }
- }
- }
-
- Loader {
- id: panelLoader
- }
-
- Component {
- id: horizontalContent
-
- Row {
- spacing: root.compact ? root.tokens.compactGap : root.tokens.contentGap
-
- Text {
- visible: !root.compact
- anchors.verticalCenter: parent.verticalCenter
- text: "MEM"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.6) : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: 0.5
- renderType: Text.NativeRendering
- }
-
- MemoryRing {
- anchors.verticalCenter: parent.verticalCenter
- percent: root.percent
- foreground: root.bar ? root.bar.foreground : Commons.Color.foreground
- accent: root.bar ? root.bar.urgent : Commons.Color.accent
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: root.usedLabel
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-
- Component {
- id: verticalContent
-
- Column {
- spacing: Commons.Style.space(2)
-
- MemoryRing {
- anchors.horizontalCenter: parent.horizontalCenter
- percent: root.percent
- foreground: root.bar ? root.bar.foreground : Commons.Color.foreground
- accent: root.bar ? root.bar.urgent : Commons.Color.accent
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: root.usedLabel
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
- }
-}
diff --git a/widgets/NetworkPanel.qml b/widgets/NetworkPanel.qml
deleted file mode 100644
index 4bebb09..0000000
--- a/widgets/NetworkPanel.qml
+++ /dev/null
@@ -1,1137 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Effects
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var networkService
-
- property bool savedOnly: false
- property bool speedDetailsVisible: false
- property string expandedKey: ""
- property string passwordKey: ""
- property string passwordText: ""
- property string identityText: ""
- property var credentialDisplayNetworks: []
- property string credentialSsid: ""
- property var credentialSecurity: null
- property string credentialError: ""
- property Item credentialEditor: null
- property string pendingForgetKey: ""
- property int cursorIndex: -1
- readonly property bool wifiControlsVisible: networkService
- && networkService.wifiAvailable === true
- readonly property var displayNetworks: filteredNetworks()
- readonly property var presentedNetworks: passwordKey !== ""
- ? credentialDisplayNetworks : displayNetworks
- readonly property bool credentialEditorFocused: !!(credentialEditor
- && credentialEditor.activeFocus)
- readonly property bool panelKeyboardFocusActive: keyCatcher.activeFocus
- readonly property int savedCount: {
- let count = 0
- const rows = networkService ? networkService.networks : []
- for (let i = 0; i < rows.length; i++) {
- if (rows[i] && rows[i].known === true) count++
- }
- return count
- }
-
- owner: ownerWidget
- open: ownerWidget.opened && networkService && networkService.ready
- focusTarget: keyCatcher
- contentWidth: fittedContentWidth(Commons.Style.space(380))
- contentHeight: fittedContentHeight(contentColumn.implicitHeight,
- Commons.Style.space(680))
-
- function filteredNetworks() {
- const result = []
- if (!wifiControlsVisible) return result
- const rows = networkService ? networkService.networks : []
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i]
- if (!row) continue
- if (savedOnly ? row.known === true : row.visible !== false) result.push(row)
- }
- return result
- }
-
- function entryKey(entry) { return entry ? String(entry.entryKey || "") : "" }
-
- function signalIcon(value) {
- const signal = Number(value || 0)
- if (signal <= 0) return "\uE1DA"
- if (signal < 22) return "\uEBE4"
- if (signal < 44) return "\uEBD6"
- if (signal < 66) return "\uEBE1"
- return "\uF065"
- }
-
- function statusText() {
- if (!networkService.backendAvailable) return "NetworkManager unavailable"
- if (wifiControlsVisible && !networkService.wifiEnabled
- && networkService.kind === "disconnected")
- return "Wi-Fi disabled"
- if (networkService.kind === "wifi") return networkService.label || "Wi-Fi connected"
- if (networkService.kind === "ethernet") return ethernetAddress()
- return "Offline"
- }
-
- function ethernetAddress() {
- const info = networkService.info || ({})
- if (!info.ip) return networkService.label || "Ethernet connected"
- return String(info.ip) + (info.prefix ? "/" + info.prefix : "")
- }
-
- function statusMeta() {
- const info = networkService.info || ({})
- const fields = []
- if (networkService.kind === "wifi") fields.push("Wi-Fi")
- else if (networkService.kind === "ethernet") fields.push("Ethernet")
- if (info.iface) fields.push(String(info.iface))
- if (networkService.kind !== "ethernet" && info.ip)
- fields.push(String(info.ip) + (info.prefix ? "/" + info.prefix : ""))
- if (networkService.kind === "wifi" && networkService.signalStrength >= 0)
- fields.push(networkService.signalStrength + "% signal")
- return fields.join(" · ")
- }
-
- function frequencyText() {
- const raw = String((networkService.info || ({})).freq || "").trim()
- if (!raw) return ""
- return /[a-z]/i.test(raw) ? raw : raw + " MHz"
- }
-
- function linkSpeedText() {
- const info = networkService.info || ({})
- if (networkService.kind === "wifi") return String(info.bitrate || "").trim()
- const raw = String(info.speed || "").trim()
- if (!raw) return ""
- const speed = Number(raw)
- if (!isFinite(speed) || speed <= 0) return raw
- return speed >= 1000
- ? (speed / 1000).toFixed(speed % 1000 === 0 ? 0 : 1) + " Gbit/s"
- : speed + " Mbit/s"
- }
-
- function metadata(entry) {
- if (!entry) return ""
- const fields = [String(entry.securityLabel || "Unknown")]
- if (entry.visible !== false) fields.push("Signal " + Math.round(entry.signal || 0) + "%")
- if (entry.known) fields.push("Saved")
- if (entry.visible === false) fields.push("Not currently visible")
- return fields.join(" · ")
- }
-
- function toggleExpanded(entry) {
- const key = entryKey(entry)
- expandedKey = expandedKey === key ? "" : key
- if (expandedKey !== key || passwordKey !== key) clearPassword()
- }
-
- function clearPassword() {
- const restorePanelFocus = passwordKey !== "" && open
- passwordKey = ""
- passwordText = ""
- identityText = ""
- credentialDisplayNetworks = []
- credentialSsid = ""
- credentialSecurity = null
- credentialError = ""
- credentialEditor = null
- if (restorePanelFocus) requestPanelKeyboardFocus(keyCatcher)
- }
-
- function openPassword(entry) {
- credentialDisplayNetworks = displayNetworks.slice()
- credentialSsid = String(entry && entry.ssid || "")
- credentialSecurity = entry ? entry.security : null
- credentialError = ""
- expandedKey = entryKey(entry)
- passwordKey = expandedKey
- passwordText = ""
- identityText = ""
- }
-
- function currentCredentialRow() {
- let result = null
- let count = 0
- const rows = displayNetworks
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i]
- if (!row || row.visible === false
- || String(row.ssid || "") !== credentialSsid
- || row.security !== credentialSecurity) continue
- result = row
- count++
- }
- return count === 1 ? result : null
- }
-
- function evaluateCredentialCompletion() {
- if (passwordKey === "") return
- const current = currentCredentialRow()
- if (current && current.connected === true) clearPassword()
- }
-
- function requestPanelKeyboardFocus(target) {
- if (!open || !target) return
- if (typeof requestKeyboardFocus === "function") {
- requestKeyboardFocus(target)
- return
- }
- Qt.callLater(function() {
- if (panel.open && target) target.forceActiveFocus()
- })
- }
-
- function registerCredentialEditor(editor) {
- if (!editor) return
- credentialEditor = editor
- requestPanelKeyboardFocus(editor)
- }
-
- function credentialEditorFocusChanged(editor, active) {
- if (!editor) return
- if (active) {
- credentialEditor = editor
- return
- }
- Qt.callLater(function() {
- if (panel.open && panel.passwordKey !== ""
- && panel.credentialEditor === editor && editor.visible
- && !editor.activeFocus && keyCatcher.activeFocus)
- panel.requestPanelKeyboardFocus(editor)
- })
- }
-
- function openNetworkSettings() {
- ownerWidget.close()
- if (bar && typeof bar.run === "function")
- bar.run("omarchy-launch-or-focus-tui nmtui")
- }
-
- function needsCredentials(entry) {
- return !!entry && !entry.known
- && (entry.securityKind === "psk"
- || entry.securityKind === "enterprise")
- }
-
- function needsNetworkSettings(entry) {
- return !!entry && !entry.known && !needsCredentials(entry)
- && entry.securityKind !== "open"
- }
-
- function runPrimary(entry) {
- if (!entry || networkService.busy) return
- if (entry.connected) {
- networkService.disconnect(entry)
- return
- }
- if (needsCredentials(entry)) {
- openPassword(entry)
- return
- }
- if (needsNetworkSettings(entry)) {
- openNetworkSettings()
- return
- }
- networkService.connect(entry)
- }
-
- function submitPassword(entry) {
- if (!entry || !passwordText || networkService.busy) return false
- let accepted = false
- if (entry.securityKind === "enterprise") {
- if (!identityText) return false
- accepted = networkService.connectEnterprise(
- entry, identityText, passwordText)
- } else {
- accepted = networkService.connectWithPassphrase(entry, passwordText)
- }
- if (!accepted)
- credentialError = "Network changed. Select it again."
- return accepted
- }
-
- function requestForget(entry) {
- const key = entryKey(entry)
- if (!key || !entry || !entry.known || entry.connected) return
- if (pendingForgetKey === key) {
- pendingForgetKey = ""
- forgetTimer.stop()
- networkService.forget(entry)
- return
- }
- pendingForgetKey = key
- forgetTimer.restart()
- }
-
- function moveCursor(delta) {
- if (displayNetworks.length === 0) {
- cursorIndex = -1
- return
- }
- cursorIndex = Math.max(0, Math.min(displayNetworks.length - 1,
- cursorIndex < 0 ? (delta > 0 ? 0 : displayNetworks.length - 1)
- : cursorIndex + delta))
- }
-
- function activateCursor() {
- if (cursorIndex < 0 || cursorIndex >= displayNetworks.length) return
- toggleExpanded(displayNetworks[cursorIndex])
- }
-
- onSavedOnlyChanged: {
- expandedKey = ""
- clearPassword()
- cursorIndex = displayNetworks.length > 0 ? 0 : -1
- }
-
- onDisplayNetworksChanged: {
- if (cursorIndex >= displayNetworks.length) cursorIndex = displayNetworks.length - 1
- if (displayNetworks.length === 0) cursorIndex = -1
- if (passwordKey !== "")
- Qt.callLater(function() { panel.evaluateCredentialCompletion() })
- }
-
- onOpenChanged: {
- if (open) {
- speedDetailsVisible = networkService.speedTestRunning
- cursorIndex = displayNetworks.length > 0 ? 0 : -1
- } else {
- savedOnly = false
- expandedKey = ""
- pendingForgetKey = ""
- speedDetailsVisible = false
- clearPassword()
- forgetTimer.stop()
- }
- }
-
- property Connections networkConnections: Connections {
- target: panel.networkService
-
- function onActionKindChanged() {
- if (!panel.networkService || panel.networkService.actionKind !== ""
- || panel.passwordKey === "") return
- Qt.callLater(function() { panel.evaluateCredentialCompletion() })
- }
- }
-
- property Timer forgetTimer: Timer {
- id: forgetTimer
- interval: 5000
- onTriggered: panel.pendingForgetKey = ""
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- blocked: panel.passwordKey !== ""
- onActiveFocusChanged: {
- if (activeFocus && panel.passwordKey !== ""
- && panel.credentialEditor) Qt.callLater(function() {
- if (keyCatcher.activeFocus && panel.open
- && panel.passwordKey !== "" && panel.credentialEditor)
- panel.requestPanelKeyboardFocus(panel.credentialEditor)
- })
- }
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
- onMoveRequested: function(_dx, dy) {
- if (dy !== 0) panel.moveCursor(dy)
- }
- onActivateRequested: panel.activateCursor()
-
- Flickable {
- id: scroller
- anchors.fill: parent
- clip: true
- contentWidth: width
- contentHeight: contentColumn.implicitHeight
- boundsBehavior: Flickable.StopAtBounds
- interactive: contentHeight > height
-
- Column {
- id: contentColumn
- width: scroller.width
- spacing: Commons.Style.space(8)
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(4)
-
- Text {
- width: parent.width - headerActions.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- text: "Network"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.heading
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Row {
- id: headerActions
- spacing: Commons.Style.space(2)
-
- IconAction {
- visible: panel.wifiControlsVisible
- icon: panel.networkService.scanning ? "sync" : "refresh"
- tooltip: "Rescan Wi-Fi"
- enabled: panel.networkService.wifiAvailable
- && panel.networkService.wifiEnabled
- onClicked: panel.networkService.refresh(true)
- }
-
- IconAction {
- icon: "close"
- tooltip: "Close"
- onClicked: panel.ownerWidget.close()
- }
- }
- }
-
- Ui.PanelSeparator { width: parent.width }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(3)
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(8)
-
- Item {
- visible: panel.networkService.kind === "ethernet"
- anchors.verticalCenter: parent.verticalCenter
- width: 20
- height: 20
-
- Image {
- id: ethernetIconSource
- anchors.fill: parent
- visible: false
- source: Qt.resolvedUrl("lan.svg")
- sourceSize: Qt.size(20, 20)
- fillMode: Image.PreserveAspectFit
- smooth: false
- mipmap: false
- }
-
- MultiEffect {
- anchors.fill: parent
- source: ethernetIconSource
- colorization: 1
- colorizationColor: panel.bar
- ? panel.bar.urgent : Commons.Color.accent
- }
- }
-
- IconText {
- visible: panel.networkService.kind !== "ethernet"
- anchors.verticalCenter: parent.verticalCenter
- text: panel.networkService.kind === "wifi"
- ? panel.signalIcon(panel.networkService.signalStrength)
- : "\uE2C1"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.pixelSize: Commons.Style.font.heading
- font.hintingPreference: Font.PreferFullHinting
- renderType: Text.NativeRendering
- fill: 1
- }
-
- Text {
- width: parent.width - x
- anchors.verticalCenter: parent.verticalCenter
- text: panel.statusText()
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: Font.Medium
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
-
- Text {
- width: parent.width
- visible: text !== ""
- text: panel.statusMeta()
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
-
- Grid {
- width: parent.width
- columns: 2
- columnSpacing: Commons.Style.space(6)
- rowSpacing: Commons.Style.space(4)
-
- DetailCell {
- width: (contentColumn.width - parent.columnSpacing) / 2
- label: "PING"
- value: panel.networkService.internetPingLatency >= 0
- ? panel.networkService.formatPing(panel.networkService.internetPingLatency)
- : "--"
- }
- DetailCell {
- width: (contentColumn.width - parent.columnSpacing) / 2
- label: "GATEWAY"
- value: String(panel.networkService.info.gateway || "--")
- }
- DetailCell {
- width: (contentColumn.width - parent.columnSpacing) / 2
- label: "DOWNLOAD"
- value: panel.networkService.formatRate(panel.networkService.downloadRate)
- }
- DetailCell {
- width: (contentColumn.width - parent.columnSpacing) / 2
- label: "UPLOAD"
- value: panel.networkService.formatRate(panel.networkService.uploadRate)
- }
- DetailCell {
- width: (contentColumn.width - parent.columnSpacing) / 2
- visible: panel.frequencyText() !== ""
- label: "FREQUENCY"
- value: panel.frequencyText()
- }
- DetailCell {
- width: (contentColumn.width - parent.columnSpacing) / 2
- visible: panel.linkSpeedText() !== ""
- label: "LINK SPEED"
- value: panel.linkSpeedText()
- }
- }
-
- Ui.PanelSeparator { width: parent.width }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(6)
-
- Row {
- width: parent.width
-
- SectionLabel {
- width: parent.width - speedButton.width
- anchors.verticalCenter: parent.verticalCenter
- text: "SPEED TEST"
- }
-
- PanelButton {
- id: speedButton
- width: Commons.Style.space(86)
- label: panel.networkService.speedTestRunning ? "Running..." : "Run test"
- enabled: !panel.networkService.speedTestRunning
- && panel.networkService.kind !== "disconnected"
- onClicked: {
- panel.speedDetailsVisible = true
- panel.networkService.runSpeedTest()
- }
- }
- }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(6)
- visible: panel.speedDetailsVisible
-
- DetailCell {
- width: (parent.width - 2 * parent.spacing) / 3
- label: "PING"
- value: panel.networkService.speedTestRunning
- && panel.networkService.speedTestPhase === "down" ? "Testing..."
- : panel.networkService.formatPing(panel.networkService.internetPingLatency)
- }
- DetailCell {
- width: (parent.width - 2 * parent.spacing) / 3
- label: "DOWNLOAD"
- value: panel.networkService.speedTestRunning
- && panel.networkService.speedTestPhase === "down" ? "Testing..."
- : panel.networkService.formatSpeed(panel.networkService.speedTestDownloadMbps)
- }
- DetailCell {
- width: (parent.width - 2 * parent.spacing) / 3
- label: "UPLOAD"
- value: panel.networkService.speedTestRunning
- && panel.networkService.speedTestPhase === "up" ? "Testing..."
- : panel.networkService.formatSpeed(panel.networkService.speedTestUploadMbps)
- }
- }
-
- Text {
- width: parent.width
- visible: panel.speedDetailsVisible
- && panel.networkService.speedTestError !== ""
- text: panel.networkService.speedTestError
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- wrapMode: Text.Wrap
- }
- }
-
- Ui.PanelSeparator { width: parent.width }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(5)
-
- SectionLabel { text: "DNS" }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(4)
-
- Repeater {
- model: panel.networkService.dnsProviders
-
- PanelButton {
- required property var modelData
- width: (contentColumn.width - 3 * Commons.Style.space(4)) / 4
- label: String(modelData)
- current: panel.networkService.dnsProvider === String(modelData)
- onClicked: {
- panel.networkService.setDns(String(modelData))
- if (String(modelData) === "Custom") panel.ownerWidget.close()
- }
- }
- }
- }
- }
-
- Ui.PanelSeparator {
- width: parent.width
- visible: panel.wifiControlsVisible
- }
-
- Row {
- width: parent.width
- height: Commons.Style.space(24)
- visible: panel.wifiControlsVisible
-
- Text {
- width: parent.width - wifiToggle.width
- anchors.verticalCenter: parent.verticalCenter
- text: "Wi-Fi"
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- renderType: Text.NativeRendering
- }
-
- PanelButton {
- id: wifiToggle
- width: Commons.Style.space(50)
- height: Commons.Style.space(22)
- label: panel.networkService.wifiEnabled ? "ON" : "OFF"
- current: panel.networkService.wifiEnabled
- enabled: panel.networkService.backendAvailable
- onClicked: panel.networkService.toggleWifi()
- }
- }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(4)
- visible: panel.wifiControlsVisible
-
- PanelButton {
- width: (parent.width - parent.spacing) / 2
- label: "Available"
- current: !panel.savedOnly
- onClicked: {
- panel.savedOnly = false
- panel.networkService.refresh(true)
- }
- }
-
- PanelButton {
- width: (parent.width - parent.spacing) / 2
- label: "Saved" + (panel.savedCount > 0 ? " (" + panel.savedCount + ")" : "")
- current: panel.savedOnly
- onClicked: panel.savedOnly = true
- }
- }
-
- Row {
- width: parent.width
- visible: panel.wifiControlsVisible
-
- SectionLabel {
- width: parent.width - scanState.width
- text: panel.savedOnly ? "SAVED NETWORKS" : "AVAILABLE NETWORKS"
- }
-
- Text {
- id: scanState
- text: panel.networkService.scanning ? "Scanning..." : ""
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- }
- }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(3)
- visible: panel.wifiControlsVisible
-
- Repeater {
- // Scanner/signal updates replace the service's JS array. Keep the
- // delegate containing an active credential editor alive until the
- // editor closes, while actions still revalidate against live rows.
- model: panel.presentedNetworks
-
- Ui.CursorSurface {
- id: networkRow
- required property var modelData
- required property int index
- readonly property string key: panel.entryKey(modelData)
- readonly property bool expanded: panel.expandedKey === key
- readonly property bool passwordOpen: panel.passwordKey === key
- readonly property bool enterpriseCredentials:
- modelData.securityKind === "enterprise"
- readonly property bool actionRunning:
- panel.networkService.actionSsid === String(modelData.ssid || "")
- || (modelData.profileUuid && panel.networkService.busy)
- width: contentColumn.width
- height: rowColumn.implicitHeight + Commons.Style.space(12)
- radius: panel.controlRadius
- hasCursor: panel.cursorIndex === index
- current: modelData.connected === true
- bordered: expanded
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- Column {
- id: rowColumn
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: Commons.Style.space(8)
- anchors.rightMargin: Commons.Style.space(8)
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(5)
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(7)
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: networkRow.modelData.visible === false ? "bookmark"
- : panel.signalIcon(networkRow.modelData.signal)
- color: panel.bar ? (networkRow.modelData.connected
- ? panel.bar.urgent : panel.bar.foreground) : Commons.Color.accent
- opacity: networkRow.modelData.visible === false ? 0.58 : 1
- font.pixelSize: Commons.Style.font.body
- fill: 1
- }
-
- Text {
- width: parent.width - x - expandIcon.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- text: String(networkRow.modelData.ssid || "Hidden network")
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: networkRow.modelData.connected ? Font.Medium : Font.Normal
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- IconText {
- id: expandIcon
- anchors.verticalCenter: parent.verticalCenter
- text: networkRow.expanded ? "expand_less" : "expand_more"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- opacity: 0.55
- font.pixelSize: Commons.Style.font.body
- fill: 1
- }
- }
-
- Text {
- width: parent.width
- visible: networkRow.expanded
- text: panel.metadata(networkRow.modelData)
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- wrapMode: Text.Wrap
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- visible: networkRow.expanded
- && panel.networkService.failureSsid === networkRow.modelData.ssid
- && panel.networkService.failureReason !== ""
- text: panel.networkService.failureReason
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- wrapMode: Text.Wrap
- }
-
- Text {
- width: parent.width
- visible: networkRow.expanded
- && panel.networkService.profileError !== ""
- && networkRow.modelData.profileUuid !== ""
- text: panel.networkService.profileError
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- wrapMode: Text.Wrap
- }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(5)
- visible: networkRow.expanded && !networkRow.passwordOpen
-
- PanelButton {
- width: parent.width - (forgetButton.visible
- ? forgetButton.width + parent.spacing : 0)
- label: networkRow.actionRunning
- ? (networkRow.modelData.connected ? "Disconnecting..." : "Connecting...")
- : networkRow.modelData.connected ? "Disconnect"
- : panel.needsNetworkSettings(networkRow.modelData)
- ? "Open network settings" : "Connect"
- enabled: !panel.networkService.busy
- onClicked: panel.runPrimary(networkRow.modelData)
- }
-
- PanelButton {
- id: forgetButton
- visible: networkRow.modelData.known === true
- && networkRow.modelData.connected !== true
- width: Commons.Style.space(92)
- label: panel.pendingForgetKey === networkRow.key
- ? "Confirm" : "Forget"
- urgent: true
- onClicked: panel.requestForget(networkRow.modelData)
- }
- }
-
- Column {
- width: parent.width
- spacing: Commons.Style.space(5)
- visible: networkRow.passwordOpen
-
- TextField {
- id: identityField
- width: parent.width
- height: Commons.Style.space(30)
- visible: networkRow.passwordOpen
- && networkRow.enterpriseCredentials
- text: panel.identityText
- placeholderText: "Identity (user@domain)"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- placeholderTextColor: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.42)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- selectByMouse: true
- onTextChanged: panel.identityText = text
- onAccepted: passwordField.forceActiveFocus()
- Keys.onEscapePressed: panel.clearPassword()
- onActiveFocusChanged:
- panel.credentialEditorFocusChanged(identityField,
- activeFocus)
- onVisibleChanged: {
- if (visible) panel.registerCredentialEditor(identityField)
- else if (panel.credentialEditor === identityField)
- panel.credentialEditor = null
- }
- Component.onCompleted: if (visible)
- panel.registerCredentialEditor(identityField)
- Component.onDestruction: if (panel.credentialEditor === identityField)
- panel.credentialEditor = null
- background: Rectangle {
- radius: panel.controlRadius
- color: identityField.activeFocus
- ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: identityField.activeFocus
- ? panel.controlHoverBorderColor
- : panel.controlBorderColor
- }
- }
-
- Text {
- width: parent.width
- visible: panel.credentialError !== ""
- text: panel.credentialError
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar
- ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- wrapMode: Text.Wrap
- }
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(5)
-
- TextField {
- id: passwordField
- width: parent.width - connectPassword.width - cancelPassword.width
- - 2 * parent.spacing
- height: Commons.Style.space(30)
- visible: networkRow.passwordOpen
- text: panel.passwordText
- placeholderText: networkRow.enterpriseCredentials
- ? "Enterprise password" : "Wi-Fi password"
- echoMode: TextInput.Password
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- placeholderTextColor: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.42)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- selectByMouse: true
- onTextChanged: panel.passwordText = text
- onAccepted: panel.submitPassword(networkRow.modelData)
- Keys.onEscapePressed: panel.clearPassword()
- onActiveFocusChanged:
- panel.credentialEditorFocusChanged(passwordField,
- activeFocus)
- onVisibleChanged: {
- if (visible && !networkRow.enterpriseCredentials)
- panel.registerCredentialEditor(passwordField)
- else if (!visible
- && panel.credentialEditor === passwordField)
- panel.credentialEditor = null
- }
- Component.onCompleted: if (visible
- && !networkRow.enterpriseCredentials)
- panel.registerCredentialEditor(passwordField)
- Component.onDestruction: if (panel.credentialEditor === passwordField)
- panel.credentialEditor = null
- background: Rectangle {
- radius: panel.controlRadius
- color: passwordField.activeFocus
- ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: passwordField.activeFocus
- ? panel.controlHoverBorderColor
- : panel.controlBorderColor
- }
- }
-
- PanelButton {
- id: connectPassword
- width: Commons.Style.space(66)
- label: panel.networkService.busy ? "Wait" : "Connect"
- enabled: panel.passwordText !== ""
- && (!networkRow.enterpriseCredentials
- || panel.identityText !== "")
- && panel.credentialError === ""
- && !panel.networkService.busy
- onClicked: panel.submitPassword(networkRow.modelData)
- }
-
- PanelButton {
- id: cancelPassword
- width: Commons.Style.space(54)
- label: "Cancel"
- onClicked: panel.clearPassword()
- }
- }
- }
- }
-
- MouseArea {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- height: Commons.Style.space(34)
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: panel.cursorIndex = networkRow.index
- onClicked: panel.toggleExpanded(networkRow.modelData)
- }
- }
- }
-
- Text {
- width: parent.width
- visible: panel.wifiControlsVisible
- && panel.displayNetworks.length === 0
- text: panel.savedOnly
- ? (panel.networkService.profilesLoaded
- ? "No saved networks" : "Loading saved networks...")
- : panel.networkService.wifiEnabled
- ? "No networks found" : "Wi-Fi is disabled"
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.58)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- horizontalAlignment: Text.AlignHCenter
- topPadding: Commons.Style.space(10)
- bottomPadding: Commons.Style.space(10)
- }
- }
-
- PanelButton {
- width: parent.width
- label: "Network settings"
- primary: true
- onClicked: panel.openNetworkSettings()
- }
- }
- }
- }
-
- component SectionLabel: Text {
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.52)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 1
- renderType: Text.NativeRendering
- }
-
- component DetailCell: Column {
- property string label: ""
- property string value: ""
- spacing: Commons.Style.space(1)
-
- Text {
- width: parent.width
- text: parent.label
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r,
- panel.bar.foreground.g, panel.bar.foreground.b, 0.45)
- : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: 0.6
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- text: parent.value
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
-
- component PanelButton: Rectangle {
- id: button
- property string label: ""
- property bool current: false
- property bool primary: false
- property bool urgent: false
- signal clicked()
- readonly property bool hovered: buttonMouse.containsMouse && enabled
- readonly property color actionColor: panel.bar
- ? panel.bar.urgent : Commons.Color.accent
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- opacity: enabled ? 1 : 0.42
- color: primary
- ? (hovered ? panel.controlPrimaryHoverColor : actionColor)
- : current ? panel.controlActiveFillColor
- : hovered ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: primary ? 0 : panel.controlBorderWidth
- border.color: current || hovered
- ? actionColor : panel.controlBorderColor
-
- Behavior on color { ColorAnimation { duration: 100 } }
- Behavior on border.color { ColorAnimation { duration: 100 } }
-
- Text {
- anchors.centerIn: parent
- width: parent.width - Commons.Style.space(8)
- text: button.label
- color: !button.enabled
- ? Qt.rgba(panel.controlForeground.r, panel.controlForeground.g,
- panel.controlForeground.b, 0.35)
- : button.primary ? (panel.shibumiTokens
- ? panel.shibumiTokens.paper : Commons.Color.background)
- : button.current || button.hovered || button.urgent && button.hovered
- ? button.actionColor : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- font.weight: button.current ? Font.Medium : Font.Normal
- horizontalAlignment: Text.AlignHCenter
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: buttonMouse
- anchors.fill: parent
- hoverEnabled: true
- enabled: button.enabled
- cursorShape: button.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: button.clicked()
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- property bool accentAction: false
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: !action.enabled
- ? Qt.rgba(action.foreground.r, action.foreground.g, action.foreground.b, 0.3)
- : action.accentAction && panel.bar ? panel.bar.urgent : action.foreground
- font.pixelSize: Commons.Style.font.body
- fill: action.accentAction ? 1 : 0
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- enabled: action.enabled
- cursorShape: action.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-}
diff --git a/widgets/NetworkWidget.qml b/widgets/NetworkWidget.qml
deleted file mode 100644
index 863d5ee..0000000
--- a/widgets/NetworkWidget.qml
+++ /dev/null
@@ -1,214 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.network"
- manageIpc: false
- property url popupSource: Qt.resolvedUrl("NetworkPanel.qml")
- property var networkServiceOverride: null
- property var sessionService: null
-
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool compact: setting("compact", false) === true
- readonly property var networkService: networkServiceOverride
- || (bar ? bar.networkService : null)
- readonly property bool networkReady: networkService && networkService.ready
- readonly property bool backendAvailable: networkReady
- && networkService.backendAvailable
- readonly property string mode: !networkReady ? "none"
- : networkService.kind === "wifi" ? "wifi"
- : networkService.kind === "ethernet" ? "ethernet" : "none"
- readonly property string label: networkReady ? networkService.label : ""
- readonly property int signal: networkReady ? networkService.signalStrength : 0
- readonly property string wifiIcon: signal <= 0 ? "signal_wifi_off"
- : signal < 22 ? "network_wifi_1_bar"
- : signal < 44 ? "network_wifi_2_bar"
- : signal < 66 ? "network_wifi_3_bar" : "signal_wifi_4_bar"
- readonly property string stateIcon: mode === "wifi" ? wifiIcon
- : mode === "ethernet" ? "lan" : "signal_wifi_off"
- readonly property string tooltipText: !networkReady ? "Network unavailable"
- : mode === "wifi" ? (label || "Wi-Fi") + " · " + signal + "%"
- : mode === "ethernet" ? "Ethernet · " + (label || "connected")
- : backendAvailable ? "Offline" : "NetworkManager unavailable"
- readonly property var panelItem: popupLoader.item
- readonly property bool panelLoaded: panelItem !== null
- readonly property var interactionTarget: actionButton
-
- visible: networkReady
- implicitWidth: visible ? (bar && bar.vertical ? bar.barSize : surface.implicitWidth) : 0
- implicitHeight: visible
- ? (bar && bar.vertical ? surface.implicitHeight : bar ? bar.barSize : 28) : 0
-
- function childPanelWidget(pluginId) {
- const id = String(pluginId || "")
- return id === moduleName || id === "omarchy.network" ? root : null
- }
-
- function ownsPanelWidget(owner) { return owner === root }
-
- function releaseSession() {
- if (sessionService && typeof sessionService.endSession === "function")
- sessionService.endSession(root)
- sessionService = null
- }
-
- function syncPanelLoader() {
- popupLoader.source = ""
- if (!opened || !networkReady || !String(popupSource)) {
- releaseSession()
- return
- }
- if (sessionService !== networkService) {
- releaseSession()
- sessionService = networkService
- sessionService.beginSession(root)
- }
- popupLoader.setSource(popupSource, {
- anchorItem: surface,
- bar: root.bar,
- ownerWidget: root,
- networkService: networkService
- })
- }
-
- function openAndScan() {
- const alreadyOpen = opened
- open()
- return networkService && (alreadyOpen ? networkService.refresh(true) : true)
- }
-
- onOpenedChanged: syncPanelLoader()
- onNetworkReadyChanged: syncPanelLoader()
- onPopupSourceChanged: syncPanelLoader()
- Component.onDestruction: {
- close()
- releaseSession()
- }
-
- Loader { id: popupLoader }
-
- Item {
- id: surface
- anchors.centerIn: parent
- implicitWidth: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? root.bar.barSize
- : content.implicitWidth + 2 * root.tokens.pillPaddingX
- implicitHeight: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? content.implicitHeight + Commons.Style.space(10)
- : root.tokens.slotHeight
- width: implicitWidth
- height: implicitHeight
-
- Loader {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- active: root.bar !== null && root.tokens !== null
- sourceComponent: Component {
- PillSurface { anchors.fill: parent; bar: root.bar }
- }
- }
-
- Loader {
- id: content
- anchors.centerIn: parent
- sourceComponent: !root.bar || !root.tokens ? null
- : root.bar.vertical ? verticalContent
- : root.compact ? compactContent : fullContent
- }
-
- Ui.WidgetButton {
- id: actionButton
- anchors.fill: parent
- bar: root.networkReady ? root.bar : null
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- fixedWidth: surface.width
- fixedHeight: surface.height
- tooltipText: root.tooltipText
- onPressed: function(button) {
- if (button === Qt.RightButton) root.openAndScan()
- else root.toggle()
- }
- }
- }
-
- Component {
- id: fullContent
-
- Row {
- spacing: root.tokens.contentGap
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: "NET"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, root.mode === "none" ? 0.38 : 0.6)
- : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: 0.5
- renderType: Text.NativeRendering
- }
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: root.stateIcon
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- opacity: root.mode === "none" ? 0.58 : 1
- font.pixelSize: root.mode === "none" ? 15 : 14
- font.weight: Font.Medium
- fill: 1
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: root.mode !== "none"
- text: root.label
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: root.mode === "wifi" ? 1 : 0
- elide: Text.ElideRight
- maximumLineCount: 1
- width: Math.min(implicitWidth, Commons.Style.space(128))
- renderType: Text.NativeRendering
- }
- }
- }
-
- Component {
- id: compactContent
-
- IconText {
- text: root.stateIcon
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- opacity: root.mode === "none" ? 0.58 : 1
- font.pixelSize: root.mode === "none" ? 15 : root.compact ? 15 : 14
- font.weight: Font.Medium
- fill: 1
- }
- }
-
- Component {
- id: verticalContent
-
- IconText {
- text: root.stateIcon
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- opacity: root.mode === "none" ? 0.58 : 1
- font.pixelSize: root.mode === "none" ? 15 : 14
- font.weight: Font.Medium
- fill: 1
- }
- }
-}
diff --git a/widgets/NotificationPanel.qml b/widgets/NotificationPanel.qml
deleted file mode 100644
index e7412a4..0000000
--- a/widgets/NotificationPanel.qml
+++ /dev/null
@@ -1,396 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var notificationService
- readonly property int pendingCount: notificationService
- && notificationService.pendingModel
- ? notificationService.pendingModel.count : 0
- readonly property int recentCount: notificationService
- && notificationService.pastModel
- ? notificationService.pastModel.count : 0
- readonly property int activeCount: pendingCount + recentCount
- readonly property var activeRows: {
- const rows = []
- function append(model, bucket) {
- if (!model) return
- for (let index = 0; index < model.count; index++) {
- const entry = model.get(index)
- if (!entry) continue
- rows.push({
- bucket: bucket,
- sourceIndex: index,
- app: String(entry.app || ""),
- appIcon: String(entry.appIcon || ""),
- summary: String(entry.summary || ""),
- body: String(entry.body || "")
- })
- }
- }
- append(notificationService ? notificationService.pendingModel : null,
- "pending")
- append(notificationService ? notificationService.pastModel : null,
- "past")
- return rows
- }
-
- owner: ownerWidget
- open: ownerWidget.notificationPanelOpen && notificationService !== null
- focusTarget: keyCatcher
- gap: 8
- padding: 12
- contentWidth: fittedContentWidth(320)
- contentHeight: fittedContentHeight(contentColumn.implicitHeight,
- 540)
-
- function closePanel() {
- ownerWidget.closeNotificationPanel()
- }
-
- function setDnd(value) {
- if (notificationService
- && typeof notificationService.setDoNotDisturb === "function")
- notificationService.setDoNotDisturb(value === true)
- }
-
- function dismiss(bucket, index) {
- if (!notificationService) return
- if (bucket === "past"
- && typeof notificationService.dismissPast === "function")
- notificationService.dismissPast(index)
- else if (bucket === "pending"
- && typeof notificationService.dismissPending === "function")
- notificationService.dismissPending(index)
- }
-
- function clearActive() {
- if (!notificationService) return
- if (typeof notificationService.clearPending === "function")
- notificationService.clearPending()
- if (typeof notificationService.clearPast === "function")
- notificationService.clearPast()
- }
-
- function openNotification(bucket, index) {
- if (!notificationService) {
- closePanel()
- return
- }
- const model = bucket === "past" ? notificationService.pastModel
- : notificationService.pendingModel
- if (!model || index < 0 || index >= model.count) {
- closePanel()
- return
- }
- const entry = model.get(index)
- if (entry && typeof notificationService.focusApp === "function")
- notificationService.focusApp(entry)
- closePanel()
- }
-
- function sanitizedBody(body, app, appIcon) {
- let text = String(body || "").replace(/
]*>/gi, "")
- const source = (String(app || "") + "\n"
- + String(appIcon || "")).toLowerCase()
- const chromium = source.indexOf("chrom") >= 0
- || source.indexOf("brave") >= 0
- || source.indexOf("vivaldi") >= 0
- || source.indexOf("microsoft-edge") >= 0
- || source.indexOf("opera") >= 0
- if (!chromium) return text
- return text
- .replace(/^\s*]*>\s*(?:https?:\/\/|www\.)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d+)?(?:\/[^<\s]*)?\s*<\/a>\s*/i, "")
- .replace(/^\s*(?:https?:\/\/|www\.)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d+)?(?:\/\S*)?\s+/i, "")
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.closePanel()
- onTabRequested: function(direction) {
- if (panel.ownerWidget
- && typeof panel.ownerWidget.switchPanel === "function")
- panel.ownerWidget.switchPanel(direction)
- }
-
- Column {
- id: contentColumn
- width: parent.width
- spacing: 8
-
- Item {
- width: parent.width
- height: 24
-
- Text {
- anchors.left: parent.left
- anchors.right: panel.shellStyle === "shibumi"
- ? closeText.left : closeAction.left
- anchors.rightMargin: 8
- anchors.verticalCenter: parent.verticalCenter
- text: panel.activeCount > 0
- ? "Notifications · " + panel.activeCount : "Notifications"
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 13
- font.letterSpacing: 2
- font.weight: Font.Medium
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- id: closeText
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- visible: panel.shellStyle === "shibumi"
- text: "\u2715"
- color: closeMouse.containsMouse
- ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
- Behavior on color { ColorAnimation { duration: 120 } }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.closePanel()
- }
- }
-
- IconAction {
- id: closeAction
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- visible: panel.shellStyle !== "shibumi"
- icon: "close"
- tooltip: "Close"
- onClicked: panel.closePanel()
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Item {
- id: listViewport
- width: parent.width
- height: notificationList.count > 0
- ? Math.min(notificationList.contentHeight, 420)
- : emptyLabel.implicitHeight
-
- ListView {
- id: notificationList
- anchors.fill: parent
- visible: count > 0
- clip: true
- spacing: 6
- boundsBehavior: Flickable.StopAtBounds
- model: panel.activeRows
-
- delegate: Rectangle {
- id: notificationRow
- required property int index
- required property string bucket
- required property int sourceIndex
- required property string app
- required property string appIcon
- required property string summary
- required property string body
-
- readonly property string cleanBody: panel.sanitizedBody(
- body, app, appIcon)
-
- width: ListView.view ? ListView.view.width : 0
- height: rowContent.implicitHeight + 16
- radius: panel.controlRadius
- color: rowHover.containsMouse
- ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: rowHover.containsMouse
- ? panel.controlHoverBorderColor : panel.controlBorderColor
-
- MouseArea {
- id: rowHover
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.openNotification(notificationRow.bucket,
- notificationRow.sourceIndex)
- }
-
- Column {
- id: rowContent
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.margins: 8
- anchors.rightMargin: 26
- spacing: 3
-
- Text {
- width: parent.width
- text: notificationRow.app || "App"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 10
- font.letterSpacing: 0.5
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- visible: text.length > 0
- text: notificationRow.summary
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width
- visible: text.length > 0
- text: notificationRow.cleanBody
- color: Commons.Util.alpha(panel.controlForeground, 0.6)
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 10
- wrapMode: Text.WordWrap
- maximumLineCount: 2
- elide: Text.ElideRight
- textFormat: Text.PlainText
- renderType: Text.NativeRendering
- }
- }
-
- Item {
- width: 18
- height: 18
- anchors.top: parent.top
- anchors.right: parent.right
- anchors.margins: 4
- z: 2
-
- Text {
- anchors.centerIn: parent
- text: "✕"
- color: dismissMouse.containsMouse
- ? panel.controlAccent
- : Commons.Util.alpha(panel.controlForeground, 0.45)
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 10
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: dismissMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.dismiss(notificationRow.bucket,
- notificationRow.sourceIndex)
- }
- }
- }
- }
-
- Text {
- id: emptyLabel
- anchors.centerIn: parent
- visible: notificationList.count === 0
- text: "No notifications"
- color: Commons.Util.alpha(panel.controlForeground, 0.3)
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- }
-
- Rectangle {
- width: parent.width
- height: 28
- visible: panel.activeCount > 0
- radius: panel.controlRadius
- color: clearMouse.containsMouse
- ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: clearMouse.containsMouse
- ? panel.controlHoverBorderColor : panel.controlBorderColor
-
- Text {
- anchors.centerIn: parent
- text: "Clear all"
- color: clearMouse.containsMouse
- ? panel.controlAccent : panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: clearMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.clearActive()
- }
- }
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-}
diff --git a/widgets/NotificationStatusView.qml b/widgets/NotificationStatusView.qml
deleted file mode 100644
index 10c4516..0000000
--- a/widgets/NotificationStatusView.qml
+++ /dev/null
@@ -1,103 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-
-Item {
- id: root
-
- required property var bar
- property real slotWidth: Commons.Style.space(26)
- readonly property real iconHorizontalOffset: Commons.Style.space(1)
- property var notificationService: null
- readonly property int pendingCount: notificationService
- && notificationService.pendingModel
- ? Math.max(0, Number(notificationService.pendingModel.count) || 0) : 0
- readonly property int recentCount: notificationService
- && notificationService.pastModel
- ? Math.max(0, Number(notificationService.pastModel.count) || 0) : 0
- readonly property int notificationCount: pendingCount + recentCount
- readonly property bool presented: notificationService !== null
- signal toggleRequested()
- signal dndRequested()
- property bool registered: false
-
- visible: presented
- implicitWidth: presented ? slotWidth : 0
- implicitHeight: bar ? bar.barSize : Commons.Style.space(35)
- width: implicitWidth
- height: implicitHeight
-
- function syncRegistration() {
- if (!bar) return
- if (visible && !registered) {
- bar.registerClickTarget(root)
- registered = true
- } else if (!visible && registered) {
- bar.unregisterClickTarget(root)
- registered = false
- }
- }
-
- onVisibleChanged: syncRegistration()
- Component.onCompleted: syncRegistration()
- Component.onDestruction: if (bar && registered) bar.unregisterClickTarget(root)
-
- IconText {
- id: bellIcon
- anchors.centerIn: parent
- anchors.horizontalCenterOffset: root.iconHorizontalOffset
- text: "\uE7F4"
- font.pixelSize: 15
- color: root.notificationCount > 0 && root.bar
- ? root.bar.urgent
- : root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.4) : Commons.Color.foreground
-
- Behavior on color { ColorAnimation { duration: 150 } }
- }
-
- Rectangle {
- visible: root.notificationCount > 0
- width: Math.max(Commons.Style.space(12), badgeText.implicitWidth + 6)
- height: Commons.Style.space(12)
- radius: height / 2
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- anchors.verticalCenter: bellIcon.verticalCenter
- anchors.verticalCenterOffset: -6
- anchors.horizontalCenter: bellIcon.horizontalCenter
- anchors.horizontalCenterOffset: 7
-
- Text {
- id: badgeText
- anchors.centerIn: parent
- text: root.notificationCount > 99 ? "99"
- : String(root.notificationCount)
- color: root.bar ? root.bar.background : Commons.Color.background
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 7
- font.weight: Font.Bold
- }
- }
-
- MouseArea {
- id: notificationMouse
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar) root.bar.showTooltip(root,
- root.notificationCount > 0
- ? root.notificationCount + (root.notificationCount === 1
- ? " notification" : " notifications")
- : "No notifications")
- onExited: if (root.bar) root.bar.hideTooltip(root)
- onClicked: function(mouse) {
- if (root.bar) root.bar.hideTooltip(root)
- if (mouse.button === Qt.RightButton) root.dndRequested()
- else root.toggleRequested()
- }
- }
-
- readonly property bool tooltipHovered: visible && notificationMouse.containsMouse
-}
diff --git a/widgets/PickerImage.qml b/widgets/PickerImage.qml
deleted file mode 100644
index 3aa0b8f..0000000
--- a/widgets/PickerImage.qml
+++ /dev/null
@@ -1,85 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-
-Item {
- id: root
-
- required property var bar
- required property var controller
- required property var entry
- property real imageRadius: Commons.Style.cornerRadius
- property real imageInset: 0
- property real washOpacity: 0
- property real decodeWidth: 0
- property real decodeHeight: 0
- property bool selected: false
- property bool current: false
- property bool interactive: true
- signal activated()
-
- Rectangle {
- anchors.fill: parent
- radius: root.imageRadius
- color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.055)
- border.color: root.selected ? root.bar.urgent
- : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.2)
- border.width: root.selected ? Commons.Style.space(2) : 1
- clip: true
-
- Image {
- anchors.fill: parent
- anchors.margins: Math.max(parent.border.width, root.imageInset)
- source: root.controller.thumbnailUrl(root.entry)
- fillMode: Image.PreserveAspectCrop
- asynchronous: true
- cache: false
- retainWhileLoading: true
- sourceSize.width: Math.max(1, Math.round(root.decodeWidth > 0
- ? root.decodeWidth : width * 1.25))
- sourceSize.height: Math.max(1, Math.round(root.decodeHeight > 0
- ? root.decodeHeight : height * 1.25))
- }
-
- Rectangle {
- anchors.fill: parent
- anchors.margins: Math.max(parent.border.width, root.imageInset)
- radius: Math.max(0, root.imageRadius - anchors.margins)
- color: root.bar.visualTokens ? root.bar.visualTokens.paper : root.bar.background
- opacity: root.washOpacity
- }
-
- IconText {
- anchors.centerIn: parent
- visible: !root.entry || !root.entry.thumbnailReady
- text: root.controller.videoMode ? "movie" : "image"
- color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.34)
- font.pixelSize: Commons.Style.font.displayLarge
- fill: 0
- }
-
- Rectangle {
- visible: root.current
- width: Commons.Style.space(8)
- height: width
- radius: width / 2
- x: Commons.Style.space(9)
- y: Commons.Style.space(9)
- color: root.bar.urgent
- border.color: Qt.rgba(0, 0, 0, 0.35)
- border.width: 1
- }
- }
-
- MouseArea {
- anchors.fill: parent
- enabled: root.interactive
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: root.activated()
- }
-}
diff --git a/widgets/PickerOverlay.qml b/widgets/PickerOverlay.qml
deleted file mode 100644
index 2cbba28..0000000
--- a/widgets/PickerOverlay.qml
+++ /dev/null
@@ -1,452 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import Quickshell.Wayland
-import qs.Commons as Commons
-import "PickerModel.js" as PickerModel
-
-PanelWindow {
- id: root
-
- required property var bar
- required property var controller
- readonly property bool validScreen: controller.activeScreen !== null
- && String(controller.activeScreen.name || "") !== ""
- readonly property bool tanzakuActive: controller.pickerStyle === "tanzaku"
- readonly property bool selectedIsCurrent: controller.selectedEntry !== null
- && (controller.mode === "theme"
- ? String(controller.selectedEntry.label || "")
- === String(controller.currentSelection || "")
- : String(controller.selectedEntry.sourcePath || "")
- === String(controller.currentSelection || ""))
-
- function selectedLabel() {
- const entry = controller.selectedEntry
- if (!entry) return controller.emptyText
- return controller.mediaMode
- ? PickerModel.mediaLabel(entry.sourcePath)
- : String(entry.label || "")
- }
-
- function selectedHeadline() {
- const label = selectedLabel()
- return controller.mediaMode
- ? controller.title + " · " + label : label
- }
-
- screen: controller.activeScreen
- visible: controller.opened && validScreen
- anchors { top: true; bottom: true; left: true; right: true }
- color: "transparent"
- exclusionMode: ExclusionMode.Ignore
- WlrLayershell.namespace: "shibumi-picker"
- WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.keyboardFocus: visible
- ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
-
- onVisibleChanged: if (visible) Qt.callLater(stage.forceActiveFocus)
-
- Rectangle {
- anchors.fill: parent
- color: root.tanzakuActive
- ? Qt.rgba(root.bar.background.r, root.bar.background.g,
- root.bar.background.b, 0.8)
- : Qt.rgba(0.035, 0.035, 0.05, 0.975)
-
- Behavior on opacity { NumberAnimation { duration: 170 } }
- }
-
- MouseArea {
- anchors.fill: parent
- onClicked: root.controller.close()
- }
-
- FocusScope {
- id: stage
- anchors.fill: parent
- focus: root.visible
-
- Keys.onPressed: function(event) {
- if (event.key === Qt.Key_Escape) {
- root.controller.close()
- } else if (event.key === Qt.Key_Left || event.key === Qt.Key_Up) {
- root.controller.moveSelection(-1)
- } else if (event.key === Qt.Key_Right || event.key === Qt.Key_Down) {
- root.controller.moveSelection(1)
- } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
- root.controller.activateSelected()
- } else if (event.key === Qt.Key_Backspace) {
- root.controller.updateFilter(root.controller.filterText.slice(0, -1))
- } else if (event.key === Qt.Key_Delete && root.controller.mediaMode) {
- root.controller.requestDeleteSelected()
- } else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C
- && root.controller.mediaMode) {
- root.controller.copySelected()
- } else if (event.key === Qt.Key_Tab) {
- root.controller.cycleStyle(event.modifiers & Qt.ShiftModifier ? -1 : 1)
- } else if (!(event.modifiers & (Qt.ControlModifier | Qt.AltModifier
- | Qt.MetaModifier)) && event.text && event.text.length === 1
- && event.text.charCodeAt(0) >= 32) {
- root.controller.updateFilter(root.controller.filterText + event.text)
- } else {
- return
- }
- event.accepted = true
- }
-
- Loader {
- id: viewLoader
- anchors.fill: parent
- sourceComponent: root.controller.pickerStyle === "hearthstone" ? hearthstoneView
- : tanzakuView
- }
-
- Component {
- id: tanzakuView
- TanzakuPickerView { controller: root.controller; bar: root.bar }
- }
-
- Component {
- id: hearthstoneView
- HearthstonePickerView { controller: root.controller; bar: root.bar }
- }
-
- Column {
- id: tanzakuFooter
-
- readonly property real focusedWidth: Math.min(Commons.Style.space(460),
- parent.width * 0.48)
- readonly property real focusedHeight: focusedWidth * 9 / 16
-
- visible: root.tanzakuActive && root.controller.selectedEntry !== null
- anchors.horizontalCenter: parent.horizontalCenter
- y: parent.height / 2 - Commons.Style.space(10) + focusedHeight / 2
- + Commons.Style.space(16)
- width: Math.min(parent.width - Commons.Style.space(48),
- focusedWidth + Commons.Style.space(120))
- spacing: Commons.Style.space(12)
-
- Rectangle {
- anchors.horizontalCenter: parent.horizontalCenter
- width: tanzakuFooter.focusedWidth * 0.42
- height: Commons.Style.space(3)
- radius: height / 2
- gradient: Gradient {
- orientation: Gradient.Horizontal
- GradientStop { position: 0; color: "transparent" }
- GradientStop { position: 0.5; color: root.bar.urgent }
- GradientStop { position: 1; color: "transparent" }
- }
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- width: parent.width
- horizontalAlignment: Text.AlignHCenter
- text: root.selectedHeadline()
- color: root.bar.foreground
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.space(22)
- font.weight: Font.DemiBold
- elide: Text.ElideRight
- maximumLineCount: 1
- renderType: Text.NativeRendering
- }
-
- Row {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.controller.mode === "theme"
- && root.controller.selectedThemePalette.length > 0
- spacing: Commons.Style.space(6)
-
- Repeater {
- model: root.controller.selectedThemePalette
- delegate: Rectangle {
- required property var modelData
- width: Commons.Style.space(13)
- height: width
- radius: width / 2
- color: String(modelData || "transparent")
- border.color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.15)
- border.width: 1
- }
- }
- }
-
- Row {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.selectedIsCurrent
- || (root.controller.mode === "theme"
- && root.controller.selectedThemeAuthor !== "")
- spacing: Commons.Style.space(12)
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: root.selectedIsCurrent
- text: "● current"
- color: root.bar.urgent
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
-
- Text {
- id: tanzakuAuthor
- anchors.verticalCenter: parent.verticalCenter
- visible: root.controller.mode === "theme"
- && root.controller.selectedThemeAuthor !== ""
- text: "by " + root.controller.selectedThemeAuthor + " ↗"
- color: tanzakuAuthorMouse.containsMouse ? root.bar.urgent
- : root.bar.visualTokens ? root.bar.visualTokens.mutedInk
- : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.45)
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
-
- MouseArea {
- id: tanzakuAuthorMouse
- anchors.fill: parent
- enabled: root.controller.selectedThemeRepo !== ""
- hoverEnabled: true
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: root.controller.openSelectedThemeRepo()
- }
- }
- }
-
- Row {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.controller.mediaMode
- spacing: Commons.Style.space(7)
-
- OverlayAction {
- icon: "open_in_new"
- label: "Open"
- onTriggered: root.controller.activateSelected()
- }
- OverlayAction {
- icon: "content_copy"
- label: "Copy"
- onTriggered: root.controller.copySelected()
- }
- OverlayAction {
- icon: root.controller.confirmDelete ? "delete_forever" : "delete"
- label: root.controller.confirmDelete ? "Confirm" : "Trash"
- urgent: root.controller.confirmDelete
- onTriggered: root.controller.requestDeleteSelected()
- }
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.controller.filterText !== ""
- text: root.controller.filterText
- color: root.bar.urgent
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.body
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "← → navigate Enter "
- + (root.controller.mediaMode ? "open" : "apply")
- + " Esc cancel type to filter Tab style"
- color: root.bar.visualTokens ? root.bar.visualTokens.mutedInk
- : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.45)
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- horizontalAlignment: Text.AlignHCenter
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.controller.statusText !== ""
- text: root.controller.statusText
- color: root.bar.urgent
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
-
- Column {
- visible: !root.tanzakuActive
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.bottom: parent.bottom
- anchors.bottomMargin: Math.max(Commons.Style.space(32), parent.height * 0.06)
- spacing: Commons.Style.space(5)
- width: Math.min(parent.width - Commons.Style.space(48), Commons.Style.space(760))
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- width: parent.width
- horizontalAlignment: Text.AlignHCenter
- text: root.selectedHeadline()
- color: root.controller.selectedEntry ? "#ececee"
- : Qt.rgba(0.92, 0.92, 0.94, 0.55)
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.space(26)
- font.weight: Font.DemiBold
- elide: Text.ElideMiddle
- maximumLineCount: 1
- renderType: Text.NativeRendering
- }
-
- Row {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.controller.mode === "theme"
- && (root.controller.selectedThemeAuthor !== ""
- || root.controller.selectedThemePalette.length > 0)
- spacing: Commons.Style.space(6)
-
- Repeater {
- model: root.controller.selectedThemePalette
- delegate: Rectangle {
- required property var modelData
- width: Commons.Style.space(12)
- height: width
- radius: width / 2
- color: String(modelData || "transparent")
- border.color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.28)
- border.width: 1
- }
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: root.controller.selectedThemeAuthor !== ""
- text: "by " + root.controller.selectedThemeAuthor
- color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.62)
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
-
- MouseArea {
- anchors.fill: parent
- enabled: root.controller.selectedThemeRepo !== ""
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: root.controller.openSelectedThemeRepo()
- }
- }
- }
-
- Row {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.controller.mediaMode && root.controller.selectedEntry !== null
- spacing: Commons.Style.space(7)
-
- OverlayAction {
- icon: "open_in_new"
- label: "Open"
- onTriggered: root.controller.activateSelected()
- }
- OverlayAction {
- icon: "content_copy"
- label: "Copy"
- onTriggered: root.controller.copySelected()
- }
- OverlayAction {
- icon: root.controller.confirmDelete ? "delete_forever" : "delete"
- label: root.controller.confirmDelete ? "Confirm" : "Trash"
- urgent: root.controller.confirmDelete
- onTriggered: root.controller.requestDeleteSelected()
- }
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "← → scroll navigate Enter "
- + (root.controller.mediaMode ? "open" : "apply") + " Esc cancel"
- + " type to filter Tab switch style"
- color: Qt.rgba(0.92, 0.92, 0.94, 0.55)
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.controller.filterText !== ""
- || root.controller.confirmDelete
- text: root.controller.filterText !== ""
- ? "Filter: " + root.controller.filterText
- : "Press Delete again to move this file to Trash"
- color: root.controller.confirmDelete ? root.bar.urgent
- : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.62)
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- visible: root.controller.statusText !== ""
- text: root.controller.statusText
- color: root.bar.urgent
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
- }
-
- component OverlayAction: Rectangle {
- id: action
- required property string icon
- required property string label
- property bool urgent: false
- signal triggered()
-
- implicitWidth: actionContent.implicitWidth + Commons.Style.space(18)
- implicitHeight: Commons.Style.space(28)
- radius: root.bar.visualTokens ? root.bar.visualTokens.tileRadius
- : Commons.Style.cornerRadius
- color: action.urgent
- ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g, root.bar.urgent.b, 0.18)
- : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, actionMouse.containsMouse ? 0.14 : 0.08)
- border.color: action.urgent ? root.bar.urgent
- : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.22)
- border.width: 1
-
- Row {
- id: actionContent
- anchors.centerIn: parent
- spacing: Commons.Style.space(5)
-
- IconText {
- anchors.verticalCenter: parent.verticalCenter
- text: action.icon
- color: action.urgent ? root.bar.urgent : root.bar.foreground
- font.pixelSize: Commons.Style.font.body
- fill: 0
- }
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: action.label
- color: action.urgent ? root.bar.urgent : root.bar.foreground
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: action.triggered()
- }
- }
-}
diff --git a/widgets/PowerProfilePanel.qml b/widgets/PowerProfilePanel.qml
deleted file mode 100644
index cb86568..0000000
--- a/widgets/PowerProfilePanel.qml
+++ /dev/null
@@ -1,206 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-import "../services/PowerModel.js" as PowerModel
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var powerService
- property int selectedIndex: 0
-
- owner: ownerWidget
- open: ownerWidget.opened && powerService.profileAvailable
- focusTarget: keyCatcher
- contentWidth: fittedContentWidth(Commons.Style.space(220))
- contentHeight: fittedContentHeight(column.implicitHeight)
-
- function syncSelection() {
- var index = powerService.profiles.indexOf(powerService.activeProfile)
- selectedIndex = index >= 0 ? index : 0
- }
-
- function moveSelection(delta) {
- var count = powerService.profiles.length
- if (count <= 0) return
- selectedIndex = (selectedIndex + delta + count) % count
- }
-
- function activateSelected() {
- if (selectedIndex < 0 || selectedIndex >= powerService.profiles.length) return
- if (powerService.setProfile(powerService.profiles[selectedIndex]))
- ownerWidget.close()
- }
-
- onOpenChanged: if (open) {
- powerService.refreshProfiles()
- syncSelection()
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
-
- Connections {
- target: panel.powerService
- function onProfilesChanged() { panel.syncSelection() }
- function onActiveProfileChanged() { panel.syncSelection() }
- }
-
- onMoveRequested: function(dx, dy) {
- panel.moveSelection(dx !== 0 ? dx : dy)
- }
- onActivateRequested: panel.activateSelected()
- onCloseRequested: panel.ownerWidget.close()
- onTabRequested: function(direction) { panel.ownerWidget.switchPanel(direction) }
-
- Column {
- id: column
- width: parent.width
- spacing: Commons.Style.space(5)
-
- Row {
- width: parent.width
- spacing: Commons.Style.space(4)
- Text {
- width: parent.width - closeAction.width - parent.spacing
- anchors.verticalCenter: parent.verticalCenter
- text: "Power Profile"
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.heading
- font.weight: Font.Medium
- }
- IconAction {
- id: closeAction
- anchors.verticalCenter: parent.verticalCenter
- icon: "close"
- tooltip: "Close"
- onClicked: panel.ownerWidget.close()
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.bar ? Qt.rgba(panel.bar.foreground.r, panel.bar.foreground.g,
- panel.bar.foreground.b, 0.18) : Commons.Color.popups.border
- }
-
- Repeater {
- model: panel.powerService.profiles
- delegate: Item {
- id: profileRow
- required property string modelData
- required property int index
- readonly property bool active: panel.powerService.activeProfile === modelData
- readonly property bool selected: panel.selectedIndex === index
- width: column.width
- height: Commons.Style.space(32)
-
- Rectangle {
- anchors.fill: parent
- radius: panel.controlRadius
- color: profileMouse.containsMouse || profileRow.selected
- ? panel.bar ? Qt.rgba(panel.bar.urgent.r, panel.bar.urgent.g,
- panel.bar.urgent.b, 0.18) : Commons.Color.background
- : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: profileRow.active && panel.bar
- ? panel.bar.urgent : profileMouse.containsMouse || profileRow.selected
- ? panel.controlHoverBorderColor : panel.controlBorderColor
- }
-
- Row {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: Commons.Style.space(8)
- anchors.rightMargin: Commons.Style.space(8)
- anchors.verticalCenter: parent.verticalCenter
- spacing: Commons.Style.space(8)
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: profileRow.modelData === "power-saver" ? "\uF06C"
- : profileRow.modelData === "performance" ? "\uF0E7" : "\uF24E"
- color: panel.bar ? panel.bar.urgent : Commons.Color.accent
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.heading
- renderType: Text.QtRendering
- }
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: PowerModel.profileLabel(profileRow.modelData)
- color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: profileRow.active ? Font.DemiBold : Font.Normal
- }
- }
-
- MouseArea {
- id: profileMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- enabled: !panel.powerService.profileActionRunning
- onEntered: panel.selectedIndex = profileRow.index
- onClicked: {
- if (panel.powerService.setProfile(profileRow.modelData))
- panel.ownerWidget.close()
- }
- }
- }
- }
-
- Text {
- visible: panel.powerService.profileActionRunning
- || panel.powerService.profileError !== ""
- width: parent.width
- text: panel.powerService.profileError !== ""
- ? panel.powerService.profileError : "Applying profile…"
- color: panel.powerService.profileError !== "" && panel.bar
- ? panel.bar.urgent : panel.bar ? panel.bar.foreground : Commons.Color.foreground
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.caption
- horizontalAlignment: Text.AlignHCenter
- }
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-}
diff --git a/widgets/PowerProfileWidget.qml b/widgets/PowerProfileWidget.qml
deleted file mode 100644
index 76bf0e8..0000000
--- a/widgets/PowerProfileWidget.qml
+++ /dev/null
@@ -1,154 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "hancore.shibumi.power-profile"
- manageIpc: false
- property url panelSource: Qt.resolvedUrl("PowerProfilePanel.qml")
-
- readonly property var powerService: bar ? bar.powerService : null
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool compact: setting("compact", false) === true
- readonly property bool profileAvailable: !!(powerService && powerService.profileAvailable)
- readonly property string profile: powerService ? powerService.activeProfile : ""
- readonly property string shortName: powerService
- ? powerService.activeProfileShortName : "---"
- readonly property string profileLabel: powerService
- ? powerService.activeProfileLabel : "Power profiles unavailable"
- readonly property string profileIcon: profile === "power-saver" ? "\uF06C"
- : profile === "performance" ? "\uF0E7" : "\uF24E"
- readonly property var interactionTarget: interaction
- readonly property var panelItem: panelLoader.item
- property var profileOwner: null
-
- visible: profileAvailable
- implicitWidth: visible ? (bar && bar.vertical ? bar.barSize : surface.implicitWidth) : 0
- implicitHeight: visible
- ? (bar && bar.vertical ? surface.implicitHeight : bar ? bar.barSize : 28) : 0
-
- function childPanelWidget(pluginId) {
- return String(pluginId || "") === "omarchy.power" ? root : null
- }
-
- function syncProfileLease() {
- if (powerService === profileOwner) return
- if (profileOwner) profileOwner.releaseProfiles()
- profileOwner = powerService
- if (profileOwner) profileOwner.acquireProfiles()
- }
-
- function syncPanelLoader() {
- if (!opened || !profileAvailable) {
- panelLoader.source = ""
- return
- }
- panelLoader.setSource(panelSource, {
- anchorItem: surface,
- bar: root.bar,
- ownerWidget: root,
- powerService: root.powerService
- })
- }
-
- function activate(button) {
- if (!powerService) return false
- if (button === Qt.RightButton) return powerService.cycleProfile()
- toggle()
- return true
- }
-
- onOpenedChanged: syncPanelLoader()
- onProfileAvailableChanged: if (!profileAvailable && opened) close()
- onPowerServiceChanged: syncProfileLease()
- Component.onCompleted: syncProfileLease()
- Component.onDestruction: {
- if (profileOwner) profileOwner.releaseProfiles()
- profileOwner = null
- }
-
- Item {
- id: surface
- anchors.centerIn: parent
- implicitWidth: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? root.bar.barSize
- : content.implicitWidth + 2 * root.tokens.pillPaddingX
- implicitHeight: !root.bar || !root.tokens ? 0
- : root.bar.vertical ? content.implicitHeight + Commons.Style.space(10)
- : root.tokens.slotHeight
- width: implicitWidth
- height: implicitHeight
-
- PillSurface {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- bar: root.bar
- }
-
- Loader {
- id: content
- anchors.centerIn: parent
- sourceComponent: !root.bar || !root.tokens ? null
- : root.compact || root.bar.vertical ? compactContent : fullContent
- }
-
- MouseArea {
- id: interaction
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar) root.bar.showTooltip(surface, root.profileLabel)
- onExited: if (root.bar) root.bar.hideTooltip(surface)
- onClicked: function(event) {
- if (root.bar) root.bar.hideTooltip(surface)
- root.activate(event.button)
- }
- }
- }
-
- Loader { id: panelLoader }
-
- Component {
- id: fullContent
- Row {
- spacing: root.tokens.contentGap
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: "PWR"
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.6) : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- font.letterSpacing: 0.5
- renderType: Text.NativeRendering
- }
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: root.shortName
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.tokens.labelSize
- renderType: Text.NativeRendering
- }
- }
- }
-
- Component {
- id: compactContent
- Text {
- text: root.profileIcon
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: root.profile === "balanced" ? 13 : 14
- renderType: Text.QtRendering
- }
- }
-}
diff --git a/widgets/QuickAccessWidget.qml b/widgets/QuickAccessWidget.qml
deleted file mode 100644
index 9718a32..0000000
--- a/widgets/QuickAccessWidget.qml
+++ /dev/null
@@ -1,160 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import qs.Commons as Commons
-
-Item {
- id: root
-
- property var bar: null
- property string moduleName: "hancore.shibumi.quick-access"
- property var settings: ({})
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property var picker: bar ? bar.pickerService : null
- readonly property var targetScreen: root.QsWindow && root.QsWindow.window
- ? root.QsWindow.window.screen : null
- readonly property bool opened: picker && picker.opened
- && picker.activeScreenName === (targetScreen ? String(targetScreen.name || "") : "")
- readonly property bool panelLoaded: opened
-
- visible: bar !== null && tokens !== null && picker !== null
- implicitWidth: visible ? actionRow.implicitWidth + 2 * tokens.pillPaddingX : 0
- implicitHeight: visible ? bar.barSize : 0
-
- function showTip(owner, text) {
- if (bar) bar.showTooltip(owner, text)
- }
-
- function hideTip(owner) {
- if (bar) bar.hideTooltip(owner)
- }
-
- function openMode(mode) {
- if (!picker) return false
- if (bar) bar.requestPopout(root)
- return picker.openMode(mode, targetScreen)
- }
-
- function toggleMode(mode) {
- if (!picker) return false
- if (picker.opened && picker.mode === mode) {
- picker.close()
- return true
- }
- return openMode(mode)
- }
-
- function open() { return openMode("wallpaper") }
- function close() { if (picker) picker.close() }
- function toggle() { return toggleMode("wallpaper") }
- function closeForPopoutSwitch() { close() }
-
- function childPanelWidget(pluginId) {
- const id = String(pluginId || "")
- return id === "hancore.shibumi.picker" || id === "hancore.shibumi.media-browser"
- ? root : null
- }
-
- onOpenedChanged: {
- if (!opened && bar) bar.releasePopout(root)
- }
- Component.onDestruction: {
- if (opened) close()
- if (bar) bar.releasePopout(root)
- }
-
- PillSurface {
- anchors.fill: parent
- anchors.topMargin: root.bar && !root.bar.vertical
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: anchors.topMargin
- bar: root.bar
- }
-
- Row {
- id: actionRow
- anchors.centerIn: parent
- spacing: Commons.Style.space(4)
-
- ActionIcon {
- icon: root.bar && root.bar.idleInhibited
- ? String.fromCodePoint(0xF06E8) : String.fromCodePoint(0xF06E9)
- nerdGlyph: true
- inactiveOpacity: 0.45
- active: root.bar && root.bar.idleInhibited
- tooltip: active ? "Idle inhibited: ON" : "Idle inhibited: OFF"
- onTriggered: if (root.bar) root.bar.idleInhibited = !root.bar.idleInhibited
- }
-
- ActionIcon {
- icon: "collections"
- active: root.picker && root.picker.opened && root.picker.mediaMode
- tooltip: "Left: Screenshots Right: Videos"
- onTriggered: function(button) {
- root.toggleMode(button === Qt.RightButton ? "videos" : "screenshots")
- }
- }
-
- ActionIcon {
- icon: "palette"
- active: root.picker && root.picker.opened && root.picker.imageMode
- tooltip: "Left: Themes Right: Wallpapers"
- onTriggered: function(button) {
- root.toggleMode(button === Qt.RightButton ? "wallpaper" : "theme")
- }
- }
- }
-
- component ActionIcon: Item {
- id: action
- required property string icon
- required property bool active
- required property string tooltip
- property bool nerdGlyph: false
- property real inactiveOpacity: 0.62
- readonly property color iconColor: root.bar
- ? (active ? root.bar.urgent
- : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, inactiveOpacity)) : Commons.Color.foreground
- signal triggered(int button)
-
- implicitWidth: Commons.Style.space(22)
- implicitHeight: root.tokens ? root.tokens.slotHeight : Commons.Style.space(28)
-
- IconText {
- anchors.centerIn: parent
- visible: !action.nerdGlyph
- text: action.icon
- color: action.iconColor
- font.pixelSize: 14
- font.weight: Font.Medium
- fill: action.active ? 1 : 0
- Behavior on color { ColorAnimation { duration: 150 } }
- }
-
- Text {
- anchors.centerIn: parent
- visible: action.nerdGlyph
- text: action.icon
- color: action.iconColor
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: Commons.Style.space(14)
- renderType: Text.QtRendering
- Behavior on color { ColorAnimation { duration: 150 } }
- }
-
- MouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: root.showTip(action, action.tooltip)
- onExited: root.hideTip(action)
- onClicked: function(mouse) {
- root.hideTip(action)
- action.triggered(mouse.button)
- }
- }
- }
-}
diff --git a/widgets/StatusIndicators.qml b/widgets/StatusIndicators.qml
deleted file mode 100644
index b1778f3..0000000
--- a/widgets/StatusIndicators.qml
+++ /dev/null
@@ -1,247 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Item {
- id: root
-
- required property var bar
- required property var statusService
-
- readonly property var stateService: bar && "shell" in bar && bar.shell
- && typeof bar.shell.serviceFor === "function"
- ? bar.shell.serviceFor("hancore.shibumi.state") : null
- readonly property bool hasActive: statusService
- && (statusService.stayAwake || statusService.notificationsSilenced
- || statusService.recording || statusService.voxtypeActive)
- readonly property string idleIconFamily: idleIcon.font.family
- readonly property string dndIconFamily: dndIcon.font.family
- readonly property string recordingIconFamily: recordingIcon.fontFamily
- readonly property string recordingIconGlyph: recordingIcon.text
- readonly property color recordingIconColor: stateService
- && typeof stateService.paletteColor === "function"
- ? stateService.paletteColor("color01")
- : bar ? bar.urgent : Commons.Color.urgent
- readonly property string voxtypeIconFamily: voxtypeIcon.font.family
- readonly property real dndOpticalCenterOffset: 1
- readonly property int activeCount: (statusService && statusService.stayAwake ? 1 : 0)
- + (statusService && statusService.notificationsSilenced ? 1 : 0)
- + (statusService && statusService.recording ? 1 : 0)
- + (statusService && statusService.voxtypeActive ? 1 : 0)
- readonly property real activeWidth:
- (statusService && statusService.stayAwake ? 20 : 0)
- + (statusService && statusService.notificationsSilenced ? 20 : 0)
- + (statusService && statusService.recording ? recordingIndicator.implicitWidth : 0)
- + (statusService && statusService.voxtypeActive ? 20 : 0)
- + Math.max(0, activeCount - 1) * Commons.Style.space(8)
-
- implicitWidth: hasActive ? activeWidth : 0
- implicitHeight: bar ? bar.barSize : Commons.Style.space(35)
- visible: hasActive
-
- function pad(value) {
- return Number(value) < 10 ? "0" + Number(value) : String(Number(value))
- }
-
- function elapsedText() {
- const elapsed = statusService ? Number(statusService.recordingElapsed || 0) : 0
- const hours = Math.floor(elapsed / 3600)
- const minutes = Math.floor((elapsed % 3600) / 60)
- const seconds = Math.floor(elapsed % 60)
- return hours > 0
- ? hours + ":" + pad(minutes) + ":" + pad(seconds)
- : pad(minutes) + ":" + pad(seconds)
- }
-
- function toggleStayAwake() {
- return statusService && typeof statusService.toggleStayAwake === "function"
- ? statusService.toggleStayAwake() : false
- }
-
- function toggleNotifications() {
- return statusService && typeof statusService.toggleNotifications === "function"
- ? statusService.toggleNotifications() : false
- }
-
- function stopRecording() {
- return statusService && typeof statusService.stopRecording === "function"
- ? statusService.stopRecording() : false
- }
-
- function activateVoxtype(button) {
- if (!statusService) return false
- if (button === Qt.RightButton
- && typeof statusService.openVoxtypeConfig === "function")
- return statusService.openVoxtypeConfig()
- if (typeof statusService.openVoxtypeModel === "function")
- return statusService.openVoxtypeModel()
- return false
- }
-
- Row {
- id: indicatorRow
- anchors.centerIn: parent
- spacing: Commons.Style.space(8)
-
- Item {
- id: idleIndicator
- visible: root.statusService && root.statusService.stayAwake
- implicitWidth: visible ? 20 : 0
- implicitHeight: root.implicitHeight
- width: implicitWidth
- height: implicitHeight
-
- Text {
- id: idleIcon
- anchors.centerIn: parent
- text: "\udb86\uded6"
- color: root.bar ? root.bar.urgent : Commons.Color.urgent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 13
- }
-
- Ui.WidgetButton {
- anchors.fill: parent
- bar: root.bar
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- tooltipText: "Idle lock disabled"
- onPressed: root.toggleStayAwake()
- }
- }
-
- Item {
- id: dndIndicator
- visible: root.statusService && root.statusService.notificationsSilenced
- implicitWidth: visible ? 20 : 0
- implicitHeight: root.implicitHeight
- width: implicitWidth
- height: implicitHeight
-
- IconText {
- id: dndIcon
- anchors.centerIn: parent
- anchors.horizontalCenterOffset: root.dndOpticalCenterOffset
- text: "\ue7f6"
- color: root.bar ? root.bar.urgent : Commons.Color.urgent
- font.pixelSize: 14
- }
-
- Ui.WidgetButton {
- anchors.fill: parent
- bar: root.bar
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- tooltipText: "Notifications silenced"
- onPressed: root.toggleNotifications()
- }
- }
-
- Item {
- id: recordingIndicator
- visible: root.statusService && root.statusService.recording
- implicitWidth: visible ? recordingRow.implicitWidth + 6 : 0
- implicitHeight: root.implicitHeight
- width: implicitWidth
- height: implicitHeight
-
- Component.onCompleted: if (root.bar) root.bar.registerClickTarget(recordingIndicator)
- Component.onDestruction: if (root.bar) root.bar.unregisterClickTarget(recordingIndicator)
-
- Row {
- id: recordingRow
- anchors.centerIn: parent
- spacing: Commons.Style.space(5)
-
- Ui.OpticalGlyph {
- id: recordingIcon
- anchors.verticalCenter: parent.verticalCenter
- width: Commons.Style.space(16)
- height: width
- text: ""
- color: root.recordingIconColor
- fontFamily: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- fontSize: 13
-
- SequentialAnimation on opacity {
- running: recordingIndicator.visible
- loops: Animation.Infinite
- NumberAnimation { to: 0.25; duration: 600; easing.type: Easing.InOutSine }
- NumberAnimation { to: 1; duration: 600; easing.type: Easing.InOutSine }
- }
- }
-
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: root.elapsedText()
- color: root.bar ? root.bar.urgent : Commons.Color.urgent
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- }
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar) root.bar.showTooltip(recordingIndicator,
- "Recording · " + root.elapsedText() + "\nClick to stop")
- onExited: if (root.bar) root.bar.hideTooltip(recordingIndicator)
- onClicked: {
- if (root.bar) root.bar.hideTooltip(recordingIndicator)
- root.stopRecording()
- }
- }
- }
-
- Item {
- id: voxtypeIndicator
- visible: root.statusService && root.statusService.voxtypeActive
- implicitWidth: visible ? 20 : 0
- implicitHeight: root.implicitHeight
- width: implicitWidth
- height: implicitHeight
-
- IconText {
- id: voxtypeIcon
- anchors.centerIn: parent
- text: root.statusService && root.statusService.voxtypeState === "recording"
- ? "\ue029" : "\ue65f"
- color: root.statusService
- && root.statusService.voxtypeState === "recording" && root.bar
- ? root.bar.urgent : root.bar ? root.bar.foreground : Commons.Color.foreground
- font.pixelSize: 14
-
- SequentialAnimation on opacity {
- running: voxtypeIndicator.visible && root.statusService
- && root.statusService.voxtypeState === "recording"
- loops: Animation.Infinite
- NumberAnimation { to: 0.35; duration: 600; easing.type: Easing.InOutSine }
- NumberAnimation { to: 1; duration: 600; easing.type: Easing.InOutSine }
- }
- }
-
- Ui.WidgetButton {
- anchors.fill: parent
- bar: root.bar
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- tooltipText: root.statusService && root.statusService.voxtypeHint
- ? root.statusService.voxtypeHint
- : root.statusService && root.statusService.voxtypeState === "recording"
- ? "Voxtype recording" : "Voxtype transcribing"
- onPressed: function(button) {
- root.activateVoxtype(button)
- }
- }
- }
- }
-}
diff --git a/widgets/StatusWidget.qml b/widgets/StatusWidget.qml
deleted file mode 100644
index 3195ba5..0000000
--- a/widgets/StatusWidget.qml
+++ /dev/null
@@ -1,542 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-
-Item {
- id: root
-
- property var bar: null
- property string moduleName: "hancore.shibumi.status"
- property var settings: ({})
- readonly property url updateSource: registeredSource(
- "hancore.shibumi.update-center")
- readonly property url traySource: registeredSource("omarchy.tray")
- property Component updateComponent: String(updateSource) ? null
- : registeredComponent("hancore.shibumi.update-center")
- property Component trayComponent: String(traySource) ? null
- : registeredComponent("omarchy.tray")
- property url trayDrawerSource: Qt.resolvedUrl("TrayDrawerPanel.qml")
- property url trayAppMenuSource: Qt.resolvedUrl("TrayAppMenuPanel.qml")
- property url notificationPanelSource: Qt.resolvedUrl("NotificationPanel.qml")
- property bool trayDrawerOpen: false
- property bool trayAppMenuOpen: false
- property var trayAppMenuItem: null
- property var trayAppMenuAnchor: null
- property real trayAppMenuAnchorX: 0
- property bool notificationPanelOpen: false
-
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property bool v2Mode: tokens && tokens.v2Shell === true
- readonly property int statusActionSlot: Commons.Style.space(22)
- readonly property int horizontalInset: tokens
- && tokens.pillPaddingX !== undefined
- ? Number(tokens.pillPaddingX) || Commons.Style.space(9)
- : Commons.Style.space(9)
- readonly property var updateWidget: updateLoader.item
- readonly property var trayWidget: trayLoader.item
- readonly property var notificationService: bar && bar.shell
- && typeof bar.shell.firstPartyServiceFor === "function"
- ? bar.shell.firstPartyServiceFor("omarchy.notifications") : null
- readonly property var trayDrawerItem: trayDrawerLoader.item
- readonly property var trayAppMenuPanelItem: trayAppMenuLoader.item
- readonly property var notificationPanelItem: notificationPanelLoader.item
- readonly property bool trayDrawerLoaded: trayDrawerItem !== null
- readonly property bool notificationPanelLoaded: notificationPanelItem !== null
- readonly property bool panelLoaded: notificationPanelLoaded
- readonly property bool updatePresented: updateWidget !== null
- readonly property bool trayPresented: trayView.presented
- readonly property bool notificationPresented: notificationView.presented
- readonly property int pendingCount: notificationView.pendingCount
- readonly property int recentCount: notificationView.recentCount
- readonly property int notificationCount: notificationView.notificationCount
- readonly property bool ready: updateWidget !== null || trayWidget !== null
- || notificationService !== null
- readonly property bool opened: trayDrawerOpen || trayAppMenuOpen
- || notificationPanelOpen
- || childOpened(updateWidget)
- || childOpened(trayWidget)
- readonly property bool hasVisibleChild: updatePresented || trayPresented
- || notificationPresented
- readonly property real childGap: Commons.Style.space(4)
- readonly property int presentedCount: (updatePresented ? 1 : 0)
- + (trayPresented ? 1 : 0)
- + (notificationPresented ? 1 : 0)
- readonly property real contentWidth:
- updateSlotWidth + traySlotWidth + notificationSlotWidth
- + Math.max(0, presentedCount - 1) * childGap
- readonly property real updateSlotWidth: updatePresented
- ? updateSlot.implicitWidth : 0
- readonly property real traySlotWidth: trayPresented
- ? trayView.implicitWidth : 0
- readonly property real notificationSlotWidth: notificationPresented
- ? notificationView.implicitWidth : 0
- readonly property real trayPinnedIconOffset:
- trayView.pinnedIconHorizontalOffset
- readonly property real notificationIconOffset:
- notificationView.iconHorizontalOffset
-
- visible: ready && hasVisibleChild
- implicitWidth: visible ? contentWidth + 2 * horizontalInset : 0
- implicitHeight: visible ? (bar ? bar.barSize : Commons.Style.space(35)) : 0
-
- function registeredComponent(id) {
- if (bar && typeof bar.registeredWidgetComponent === "function")
- return bar.registeredWidgetComponent(id)
- const registry = bar ? bar.barWidgetRegistry : null
- if (registry) void(registry.revision)
- const widgets = registry && registry.widgets ? registry.widgets : ({})
- const entry = widgets[id]
- return entry ? entry.component : null
- }
-
- function registeredSource(id) {
- return bar && typeof bar.registeredWidgetSource === "function"
- ? bar.registeredWidgetSource(id) : ""
- }
-
- function childSettings(id) {
- return bar && typeof bar.widgetSettings === "function"
- ? bar.widgetSettings("G3", id) : ({})
- }
-
- function persistEmbeddedTraySettings(moduleId, values) {
- const id = String(moduleId || "")
- if (id !== "omarchy.tray" || !values || typeof values !== "object"
- || Array.isArray(values)) return false
- const state = bar && bar.shell
- && typeof bar.shell.serviceFor === "function"
- ? bar.shell.serviceFor("hancore.shibumi.state") : null
- if (!state || typeof state.setWidgetSetting !== "function") return false
- let changed = false
- for (const key in values) {
- if (key === "id"
- || !Object.prototype.hasOwnProperty.call(values, key)) continue
- changed = state.setWidgetSetting("G3", id, key, values[key]) || changed
- }
- return changed
- }
-
- function injectChild(item, id) {
- if (!item) return
- if ("bar" in item) item.bar = hostProxy
- if ("moduleName" in item) item.moduleName = id
- if ("settings" in item) item.settings = childSettings(id)
- item.opacity = 0
- }
-
- function injectUpdateChild(item) {
- if (!item) return
- if ("bar" in item) item.bar = root.bar
- if ("moduleName" in item) item.moduleName = "hancore.shibumi.update-center"
- if ("settings" in item)
- item.settings = childSettings("hancore.shibumi.update-center")
- }
-
- function injectChildren() {
- injectUpdateChild(updateWidget)
- injectChild(trayWidget, "omarchy.tray")
- }
-
- function syncLoader(loader, component, source, id) {
- loader.sourceComponent = null
- loader.source = ""
- if (component !== null) {
- loader.sourceComponent = component
- } else if (String(source)) {
- loader.setSource(source, {
- bar: hostProxy,
- moduleName: id,
- settings: childSettings(id)
- })
- }
- }
-
- function syncTrayLoader() {
- syncLoader(trayLoader, trayComponent, traySource, "omarchy.tray")
- }
-
- function syncUpdateLoader() {
- updateLoader.sourceComponent = null
- updateLoader.source = ""
- if (updateComponent !== null) {
- updateLoader.sourceComponent = updateComponent
- } else if (String(updateSource)) {
- updateLoader.setSource(updateSource, {
- bar: root.bar,
- moduleName: "hancore.shibumi.update-center",
- settings: childSettings("hancore.shibumi.update-center")
- })
- }
- }
-
- function syncTrayDrawerLoader() {
- trayDrawerLoader.source = ""
- if (!trayDrawerOpen || !trayWidget || !String(trayDrawerSource)) return
- trayDrawerLoader.setSource(trayDrawerSource, {
- ownerWidget: root,
- trayBackend: trayWidget,
- anchorItem: trayView,
- bar: root.bar
- })
- }
-
- function syncTrayAppMenuLoader() {
- trayAppMenuLoader.source = ""
- if (!trayAppMenuOpen || !trayAppMenuItem || !trayAppMenuAnchor
- || !String(trayAppMenuSource)) return
- trayAppMenuLoader.setSource(trayAppMenuSource, {
- ownerWidget: root,
- trayItem: trayAppMenuItem,
- anchorItem: trayAppMenuAnchor,
- bar: root.bar
- })
- }
-
- function syncNotificationPanelLoader() {
- notificationPanelLoader.source = ""
- if (!notificationPanelOpen || !notificationService
- || !String(notificationPanelSource)) return
- notificationPanelLoader.setSource(notificationPanelSource, {
- ownerWidget: root,
- notificationService: root.notificationService,
- anchorItem: notificationView,
- bar: root.bar
- })
- }
-
- function childOpened(item) {
- if (!item) return false
- return item.opened === true || item.popupOpen === true
- || item.managePopupOpen === true || item.trayMenuOpen === true
- }
-
- function open() {
- closeTrayDrawer()
- closeChild(updateWidget)
- closeChild(trayWidget)
- if (!notificationService || !String(notificationPanelSource)) return false
- notificationPanelOpen = true
- return true
- }
-
- function closeChild(item) {
- if (!item) return
- if (typeof item.close === "function") item.close()
- else {
- if ("opened" in item) item.opened = false
- if ("popupOpen" in item) item.popupOpen = false
- if ("managePopupOpen" in item) item.managePopupOpen = false
- if ("trayMenuOpen" in item) item.trayMenuOpen = false
- }
- }
-
- function closeTrayDrawer() {
- trayDrawerOpen = false
- closeTrayAppMenu()
- // The host tray menu is a separate popup owned by omarchy.tray. Closing
- // our drawer must close that popup too, otherwise Escape/outside-click
- // can leave the app menu visible without its owning drawer.
- closeChild(trayWidget)
- }
-
- function trayItemName(item) {
- if (!item) return "Tray App"
- const title = String(item.title || "").trim()
- if (title !== "") return title
- const tooltipTitle = String(item.tooltipTitle || "").trim()
- if (tooltipTitle !== "") return tooltipTitle
- return String(item.id || "Tray App")
- }
-
- function appMenuAnchorX(anchor) {
- const fallback = trayView.x + trayView.width / 2
- if (!anchor || typeof anchor.mapToItem !== "function"
- || typeof root.mapFromItem !== "function") return fallback
- try {
- const globalPoint = anchor.mapToItem(null,
- Number(anchor.width || 0) / 2, Number(anchor.height || 0) / 2)
- const localPoint = root.mapFromItem(null, globalPoint.x, globalPoint.y)
- return Number.isFinite(localPoint.x) ? localPoint.x : fallback
- } catch (error) {
- return fallback
- }
- }
-
- function openTrayAppMenu(item, anchor) {
- if (!item || !item.menu || !anchor || !String(trayAppMenuSource))
- return false
- closeChild(trayWidget)
- trayAppMenuItem = item
- trayAppMenuAnchorX = appMenuAnchorX(anchor)
- // The clicked button lives in the full-screen drawer window. Preserve its
- // X position, but anchor the child panel to the real bar window so panel
- // height and top/bottom placement are computed from the bar, as in V1.
- trayAppMenuAnchor = trayAppMenuBarAnchor
- // V1 popupOpened() makes the drawer and the DBus menu mutually exclusive.
- // Closing only after preserving the X coordinate avoids leaving the
- // full-screen drawer layer above the newly opened menu.
- trayDrawerOpen = false
- trayAppMenuOpen = true
- return true
- }
-
- function closeTrayAppMenu() {
- trayAppMenuOpen = false
- trayAppMenuItem = null
- trayAppMenuAnchor = null
- closeChild(trayWidget)
- }
-
- function closeNotificationPanel() {
- notificationPanelOpen = false
- }
-
- function toggleTrayDrawer() {
- if (!trayWidget || Number(trayWidget.drawerCount || 0) <= 0) return false
- if (!trayDrawerOpen) {
- closeChild(updateWidget)
- closeNotificationPanel()
- closeChild(trayWidget)
- }
- trayDrawerOpen = !trayDrawerOpen
- return true
- }
-
- function toggleNotificationDnd() {
- const service = notificationService
- if (!service || typeof service.setDoNotDisturb !== "function") return false
- service.setDoNotDisturb(service.doNotDisturb !== true)
- return true
- }
-
- function close() {
- closeTrayDrawer()
- closeTrayAppMenu()
- closeNotificationPanel()
- closeChild(updateWidget)
- closeChild(trayWidget)
- }
-
- function toggle() {
- if (notificationPanelOpen) {
- closeNotificationPanel()
- return true
- }
- return open()
- }
-
- function switchPanel(direction) {
- return bar && typeof bar.switchPanelFrom === "function"
- ? bar.switchPanelFrom(root, direction) : false
- }
-
- function childPanelWidget(pluginId) {
- const id = String(pluginId || "")
- if (id === "hancore.shibumi.update-center") return updateWidget
- return id === "omarchy.notifications" ? root : null
- }
-
- function ownsPanelWidget(owner) {
- return !!owner && (owner === root || owner === updateWidget
- || owner === notificationPanelItem
- || owner === trayWidget || owner === trayDrawerItem
- || owner === trayAppMenuPanelItem
- || (trayAppMenuPanelItem && owner === trayAppMenuPanelItem.owner))
- }
-
- function scheduleChildSync() {
- childSyncTimer.restart()
- }
-
- onBarChanged: {
- scheduleChildSync()
- if (trayDrawerOpen) syncTrayDrawerLoader()
- if (notificationPanelOpen) syncNotificationPanelLoader()
- }
- onSettingsChanged: injectChildren()
- onUpdateComponentChanged: scheduleChildSync()
- onUpdateSourceChanged: scheduleChildSync()
- onTrayComponentChanged: scheduleChildSync()
- onTraySourceChanged: scheduleChildSync()
- onTrayDrawerOpenChanged: syncTrayDrawerLoader()
- onTrayDrawerSourceChanged: syncTrayDrawerLoader()
- onTrayWidgetChanged: if (trayDrawerOpen) syncTrayDrawerLoader()
- onTrayAppMenuOpenChanged: syncTrayAppMenuLoader()
- onTrayAppMenuSourceChanged: syncTrayAppMenuLoader()
- onTrayAppMenuItemChanged: if (trayAppMenuOpen) syncTrayAppMenuLoader()
- onTrayAppMenuAnchorChanged: if (trayAppMenuOpen) syncTrayAppMenuLoader()
- onNotificationPanelOpenChanged: syncNotificationPanelLoader()
- onNotificationPanelSourceChanged: syncNotificationPanelLoader()
- onNotificationServiceChanged: if (notificationPanelOpen)
- syncNotificationPanelLoader()
-
- Connections {
- target: root.bar
- function onLayoutConfigChanged() { root.injectChildren() }
- }
-
- Component.onCompleted: {
- scheduleChildSync()
- }
- Component.onDestruction: {
- close()
- if (updateWidget && "bar" in updateWidget) updateWidget.bar = null
- if (trayWidget && "bar" in trayWidget) trayWidget.bar = null
- }
-
- QtObject {
- id: trayShellProxy
-
- readonly property var realShell: root.bar ? root.bar.shell : null
-
- function updateEntryInline(moduleId, values) {
- if (String(moduleId || "") === "omarchy.tray")
- return root.persistEmbeddedTraySettings(moduleId, values)
- return realShell && typeof realShell.updateEntryInline === "function"
- ? realShell.updateEntryInline(moduleId, values) : false
- }
-
- function serviceFor(pluginId) {
- return realShell && typeof realShell.serviceFor === "function"
- ? realShell.serviceFor(pluginId) : null
- }
-
- function firstPartyServiceFor(pluginId) {
- return realShell && typeof realShell.firstPartyServiceFor === "function"
- ? realShell.firstPartyServiceFor(pluginId) : null
- }
- }
-
- QtObject {
- id: hostProxy
-
- readonly property var realBar: root.bar
- readonly property bool vertical: realBar ? realBar.vertical === true : false
- readonly property int barSize: realBar ? Number(realBar.barSize) : 35
- readonly property int sizeHorizontal: realBar
- && realBar.sizeHorizontal !== undefined
- ? Number(realBar.sizeHorizontal) : barSize
- readonly property string position: realBar
- ? String(realBar.position || "top") : "top"
- readonly property string fontFamily: realBar
- ? String(realBar.fontFamily || "monospace") : "monospace"
- readonly property color foreground: realBar ? realBar.foreground : "#ffffff"
- readonly property color barForeground: foreground
- readonly property color urgent: realBar ? realBar.urgent : foreground
- readonly property bool foregroundAnimationEnabled: realBar
- ? realBar.foregroundAnimationEnabled !== false : false
- readonly property var shell: trayShellProxy
- readonly property var layoutConfig: realBar ? realBar.layoutConfig : ({})
- readonly property var activePopout: realBar ? realBar.activePopout : null
- readonly property var clickTargets: realBar ? realBar.clickTargets : []
-
- function registerClickTarget(_target) {}
- function unregisterClickTarget(_target) {}
- function showTooltip(_target, _text) {}
- function hideTooltip(_target) {}
-
- function requestPopout(owner) {
- if (realBar && typeof realBar.requestPopout === "function")
- realBar.requestPopout(owner)
- }
-
- function releasePopout(owner) {
- if (realBar && typeof realBar.releasePopout === "function")
- realBar.releasePopout(owner)
- }
-
- function switchPanelFrom(_owner, direction) {
- return realBar && typeof realBar.switchPanelFrom === "function"
- ? realBar.switchPanelFrom(root, direction) : false
- }
-
- function targetBelongsToWindow(target, window) {
- return realBar && typeof realBar.targetBelongsToWindow === "function"
- ? realBar.targetBelongsToWindow(target, window) : false
- }
- }
-
- Timer {
- id: childSyncTimer
- interval: 0
- onTriggered: {
- root.syncUpdateLoader()
- root.syncTrayLoader()
- root.injectChildren()
- }
- }
-
- PillSurface {
- anchors.fill: parent
- anchors.topMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- anchors.bottomMargin: root.tokens
- ? Math.round((parent.height - root.tokens.pillHeight) / 2) : 0
- bar: root.bar
- }
-
- Row {
- id: statusRow
- anchors.centerIn: parent
- spacing: root.childGap
- z: 2
-
- Item {
- id: updateSlot
- anchors.verticalCenter: parent.verticalCenter
- visible: updateLoader.item !== null
- implicitWidth: visible ? root.statusActionSlot : 0
- implicitHeight: updateLoader.implicitHeight
- width: implicitWidth
- height: implicitHeight
-
- Loader {
- id: updateLoader
- anchors.centerIn: parent
- onLoaded: root.injectUpdateChild(item)
- }
- }
-
- TrayStatusView {
- id: trayView
- bar: root.bar
- trayBackend: root.trayWidget
- onDrawerRequested: root.toggleTrayDrawer()
- }
-
- NotificationStatusView {
- id: notificationView
- bar: root.bar
- slotWidth: root.statusActionSlot
- notificationService: root.notificationService
- onToggleRequested: root.toggle()
- onDndRequested: root.toggleNotificationDnd()
- }
- }
-
- Loader {
- id: trayLoader
- x: statusRow.x + trayView.x
- width: trayView.width
- height: root.height
- opacity: 0
- z: 0
- onLoaded: {
- root.injectChild(item, "omarchy.tray")
- }
- }
-
- Loader { id: trayDrawerLoader }
- Loader { id: trayAppMenuLoader }
-
- Item {
- id: trayAppMenuBarAnchor
- x: root.trayAppMenuAnchorX
- y: 0
- width: 0
- height: root.height
- visible: false
- }
- Loader { id: notificationPanelLoader }
-}
diff --git a/widgets/SystemUpdateWidget.qml b/widgets/SystemUpdateWidget.qml
deleted file mode 100644
index bc25138..0000000
--- a/widgets/SystemUpdateWidget.qml
+++ /dev/null
@@ -1,63 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-// V1 presentation over Quattro's registered omarchy.system-update owner.
-// The backend keeps update detection, IPC, and launch ownership.
-Ui.BarWidget {
- id: root
-
- moduleName: "omarchy.system-update"
- property var backendWidget: null
-
- readonly property bool updateAvailable: backendWidget
- ? backendWidget.updateAvailable === true : false
- readonly property string tooltipText: "Omarchy update available"
- readonly property string iconFamily: updateIcon.font.family
- readonly property real opticalCenterOffset: 1
- readonly property var interactionTarget: updateMouse
-
- visible: updateAvailable
- implicitWidth: updateAvailable ? 20 : 0
- implicitHeight: bar ? bar.barSize : Commons.Style.space(35)
- width: implicitWidth
- height: implicitHeight
-
- function activate() {
- if (!backendWidget || typeof backendWidget.runUpdate !== "function") return false
- backendWidget.runUpdate()
- return true
- }
-
- function refresh() {
- if (!backendWidget || typeof backendWidget.refresh !== "function") return false
- backendWidget.refresh()
- return true
- }
-
- IconText {
- id: updateIcon
- anchors.centerIn: parent
- anchors.horizontalCenterOffset: root.opticalCenterOffset
- text: "\ue627"
- color: root.bar ? root.bar.urgent : Commons.Color.urgent
- font.pixelSize: 15
- }
-
- MouseArea {
- id: updateMouse
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar) root.bar.showTooltip(root, root.tooltipText)
- onExited: if (root.bar) root.bar.hideTooltip(root)
- onClicked: function(mouse) {
- if (root.bar) root.bar.hideTooltip(root)
- if (mouse.button === Qt.RightButton) root.refresh()
- else root.activate()
- }
- }
-}
diff --git a/widgets/TanzakuPickerView.qml b/widgets/TanzakuPickerView.qml
deleted file mode 100644
index 79ca6fb..0000000
--- a/widgets/TanzakuPickerView.qml
+++ /dev/null
@@ -1,155 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-
-Item {
- id: root
- required property var bar
- required property var controller
-
- readonly property real centerX: width / 2
- readonly property real centerY: height / 2 - Commons.Style.space(10)
- readonly property real focusedWidth: Math.min(Commons.Style.space(460), width * 0.48)
- readonly property real focusedHeight: focusedWidth * 9 / 16
- readonly property real peekWidth: Commons.Style.space(104)
- readonly property real stripWidth: Commons.Style.space(24)
- readonly property real gap: Commons.Style.space(8)
- readonly property int maxVisible: 5
- property bool navigationAnimationsEnabled: false
- property int layoutGeneration: 0
-
- function settleInitialLayout() {
- navigationAnimationsEnabled = false
- const generation = ++layoutGeneration
- if (!controller.opened || controller.filteredEntries.length === 0) return
- Qt.callLater(function() {
- if (generation !== root.layoutGeneration || !root.controller.opened
- || root.controller.filteredEntries.length === 0) return
- root.navigationAnimationsEnabled = true
- })
- }
-
- function isCurrent(entry) {
- if (!entry) return false
- if (controller.mode === "theme")
- return String(entry.label || "") === String(controller.currentSelection || "")
- return String(entry.sourcePath || "") === String(controller.currentSelection || "")
- }
-
- function itemWidth(relative) {
- const distance = Math.abs(relative)
- return distance === 0 ? focusedWidth
- : distance === 1 ? peekWidth : stripWidth
- }
-
- function itemX(relative) {
- const targetWidth = itemWidth(relative)
- if (relative === 0) return centerX - targetWidth / 2
- if (relative === -1) return centerX - focusedWidth / 2 - gap - targetWidth
- if (relative === 1) return centerX + focusedWidth / 2 + gap
- const offset = peekWidth + gap + (Math.abs(relative) - 2) * (stripWidth + gap)
- return relative < 0
- ? centerX - focusedWidth / 2 - gap - offset - targetWidth
- : centerX + focusedWidth / 2 + gap + offset
- }
-
- Repeater {
- model: root.controller.filteredEntries
-
- delegate: Item {
- id: slice
- required property int index
- required property var modelData
- readonly property int relative: index - root.controller.selectedIndex
- readonly property bool focused: relative === 0
- readonly property bool neighbor: Math.abs(relative) === 1
-
- visible: Math.abs(relative) <= root.maxVisible
- width: root.itemWidth(relative)
- height: root.focusedHeight
- x: root.itemX(relative)
- y: root.centerY - height / 2
- z: focused ? 20 : 10 - Math.abs(relative)
- opacity: 1
-
- Behavior on x {
- enabled: root.navigationAnimationsEnabled
- NumberAnimation { duration: 260; easing.type: Easing.OutCubic }
- }
- Behavior on width {
- enabled: root.navigationAnimationsEnabled
- NumberAnimation { duration: 260; easing.type: Easing.OutCubic }
- }
- Behavior on opacity {
- enabled: root.navigationAnimationsEnabled
- NumberAnimation { duration: 200 }
- }
-
- PickerImage {
- anchors.fill: parent
- bar: root.bar
- controller: root.controller
- entry: slice.modelData
- selected: slice.focused
- current: root.isCurrent(slice.modelData)
- imageRadius: Commons.Style.space(8)
- imageInset: Commons.Style.space(3)
- washOpacity: slice.focused ? 0 : slice.neighbor ? 0.28 : 0.5
- decodeWidth: root.focusedWidth
- decodeHeight: root.focusedHeight
- onActivated: slice.focused
- ? root.controller.activateSelected() : root.controller.selectIndex(slice.index)
- }
- }
- }
-
- Connections {
- target: root.controller
- function onRequestSerialChanged() { root.settleInitialLayout() }
- function onOpenedChanged() { root.settleInitialLayout() }
- function onFilteredEntriesChanged() { root.settleInitialLayout() }
- }
-
- Component.onCompleted: settleInitialLayout()
-
- Text {
- visible: root.controller.selectedEntry !== null
- anchors.horizontalCenter: parent.horizontalCenter
- y: root.centerY - root.focusedHeight / 2 - Commons.Style.space(22) - height
- text: root.controller.mode === "theme" ? "THEME"
- : root.controller.mode === "wallpaper" ? "WALLPAPER"
- : String(root.controller.title || "").toUpperCase()
- color: root.bar.urgent
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- font.letterSpacing: Commons.Style.space(3)
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- visible: root.controller.selectedEntry !== null
- x: root.centerX + root.focusedWidth / 2 - width
- y: root.centerY - root.focusedHeight / 2 - Commons.Style.space(23) - height
- text: (root.controller.selectedIndex + 1) + " / "
- + root.controller.filteredEntries.length
- color: root.bar.visualTokens ? root.bar.visualTokens.mutedInk
- : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.45)
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.caption
- renderType: Text.NativeRendering
- }
-
- Text {
- visible: root.controller.selectedEntry === null
- anchors.centerIn: parent
- text: root.controller.emptyText
- color: root.bar.foreground
- font.family: root.bar.fontFamily
- font.pixelSize: Commons.Style.font.body
- font.letterSpacing: Commons.Style.space(1)
- renderType: Text.NativeRendering
- }
-}
diff --git a/widgets/TrayAppMenuPanel.qml b/widgets/TrayAppMenuPanel.qml
deleted file mode 100644
index 7007632..0000000
--- a/widgets/TrayAppMenuPanel.qml
+++ /dev/null
@@ -1,362 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-// V1 tray-menu presentation over Quattro's authoritative SystemTrayItem.menu.
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var trayItem
-
- readonly property var rootMenu: trayItem ? trayItem.menu : null
- property var menuStack: []
- readonly property var currentMenu: menuStack.length > 0
- ? menuStack[menuStack.length - 1] : null
- readonly property var currentMenuChildren: menuStack.length > 1
- ? submenuOpener.children : rootMenuOpener.children
- property real rootMenuImplicitHeight: 0
- readonly property string appName: ownerWidget
- && typeof ownerWidget.trayItemName === "function"
- ? ownerWidget.trayItemName(trayItem) : "App Menu"
-
- // Share the drawer's popout owner. A separate owner makes the coordinator
- // close the status group while this menu is still opening.
- owner: ownerWidget
- open: ownerWidget.trayAppMenuOpen
- focusTarget: keyCatcher
- padding: Commons.Style.space(8)
- contentWidth: fittedContentWidth(Commons.Style.space(220))
- contentHeight: fittedContentHeight(
- Math.max(menuColumn.implicitHeight, rootMenuImplicitHeight))
-
- function resetMenuStack() {
- rootMenuImplicitHeight = 0
- menuStack = open && rootMenu ? [rootMenu] : []
- }
-
- function closeMenu() {
- closeSubmenus()
- ownerWidget.closeTrayAppMenu()
- }
-
- function pushSubmenu(entry) {
- if (!entry || entry.hasChildren !== true) return false
- menuStack = menuStack.concat([entry])
- Qt.callLater(function() {
- if (panel.currentMenu !== entry) return
- if (typeof entry.updateLayout === "function") entry.updateLayout()
- })
- return true
- }
-
- function popSubmenu() {
- if (menuStack.length <= 1) return false
- menuStack = menuStack.slice(0, -1)
- return true
- }
-
- function closeSubmenus() {
- menuStack = []
- }
-
- function stripMnemonic(value) {
- return String(value || "").replace(/_([^_])/, "$1")
- }
-
- onOpenChanged: resetMenuStack()
- onRootMenuChanged: resetMenuStack()
- Component.onCompleted: resetMenuStack()
-
- Item {
- visible: false
- width: 0
- height: 0
-
- QsMenuOpener {
- // Keep the DBusMenuHandle referenced for the complete panel lifetime.
- // Releasing it while switching to a child destroys that child's tree.
- id: rootMenuOpener
- menu: panel.open ? panel.rootMenu : null
- }
-
- QsMenuOpener {
- id: submenuOpener
- menu: panel.menuStack.length > 1 ? panel.currentMenu : null
- }
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.closeMenu()
-
- Column {
- id: menuColumn
- width: parent.width
- spacing: Commons.Style.space(1)
- onImplicitHeightChanged: {
- if (panel.menuStack.length <= 1)
- panel.rootMenuImplicitHeight = implicitHeight
- }
-
- Item {
- width: parent.width
- height: Commons.Style.space(28)
-
- Image {
- id: appIcon
- anchors.left: parent.left
- anchors.leftMargin: Commons.Style.space(6)
- anchors.verticalCenter: parent.verticalCenter
- visible: String(panel.trayItem ? panel.trayItem.icon || "" : "") !== ""
- source: panel.trayItem ? String(panel.trayItem.icon || "") : ""
- sourceSize.width: Commons.Style.space(16)
- sourceSize.height: Commons.Style.space(16)
- width: visible ? Commons.Style.space(16) : 0
- height: Commons.Style.space(16)
- fillMode: Image.PreserveAspectFit
- smooth: true
- }
-
- Text {
- anchors.left: appIcon.right
- anchors.leftMargin: appIcon.visible ? Commons.Style.space(8) : 0
- anchors.right: panel.shellStyle === "shibumi"
- ? closeText.left : closeAction.left
- anchors.rightMargin: Commons.Style.space(8)
- anchors.verticalCenter: parent.verticalCenter
- text: panel.appName !== "" ? panel.appName : "App Menu"
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.body
- font.weight: Font.Medium
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- id: closeText
- anchors.right: parent.right
- anchors.rightMargin: Commons.Style.space(6)
- anchors.verticalCenter: parent.verticalCenter
- visible: panel.shellStyle === "shibumi"
- text: "\u2715"
- color: closeMouse.containsMouse
- ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
- Behavior on color { ColorAnimation { duration: 120 } }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- anchors.margins: -6
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.closeMenu()
- }
- }
-
- IconAction {
- id: closeAction
- anchors.right: parent.right
- anchors.rightMargin: Commons.Style.space(6)
- anchors.verticalCenter: parent.verticalCenter
- visible: panel.shellStyle !== "shibumi"
- icon: "close"
- tooltip: "Close"
- onClicked: panel.closeMenu()
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Rectangle {
- visible: panel.menuStack.length > 1
- width: parent.width
- height: visible ? Commons.Style.space(24) : 0
- radius: panel.controlRadius
- color: backMouse.containsMouse
- ? panel.controlHoverFillColor : "transparent"
-
- Text {
- anchors.left: parent.left
- anchors.leftMargin: Commons.Style.space(8)
- anchors.verticalCenter: parent.verticalCenter
- text: "\u2039 back"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: backMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.popSubmenu()
- }
- }
-
- Rectangle {
- visible: panel.menuStack.length > 1
- width: parent.width
- height: visible ? 1 : 0
- color: panel.dividerColor
- }
-
- Repeater {
- model: panel.currentMenuChildren
-
- delegate: Item {
- id: menuEntry
- required property var modelData
-
- width: menuColumn.width
- height: modelData.isSeparator
- ? Commons.Style.space(7) : Commons.Style.space(26)
-
- Rectangle {
- visible: menuEntry.modelData.isSeparator
- anchors.verticalCenter: parent.verticalCenter
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Rectangle {
- visible: !menuEntry.modelData.isSeparator
- anchors.fill: parent
- radius: panel.controlRadius
- color: entryMouse.containsMouse && menuEntry.modelData.enabled
- ? panel.controlActiveFillColor : "transparent"
- Behavior on color { ColorAnimation { duration: 100 } }
-
- Text {
- id: checkMark
- anchors.left: parent.left
- anchors.leftMargin: Commons.Style.space(6)
- anchors.verticalCenter: parent.verticalCenter
- width: Commons.Style.space(12)
- text: menuEntry.modelData.checkState === Qt.Checked ? "\u2713" : ""
- color: panel.controlAccent
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- renderType: Text.NativeRendering
- }
-
- Image {
- id: entryIcon
- anchors.left: checkMark.right
- anchors.leftMargin: Commons.Style.space(2)
- anchors.verticalCenter: parent.verticalCenter
- visible: String(menuEntry.modelData.icon || "") !== ""
- source: String(menuEntry.modelData.icon || "")
- sourceSize.width: Commons.Style.space(14)
- sourceSize.height: Commons.Style.space(14)
- width: visible ? Commons.Style.space(14) : 0
- height: Commons.Style.space(14)
- fillMode: Image.PreserveAspectFit
- smooth: true
- }
-
- Text {
- anchors.left: entryIcon.right
- anchors.leftMargin: Commons.Style.space(6)
- anchors.right: submenuArrow.left
- anchors.rightMargin: Commons.Style.space(4)
- anchors.verticalCenter: parent.verticalCenter
- text: panel.stripMnemonic(menuEntry.modelData.text)
- color: menuEntry.modelData.enabled
- ? panel.controlForeground
- : Commons.Util.alpha(panel.controlForeground, 0.35)
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.bodySmall
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- id: submenuArrow
- anchors.right: parent.right
- anchors.rightMargin: Commons.Style.space(8)
- anchors.verticalCenter: parent.verticalCenter
- visible: menuEntry.modelData.hasChildren
- text: "\u203a"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: Commons.Style.font.heading
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: entryMouse
- anchors.fill: parent
- enabled: menuEntry.modelData.enabled
- hoverEnabled: true
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: {
- if (menuEntry.modelData.hasChildren) {
- panel.pushSubmenu(menuEntry.modelData)
- } else {
- menuEntry.modelData.triggered()
- panel.closeMenu()
- }
- }
- }
- }
- }
- }
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-}
diff --git a/widgets/TrayDrawerPanel.qml b/widgets/TrayDrawerPanel.qml
deleted file mode 100644
index c22d1fa..0000000
--- a/widgets/TrayDrawerPanel.qml
+++ /dev/null
@@ -1,400 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import Quickshell.Services.SystemTray
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var trayBackend
- readonly property var drawerItems: trayBackend
- && Array.isArray(trayBackend.drawerItems) ? trayBackend.drawerItems : []
- readonly property int attentionCount: {
- let count = 0
- for (let index = 0; index < drawerItems.length; index++) {
- if (itemNeedsAttention(drawerItems[index])) count++
- }
- return count
- }
-
- owner: ownerWidget
- open: ownerWidget.trayDrawerOpen
- focusTarget: keyCatcher
- // V1 contract: 328px outer card with 304px usable row width.
- gap: 8
- padding: 12
- contentWidth: fittedContentWidth(328)
- contentHeight: fittedContentHeight(contentColumn.implicitHeight)
-
- function closePanel() { ownerWidget.closeTrayDrawer() }
-
- function itemName(item) {
- if (!item) return "Tray App"
- const title = String(item.title || "").trim()
- if (title !== "") return title
- const tooltipTitle = String(item.tooltipTitle || "").trim()
- if (tooltipTitle !== "") return tooltipTitle
- let fallback = String(item.id || "").trim()
- const slash = fallback.lastIndexOf("/")
- if (slash >= 0 && slash < fallback.length - 1)
- fallback = fallback.substring(slash + 1)
- fallback = fallback.replace(/^org\.(kde|ayatana|freedesktop)\./i, "")
- .replace(/[_-]+/g, " ")
- return fallback !== "" ? fallback : "Tray App"
- }
-
- function itemDescription(item, displayName) {
- if (!item) return ""
- const description = String(item.tooltipDescription || "").trim()
- const tooltipTitle = String(item.tooltipTitle || "").trim()
- const name = String(displayName || "").trim().toLowerCase()
- if (description !== "" && description.toLowerCase() !== name)
- return description
- if (tooltipTitle !== "" && tooltipTitle.toLowerCase() !== name)
- return tooltipTitle
- return ""
- }
-
- function itemNeedsAttention(item) {
- return !!item && item.status === Status.NeedsAttention
- }
-
- function itemHasMenu(item) {
- return !!item && (item.hasMenu === true || item.menu)
- }
-
- function togglePin(item) {
- const id = item ? String(item.id || "") : ""
- if (!id || !trayBackend || typeof trayBackend.togglePin !== "function")
- return false
- trayBackend.togglePin(id)
- return true
- }
-
- function openAppMenu(item, anchor, mouse) {
- if (!item || !anchor) return false
- if (ownerWidget && typeof ownerWidget.openTrayAppMenu === "function"
- && ownerWidget.openTrayAppMenu(item, anchor)) return true
- if (!trayBackend || typeof trayBackend.openTrayMenu !== "function")
- return false
- trayBackend.openTrayMenu(item, anchor, mouse)
- return true
- }
-
- function activateApp(item) {
- if (!item || typeof item.activate !== "function") return false
- closePanel()
- Qt.callLater(function() { item.activate() })
- return true
- }
-
- onDrawerItemsChanged: if (open && drawerItems.length === 0) closePanel()
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.closePanel()
-
- Column {
- id: contentColumn
- width: parent.width
- spacing: 8
-
- Item {
- width: parent.width
- height: 24
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "Tray Apps"
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 13
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: panel.shellStyle === "shibumi"
- ? closeText.left : closeAction.left
- anchors.rightMargin: 12
- anchors.verticalCenter: parent.verticalCenter
- text: panel.drawerItems.length
- + (panel.drawerItems.length === 1 ? " APP" : " APPS")
- + (panel.attentionCount > 0
- ? " \u00b7 " + panel.attentionCount + " ATTENTION" : "")
- color: panel.attentionCount > 0
- ? panel.controlAccent : panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 10
- renderType: Text.NativeRendering
- }
-
- Text {
- id: closeText
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- visible: panel.shellStyle === "shibumi"
- text: "\u2715"
- color: closeMouse.containsMouse
- ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
- Behavior on color { ColorAnimation { duration: 120 } }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- anchors.margins: -6
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.closePanel()
- }
- }
-
- IconAction {
- id: closeAction
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- visible: panel.shellStyle !== "shibumi"
- icon: "close"
- tooltip: "Close"
- onClicked: panel.closePanel()
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Item {
- width: parent.width
- height: Math.min(trayRows.implicitHeight, 320)
-
- Flickable {
- anchors.fill: parent
- contentHeight: trayRows.implicitHeight
- clip: true
- interactive: contentHeight > height
- boundsBehavior: Flickable.StopAtBounds
-
- Column {
- id: trayRows
- width: parent.width
- spacing: 6
-
- Repeater {
- model: panel.drawerItems
-
- delegate: Item {
- id: appRow
- required property var modelData
-
- readonly property string appName: panel.itemName(modelData)
- readonly property string appDescription:
- panel.itemDescription(modelData, appName)
- readonly property bool needsAttention:
- panel.itemNeedsAttention(modelData)
- readonly property bool hasMenu: panel.itemHasMenu(modelData)
- readonly property string statusDescription: needsAttention
- ? "\u26a0 "
- + (appDescription !== "" ? appDescription : "Needs attention")
- : appDescription
- readonly property real cellWidth: 96
-
- width: trayRows.width
- height: 28
-
- Rectangle {
- id: appButton
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- width: appRow.cellWidth
- height: parent.height
- radius: panel.controlRadius
- color: activateMouse.containsMouse
- ? panel.controlHoverFillColor : panel.controlFillColor
- border.color: activateMouse.containsMouse
- ? panel.controlHoverBorderColor : panel.controlBorderColor
- border.width: panel.controlBorderWidth
-
- Image {
- id: appIcon
- anchors.left: parent.left
- anchors.leftMargin: 8
- anchors.verticalCenter: parent.verticalCenter
- source: String(appRow.modelData.icon || "")
- sourceSize.width: 16
- sourceSize.height: 16
- width: 16
- height: 16
- fillMode: Image.PreserveAspectFit
- smooth: true
- }
-
- Text {
- anchors.left: appIcon.right
- anchors.leftMargin: 6
- anchors.right: parent.right
- anchors.rightMargin: 6
- anchors.verticalCenter: parent.verticalCenter
- text: (appRow.needsAttention ? "\u26a0 " : "") + appRow.appName
- color: appRow.needsAttention || activateMouse.containsMouse
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- font.weight: Font.Medium
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: activateMouse.containsMouse
- text: appRow.statusDescription !== ""
- ? appRow.appName + "\n" + appRow.statusDescription
- : appRow.appName
- }
-
- MouseArea {
- id: activateMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: function(mouse) {
- if (appRow.modelData.onlyMenu === true && appRow.hasMenu)
- panel.openAppMenu(appRow.modelData, appButton, mouse)
- else
- panel.activateApp(appRow.modelData)
- }
- }
- }
-
- Rectangle {
- id: pinButton
- anchors.left: appButton.right
- anchors.leftMargin: 8
- anchors.verticalCenter: parent.verticalCenter
- width: appRow.cellWidth
- height: parent.height
- radius: panel.controlRadius
- color: pinMouse.containsMouse
- ? panel.controlHoverFillColor : panel.controlFillColor
- border.color: pinMouse.containsMouse
- ? panel.controlHoverBorderColor : panel.controlBorderColor
- border.width: panel.controlBorderWidth
-
- Text {
- anchors.centerIn: parent
- text: "Pin"
- color: pinMouse.containsMouse
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: pinMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.togglePin(appRow.modelData)
- }
- }
-
- Rectangle {
- id: menuButton
- anchors.left: pinButton.right
- anchors.leftMargin: 8
- anchors.verticalCenter: parent.verticalCenter
- width: appRow.cellWidth
- height: parent.height
- radius: panel.controlRadius
- color: !appRow.hasMenu ? "transparent"
- : menuMouse.containsMouse
- ? panel.controlHoverFillColor : panel.controlFillColor
- border.color: !appRow.hasMenu
- ? Commons.Util.alpha(panel.controlForeground, 0.10)
- : menuMouse.containsMouse
- ? panel.controlHoverBorderColor : panel.controlBorderColor
- border.width: panel.controlBorderWidth
- opacity: appRow.hasMenu ? 1 : 0.42
-
- Text {
- anchors.centerIn: parent
- text: appRow.hasMenu ? "AppMenu" : "No Menu"
- color: menuMouse.containsMouse && appRow.hasMenu
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: menuMouse
- anchors.fill: parent
- enabled: appRow.hasMenu
- hoverEnabled: enabled
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: function(mouse) {
- panel.openAppMenu(appRow.modelData, menuButton, mouse)
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-}
diff --git a/widgets/TrayStatusView.qml b/widgets/TrayStatusView.qml
deleted file mode 100644
index f6eebfb..0000000
--- a/widgets/TrayStatusView.qml
+++ /dev/null
@@ -1,210 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-
-Item {
- id: root
-
- required property var bar
- property var trayBackend: null
- readonly property var pinnedItems: trayBackend
- && Array.isArray(trayBackend.pinnedItems) ? trayBackend.pinnedItems : []
- readonly property int drawerCount: trayBackend
- && trayBackend.drawerCount !== undefined
- ? Math.max(0, Number(trayBackend.drawerCount) || 0) : 0
- readonly property int pinnedCount: pinnedItems.length
- readonly property int totalCount: pinnedItems.length + drawerCount
- readonly property string drawerTooltipText: totalCount
- + (totalCount === 1 ? " app" : " apps")
- + (drawerCount > 0 ? " \u00b7 " + drawerCount + " hidden" : "")
- readonly property bool presented: pinnedItems.length > 0 || drawerCount > 0
- readonly property int visibleItemCount: pinnedCount + (drawerCount > 0 ? 1 : 0)
- readonly property real itemGap: Commons.Style.space(2)
- readonly property real pinnedIconHorizontalOffset: Commons.Style.space(1)
- signal drawerRequested()
-
- visible: presented
- implicitWidth: presented
- ? pinnedCount * Commons.Style.space(18)
- + (drawerCount > 0 ? Commons.Style.space(22) : 0)
- + Math.max(0, visibleItemCount - 1) * itemGap
- : 0
- implicitHeight: bar ? bar.barSize : Commons.Style.space(35)
- width: implicitWidth
- height: implicitHeight
-
- function itemId(item) {
- return item ? String(item.id || "") : ""
- }
-
- function tooltip(item) {
- if (!item) return "Tray application"
- if (trayBackend && typeof trayBackend.trayTooltip === "function")
- return String(trayBackend.trayTooltip(item) || "Tray application")
- return String(item.tooltipTitle || item.title || item.id || "Tray application")
- }
-
- function openMenu(item, anchor, mouse) {
- if (!trayBackend || typeof trayBackend.openTrayMenu !== "function") return false
- trayBackend.openTrayMenu(item, anchor, mouse)
- return true
- }
-
- function togglePin(item) {
- const id = itemId(item)
- if (!id || !trayBackend || typeof trayBackend.togglePin !== "function")
- return false
- trayBackend.togglePin(id)
- return true
- }
-
- Row {
- id: trayRow
- anchors.centerIn: parent
- spacing: root.itemGap
-
- Repeater {
- model: root.pinnedItems
-
- delegate: Item {
- id: trayDelegate
- required property var modelData
-
- implicitWidth: Commons.Style.space(18)
- implicitHeight: root.implicitHeight
- width: implicitWidth
- height: implicitHeight
-
- Component.onCompleted: if (root.bar) root.bar.registerClickTarget(trayDelegate)
- Component.onDestruction: {
- if (root.bar) root.bar.unregisterClickTarget(trayDelegate)
- }
-
- Image {
- anchors.centerIn: parent
- anchors.horizontalCenterOffset: root.pinnedIconHorizontalOffset
- source: String(trayDelegate.modelData.icon || "")
- sourceSize.width: Commons.Style.space(14)
- sourceSize.height: Commons.Style.space(14)
- width: Commons.Style.space(14)
- height: Commons.Style.space(14)
- fillMode: Image.PreserveAspectFit
- smooth: true
- }
-
- MouseArea {
- id: trayMouse
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar)
- root.bar.showTooltip(trayDelegate, root.tooltip(trayDelegate.modelData))
- onExited: if (root.bar) root.bar.hideTooltip(trayDelegate)
- onClicked: function(mouse) {
- if (root.bar) root.bar.hideTooltip(trayDelegate)
- if (mouse.button === Qt.RightButton) {
- root.togglePin(trayDelegate.modelData)
- } else if (mouse.button === Qt.MiddleButton
- || trayDelegate.modelData.onlyMenu === true) {
- root.openMenu(trayDelegate.modelData, trayDelegate, mouse)
- } else if (typeof trayDelegate.modelData.activate === "function") {
- trayDelegate.modelData.activate()
- }
- }
- onWheel: function(wheel) {
- if (typeof trayDelegate.modelData.scroll === "function")
- trayDelegate.modelData.scroll(wheel.angleDelta.y, false)
- }
- }
-
- readonly property bool tooltipHovered: visible && trayMouse.containsMouse
- }
- }
-
- Item {
- id: drawerToggle
- property bool registered: false
- visible: root.drawerCount > 0
- implicitWidth: visible ? Commons.Style.space(22) : 0
- implicitHeight: root.implicitHeight
- width: implicitWidth
- height: implicitHeight
-
- function syncRegistration() {
- if (!root.bar) return
- if (visible && !registered) {
- root.bar.registerClickTarget(drawerToggle)
- registered = true
- } else if (!visible && registered) {
- root.bar.unregisterClickTarget(drawerToggle)
- registered = false
- }
- }
-
- onVisibleChanged: syncRegistration()
- Component.onCompleted: syncRegistration()
- Component.onDestruction: {
- if (root.bar && registered) root.bar.unregisterClickTarget(drawerToggle)
- }
-
- IconText {
- id: moreIcon
- anchors.centerIn: parent
- text: "\uE5D3"
- font.pixelSize: 16
- color: drawerMouse.containsMouse && root.bar
- ? root.bar.foreground
- : root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.7) : Commons.Color.foreground
-
- Behavior on color { ColorAnimation { duration: 150 } }
- }
-
- Rectangle {
- visible: root.drawerCount > 0
- width: Math.max(Commons.Style.space(12), badgeText.implicitWidth + 6)
- height: Commons.Style.space(12)
- radius: height / 2
- color: root.bar ? root.bar.urgent : Commons.Color.accent
- anchors.verticalCenter: moreIcon.verticalCenter
- anchors.verticalCenterOffset: -6
- anchors.horizontalCenter: moreIcon.horizontalCenter
- anchors.horizontalCenterOffset: 7
-
- Text {
- id: badgeText
- anchors.centerIn: parent
- text: root.drawerCount > 99 ? "99" : String(root.drawerCount)
- color: root.bar ? root.bar.background : Commons.Color.background
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 7
- font.weight: Font.Bold
- }
- }
-
- MouseArea {
- id: drawerMouse
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: if (root.bar)
- root.bar.showTooltip(drawerToggle, root.drawerTooltipText)
- onExited: if (root.bar) root.bar.hideTooltip(drawerToggle)
- onClicked: function(mouse) {
- if (root.bar) root.bar.hideTooltip(drawerToggle)
- if (mouse.button === Qt.RightButton && root.trayBackend
- && root.trayBackend.managePopupOpen !== undefined) {
- root.trayBackend.managePopupOpen = !root.trayBackend.managePopupOpen
- } else {
- root.drawerRequested()
- }
- }
- }
-
- readonly property bool tooltipHovered: visible && drawerMouse.containsMouse
- }
- }
-}
diff --git a/widgets/WeatherPanel.qml b/widgets/WeatherPanel.qml
deleted file mode 100644
index d49092f..0000000
--- a/widgets/WeatherPanel.qml
+++ /dev/null
@@ -1,474 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var weatherService
-
- readonly property bool useImperial: ownerWidget.useImperial === true
- readonly property var forecastDays: weatherService
- && weatherService.forecastDays ? weatherService.forecastDays : []
- readonly property color primaryTextColor: shibumiTokens
- && shibumiTokens.paper !== undefined
- ? shibumiTokens.paper : renderedSurfaceColor
-
- owner: ownerWidget
- open: ownerWidget.panelOpen && weatherService !== null
- focusTarget: keyCatcher
- centerOnBar: true
- centerOnBarOffset: 1
- gap: 8
- padding: 12
- contentWidth: fittedContentWidth(300)
- contentHeight: fittedContentHeight(contentColumn.implicitHeight,
- 520)
-
- function closePanel() {
- ownerWidget.close()
- }
-
- function refresh() {
- if (weatherService && typeof weatherService.refresh === "function")
- weatherService.refresh(true)
- }
-
- function dayLabel(dateString, index) {
- if (index === 0) return "Today"
- if (index === 1) return "Tomorrow"
- const date = new Date(String(dateString || "") + "T00:00:00")
- if (isNaN(date.getTime())) return String(dateString || "")
- return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][date.getDay()]
- }
-
- function dayRange(day) {
- if (!day) return ""
- const minimum = useImperial ? day.minF : day.minC
- const maximum = useImperial ? day.maxF : day.maxC
- return minimum + "°/" + maximum + "°" + (useImperial ? "F" : "C")
- }
-
- function feelsText() {
- if (!weatherService) return ""
- const value = useImperial ? weatherService.feelsF : weatherService.feelsC
- return value === "" ? "" : value + "°" + (useImperial ? "F" : "C")
- }
-
- function windText() {
- if (!weatherService) return ""
- const value = useImperial ? weatherService.windMph : weatherService.windKmh
- return value === "" ? "" : value + (useImperial ? " mph" : " km/h")
- }
-
- onOpenChanged: if (open && weatherService && !weatherService.loaded) refresh()
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onCloseRequested: panel.closePanel()
- onTabRequested: function(direction) {
- if (panel.ownerWidget
- && typeof panel.ownerWidget.switchPanel === "function")
- panel.ownerWidget.switchPanel(direction)
- }
-
- Column {
- id: contentColumn
- width: parent.width
- spacing: 8
-
- Item {
- width: parent.width
- height: 24
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "Weather"
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 13
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- visible: panel.shellStyle === "shibumi"
- text: "\u2715"
- color: closeMouse.containsMouse
- ? panel.controlAccent : panel.controlMuted
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
- Behavior on color { ColorAnimation { duration: 120 } }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.closePanel()
- }
- }
-
- IconAction {
- icon: "close"
- tooltip: "Close"
- visible: panel.shellStyle !== "shibumi"
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- onClicked: panel.closePanel()
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Item {
- width: parent.width
- height: 36
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: panel.weatherService && panel.weatherService.loaded
- ? panel.ownerWidget.temperature + panel.ownerWidget.unitSuffix : "—"
- color: panel.controlAccent
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 26
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- width: parent.width * 0.55
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: panel.weatherService
- ? panel.weatherService.description : ""
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- horizontalAlignment: Text.AlignRight
- wrapMode: Text.WordWrap
- renderType: Text.NativeRendering
- }
- }
-
- Column {
- width: parent.width
- spacing: 4
-
- Row {
- width: parent.width
- visible: panel.weatherService
- && panel.weatherService.place !== ""
-
- Text {
- width: parent.width * 0.4
- text: "Location"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- Text {
- width: parent.width * 0.6
- text: panel.weatherService ? panel.weatherService.place : ""
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
- }
-
- Row {
- width: parent.width
- visible: panel.feelsText() !== ""
-
- Text {
- width: parent.width * 0.4
- text: "Feels like"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- Text {
- text: panel.feelsText()
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- }
-
- Row {
- width: parent.width
- visible: panel.weatherService
- && panel.weatherService.humidity !== ""
-
- Text {
- width: parent.width * 0.4
- text: "Humidity"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- Text {
- text: panel.weatherService
- ? panel.weatherService.humidity + "%" : ""
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- }
-
- Row {
- width: parent.width
- visible: panel.windText() !== ""
-
- Text {
- width: parent.width * 0.4
- text: "Wind"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- Text {
- text: panel.windText()
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: panel.dividerColor
- }
-
- Column {
- id: forecastColumn
- width: parent.width
- spacing: 5
- visible: panel.forecastDays.length > 0
-
- Text {
- text: "3-DAY FORECAST"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 10
- font.letterSpacing: 1
- renderType: Text.NativeRendering
- }
-
- Repeater {
- model: panel.forecastDays
-
- delegate: Item {
- id: forecastRow
- required property var modelData
- required property int index
-
- width: forecastColumn.width
- height: 24
-
- Text {
- width: 66
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: panel.dayLabel(forecastRow.modelData.date,
- forecastRow.index)
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.left: parent.left
- anchors.leftMargin: 76
- anchors.verticalCenter: parent.verticalCenter
- text: panel.weatherService.glyphForCode(
- forecastRow.modelData.code, false)
- color: panel.controlAccent
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 14
- renderType: Text.NativeRendering
- }
-
- Text {
- width: 76
- anchors.left: parent.left
- anchors.leftMargin: 106
- anchors.verticalCenter: parent.verticalCenter
- text: panel.dayRange(forecastRow.modelData)
- color: panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- elide: Text.ElideRight
- renderType: Text.NativeRendering
- }
-
- Text {
- width: 76
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: Math.round(Number(forecastRow.modelData.rain || 0))
- + "% rain"
- color: panel.controlMutedHigh
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 10
- horizontalAlignment: Text.AlignRight
- renderType: Text.NativeRendering
- }
- }
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- visible: forecastColumn.visible
- color: panel.dividerColor
- }
-
- Row {
- width: parent.width
- height: 28
- spacing: 6
-
- Rectangle {
- width: (parent.width - parent.spacing) / 2
- height: parent.height
- radius: panel.controlRadius
- color: panel.weatherService && panel.weatherService.refreshing
- ? panel.controlActiveFillColor
- : refreshMouse.containsMouse
- ? panel.controlPrimaryHoverColor : panel.controlAccent
-
- Text {
- anchors.centerIn: parent
- text: panel.weatherService && panel.weatherService.refreshing
- ? "Refreshing\u2026" : "Refresh"
- color: panel.primaryTextColor
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: refreshMouse
- anchors.fill: parent
- enabled: !(panel.weatherService
- && panel.weatherService.refreshing)
- hoverEnabled: true
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: panel.refresh()
- }
- }
-
- Rectangle {
- width: (parent.width - parent.spacing) / 2
- height: parent.height
- radius: panel.controlRadius
- color: unitMouse.containsMouse
- ? panel.controlHoverFillColor : panel.controlFillColor
- border.width: panel.controlBorderWidth
- border.color: unitMouse.containsMouse
- ? panel.controlHoverBorderColor : panel.controlBorderColor
-
- Text {
- anchors.centerIn: parent
- text: panel.useImperial ? "metric" : "imperial"
- color: unitMouse.containsMouse
- ? panel.controlAccent : panel.controlForeground
- font.family: panel.bar ? panel.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- id: unitMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: panel.ownerWidget.toggleUnit()
- }
- }
- }
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: panel.controlRadius
- foreground: panel.bar ? panel.bar.foreground : Commons.Color.foreground
- accent: panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: panel
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-}
diff --git a/widgets/WeatherWidget.qml b/widgets/WeatherWidget.qml
deleted file mode 100644
index 0b53b4b..0000000
--- a/widgets/WeatherWidget.qml
+++ /dev/null
@@ -1,127 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.BarWidget {
- id: root
-
- moduleName: "omarchy.weather"
- required property var weatherService
- property url panelSource: Qt.resolvedUrl("WeatherPanel.qml")
- property bool panelOpen: false
-
- readonly property var panelItem: panelLoader.item
- readonly property bool panelLoaded: panelItem !== null
- readonly property bool opened: panelOpen
- readonly property var stateService: bar && bar.shell
- && typeof bar.shell.serviceFor === "function"
- ? bar.shell.serviceFor("hancore.shibumi.state") : null
- readonly property string unitSetting: String(setting("unit", "") || "").toLowerCase()
- readonly property bool useImperial: unitSetting === "imperial"
- || (unitSetting !== "metric" && /^en[_-]US/.test(Qt.locale().name))
- readonly property string temperature: weatherService
- ? (useImperial ? weatherService.tempF : weatherService.tempC) : ""
- readonly property string unitSuffix: useImperial ? "°F" : "°C"
- readonly property real iconSlotWidth: 20
- readonly property string tooltipText: {
- if (!weatherService) return "Weather unavailable"
- if (!weatherService.loaded)
- return weatherService.unavailable ? "Weather offline" : "Loading weather"
- const details = (weatherService.place ? weatherService.place + " · " : "")
- + temperature + unitSuffix
- + (weatherService.description ? " / " + weatherService.description : "")
- return weatherService.unavailable ? "Weather stale · " + details : details
- }
-
- implicitWidth: bar && bar.vertical ? bar.barSize : iconSlotWidth
- implicitHeight: bar ? bar.barSize : Commons.Style.space(35)
-
- function refresh() {
- if (weatherService) weatherService.refresh(true)
- }
-
- function syncPanelLoader() {
- panelLoader.source = ""
- if (!panelOpen || !weatherService || !String(panelSource)) return
- panelLoader.setSource(panelSource, {
- ownerWidget: root,
- weatherService: root.weatherService,
- anchorItem: root,
- bar: root.bar
- })
- }
-
- function open() {
- if (!weatherService || !String(panelSource)) return false
- panelOpen = true
- return true
- }
-
- function close() {
- panelOpen = false
- }
-
- function toggle() {
- if (panelOpen) close()
- else return open()
- return true
- }
-
- function togglePanel() {
- return toggle()
- }
-
- function toggleUnit() {
- return stateService && typeof stateService.setWidgetSetting === "function"
- ? stateService.setWidgetSetting("G8", moduleName, "unit",
- useImperial ? "metric" : "imperial") : false
- }
-
- function switchPanel(direction) {
- return bar && typeof bar.switchPanelFrom === "function"
- ? bar.switchPanelFrom(root, direction) : false
- }
-
- function ownsPanelWidget(item) {
- return item === root || item === panelItem
- }
-
- onPanelOpenChanged: syncPanelLoader()
- onPanelSourceChanged: if (panelOpen) syncPanelLoader()
- onWeatherServiceChanged: if (panelOpen) syncPanelLoader()
- onBarChanged: if (panelOpen) syncPanelLoader()
-
- Text {
- id: weatherIcon
- anchors.centerIn: parent
- text: root.weatherService && root.weatherService.loaded
- ? root.weatherService.icon
- : root.weatherService && root.weatherService.unavailable ? "?" : "·"
- color: root.weatherService && root.weatherService.unavailable
- && !root.weatherService.loaded && root.bar
- ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.4)
- : root.bar ? root.bar.foreground : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 14
- renderType: Text.NativeRendering
- }
-
- Ui.WidgetButton {
- anchors.fill: parent
- bar: root.bar
- text: " "
- keepSpace: true
- horizontalMargin: 0
- verticalPadding: 0
- tooltipText: root.tooltipText
- onPressed: function(button) {
- if (button === Qt.RightButton || button === Qt.MiddleButton) root.refresh()
- else root.togglePanel()
- }
- }
-
- Loader { id: panelLoader }
-}
diff --git a/widgets/WidgetRegistry.qml b/widgets/WidgetRegistry.qml
deleted file mode 100644
index 77678f8..0000000
--- a/widgets/WidgetRegistry.qml
+++ /dev/null
@@ -1,131 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import "../menu" as Menu
-
-Item {
- id: root
-
- property var bar: null
-
- visible: false
- width: 0
- height: 0
-
- function componentFor(moduleName) {
- if (moduleName === "hancore.shibumi.bar") return menuWidget
- if (moduleName === "omarchy.workspaces"
- || moduleName === "hancore.shibumi.workspaces") return workspaceWidget
- if (moduleName === "hancore.shibumi.memory") return memoryWidget
- if (moduleName === "hancore.shibumi.cpu") return cpuWidget
- if (moduleName === "hancore.shibumi.audio") return audioWidget
- if (moduleName === "hancore.shibumi.ai") return aiUsageWidget
- if (moduleName === "hancore.shibumi.status") return statusWidget
- if (moduleName === "hancore.shibumi.media") return mediaWidget
- if (moduleName === "hancore.shibumi.quick-access") return quickAccessWidget
- if (moduleName === "hancore.shibumi.network") return networkWidget
- if (moduleName === "hancore.shibumi.brightness") return brightnessWidget
- if (moduleName === "hancore.shibumi.battery") return batteryWidget
- if (moduleName === "hancore.shibumi.power-profile") return powerProfileWidget
- if (moduleName === "hancore.shibumi.bluetooth") return bluetoothWidget
- if (moduleName === "hancore.shibumi.center") return centerWidget
- if (moduleName === "omarchy.clock") return clockWidget
- return null
- }
-
- Component {
- id: menuWidget
-
- Menu.BarWidget { bar: root.bar }
- }
-
- Component {
- id: workspaceWidget
-
- WorkspaceWidget { bar: root.bar }
- }
-
- Component {
- id: memoryWidget
-
- MemoryWidget { bar: root.bar }
- }
-
- Component {
- id: cpuWidget
-
- CpuWidget { bar: root.bar }
- }
-
- Component {
- id: statusWidget
-
- StatusWidget { bar: root.bar }
- }
-
- Component {
- id: audioWidget
-
- AudioWidget { bar: root.bar }
- }
-
- Component {
- id: aiUsageWidget
-
- AiUsageWidget { bar: root.bar }
- }
-
- Component {
- id: mediaWidget
-
- MediaWidget { bar: root.bar }
- }
-
- Component {
- id: quickAccessWidget
-
- QuickAccessWidget { bar: root.bar }
- }
-
- Component {
- id: networkWidget
-
- NetworkWidget { bar: root.bar }
- }
-
- Component {
- id: brightnessWidget
-
- BrightnessWidget { bar: root.bar }
- }
-
- Component {
- id: batteryWidget
-
- BatteryWidget { bar: root.bar }
- }
-
- Component {
- id: powerProfileWidget
-
- PowerProfileWidget { bar: root.bar }
- }
-
- Component {
- id: bluetoothWidget
-
- BluetoothWidget { bar: root.bar }
- }
-
- Component {
- id: centerWidget
-
- CenterWidget { bar: root.bar }
- }
-
- Component {
- id: clockWidget
-
- ClockWidget { bar: root.bar }
- }
-}
diff --git a/widgets/WorkspacePanel.qml b/widgets/WorkspacePanel.qml
deleted file mode 100644
index c757fa2..0000000
--- a/widgets/WorkspacePanel.qml
+++ /dev/null
@@ -1,60 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-ShibumiPanel {
- id: panel
-
- required property var ownerWidget
- required property var workspaceService
- property int cursorIndex: workspaceService.entries.length > 0 ? 0 : -1
- readonly property var rows: workspaceService.entries
- readonly property int renderedRowCount: panelContent.renderedRowCount
- readonly property bool controlsFitWidth: panelContent.controlsFitWidth
-
- owner: ownerWidget
- open: ownerWidget.opened
- focusTarget: panelContent.focusTarget
- gap: 8
- padding: 12
- contentWidth: fittedContentWidth(240)
- contentHeight: fittedContentHeight(panelContent.implicitHeight)
-
- function focusedRowIndex() {
- for (let index = 0; index < rows.length; index++) {
- if (rows[index] && rows[index].focused === true) return index
- }
- return rows.length > 0 ? 0 : -1
- }
-
- function moveCursor(delta) {
- if (rows.length === 0) {
- cursorIndex = -1
- return false
- }
- cursorIndex = (Math.max(0, cursorIndex) + delta + rows.length) % rows.length
- panelContent.positionAt(cursorIndex)
- return true
- }
-
- function activateCursor() {
- if (cursorIndex < 0 || cursorIndex >= rows.length) return false
- const focused = workspaceService.focusWorkspace(rows[cursorIndex].id)
- if (focused) ownerWidget.close()
- return focused
- }
-
- onRowsChanged: {
- if (rows.length === 0) cursorIndex = -1
- else cursorIndex = Math.max(0, Math.min(cursorIndex, rows.length - 1))
- }
- onOpenChanged: if (open) cursorIndex = focusedRowIndex()
-
- WorkspacePanelContent {
- id: panelContent
- anchors.fill: parent
- controller: panel
- }
-}
diff --git a/widgets/WorkspacePanelContent.qml b/widgets/WorkspacePanelContent.qml
deleted file mode 100644
index 5bf624d..0000000
--- a/widgets/WorkspacePanelContent.qml
+++ /dev/null
@@ -1,205 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Item {
- id: root
-
- required property var controller
- readonly property Item focusTarget: keyCatcher
- readonly property int renderedRowCount: workspaceList.count
- readonly property bool controlsFitWidth: true
- readonly property var bar: controller.bar
- readonly property var workspaceService: controller.workspaceService
-
- // Keep the V1 71 px one-row content contract independent of whether this
- // component is hosted by a PanelWindow or an offscreen test Item.
- implicitHeight: 24 + 8 + 1 + 8 + workspaceList.height
-
- function positionAt(index) {
- if (index >= 0 && index < workspaceList.count)
- workspaceList.positionViewAtIndex(index, ListView.Contain)
- }
-
- Ui.PanelKeyCatcher {
- id: keyCatcher
- anchors.fill: parent
- onMoveRequested: function(_dx, dy) {
- if (dy !== 0) root.controller.moveCursor(dy)
- }
- onActivateRequested: root.controller.activateCursor()
- onCloseRequested: root.controller.ownerWidget.close()
- onTabRequested: function(direction) {
- root.controller.ownerWidget.switchPanel(direction)
- }
-
- Column {
- id: panelColumn
- width: parent.width
- spacing: 8
-
- Item {
- width: parent.width
- height: 24
-
- Text {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- text: "Workspaces"
- color: root.bar ? root.bar.foreground : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 13
- font.letterSpacing: 2
- font.weight: Font.Medium
- renderType: Text.NativeRendering
- }
-
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- visible: root.controller.shellStyle === "shibumi"
- text: "\u2715"
- color: closeMouse.containsMouse
- ? root.controller.controlAccent : root.controller.controlMuted
- font.family: root.bar ? root.bar.fontFamily
- : Commons.Style.font.family
- font.pixelSize: 12
- renderType: Text.NativeRendering
- Behavior on color { ColorAnimation { duration: 120 } }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: root.controller.ownerWidget.close()
- }
- }
-
- IconAction {
- id: closeButton
- icon: "close"
- tooltip: "Close"
- visible: root.controller.shellStyle !== "shibumi"
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- onClicked: root.controller.ownerWidget.close()
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: root.bar ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g,
- root.bar.foreground.b, 0.18) : Commons.Color.popups.border
- }
-
- ListView {
- id: workspaceList
- width: parent.width
- readonly property int visibleRows: Math.min(8, count)
- height: visibleRows > 0
- ? visibleRows * 30 + Math.max(0, visibleRows - 1) * 4 : 0
- model: root.controller.rows
- clip: true
- boundsBehavior: Flickable.StopAtBounds
- spacing: 4
-
- delegate: Rectangle {
- id: row
- required property var modelData
- required property int index
- readonly property bool hasCursor: root.controller.cursorIndex === index
- readonly property bool current: modelData.focused === true
- width: ListView.view.width
- height: 30
- radius: root.controller.controlRadius
- color: current ? root.controller.controlActiveFillColor
- : hasCursor ? root.controller.controlHoverFillColor
- : root.controller.controlFillColor
- border.width: root.controller.controlBorderWidth
- border.color: current || hasCursor ? root.controller.controlAccent
- : root.controller.controlBorderColor
-
- Behavior on color { ColorAnimation { duration: 120 } }
-
- Text {
- id: workspaceLabel
- anchors.left: parent.left
- anchors.right: windowCountLabel.left
- anchors.leftMargin: 10
- anchors.rightMargin: 10
- anchors.verticalCenter: parent.verticalCenter
- text: "Workspace " + row.modelData.id
- color: row.current || row.hasCursor ? root.controller.controlAccent
- : root.bar ? root.bar.foreground : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 12
- font.weight: row.modelData.focused ? Font.Medium : Font.Normal
- renderType: Text.NativeRendering
- elide: Text.ElideRight
- }
-
- Text {
- id: windowCountLabel
- anchors.right: parent.right
- anchors.rightMargin: 10
- anchors.verticalCenter: parent.verticalCenter
- text: String(row.modelData.windowCount)
- color: root.controller.controlMutedHigh
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 10
- renderType: Text.NativeRendering
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: root.controller.cursorIndex = row.index
- onClicked: {
- root.controller.cursorIndex = row.index
- root.controller.activateCursor()
- }
- }
- }
- }
- }
- }
-
- component IconAction: Ui.CursorSurface {
- id: action
- property string icon: ""
- property string tooltip: ""
- signal clicked()
- implicitWidth: Commons.Style.space(28)
- implicitHeight: Commons.Style.space(28)
- radius: root.controller.controlRadius
- foreground: root.bar ? root.bar.foreground : Commons.Color.foreground
- accent: root.bar ? root.bar.urgent : Commons.Color.accent
-
- IconText {
- anchors.centerIn: parent
- text: action.icon
- color: action.foreground
- font.pixelSize: Commons.Style.font.body
- }
-
- MouseArea {
- id: actionMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onContainsMouseChanged: action.hasCursor = containsMouse
- onClicked: action.clicked()
- }
-
- ShibumiPanelToolTip {
- panel: root.controller
- visible: action.tooltip !== "" && actionMouse.containsMouse
- text: action.tooltip
- }
- }
-}
diff --git a/widgets/WorkspaceWidget.qml b/widgets/WorkspaceWidget.qml
deleted file mode 100644
index 12142fa..0000000
--- a/widgets/WorkspaceWidget.qml
+++ /dev/null
@@ -1,319 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import QtQuick
-import qs.Commons as Commons
-import qs.Ui as Ui
-
-Ui.Panel {
- id: root
-
- moduleName: "omarchy.workspaces"
- manageIpc: false
- property url panelSource: Qt.resolvedUrl("WorkspacePanel.qml")
- readonly property var workspaceService: bar ? bar.workspaceService : null
- readonly property var stateService: bar && bar.shell
- && typeof bar.shell.serviceFor === "function"
- ? bar.shell.serviceFor("hancore.shibumi.state") : null
- readonly property var tokens: bar ? bar.visualTokens : null
- readonly property var workspaceIds: workspaceService
- ? workspaceService.visibleWorkspaceIds : []
- readonly property string workspaceStyle: workspaceService
- ? workspaceService.style : "default"
- readonly property color pacmanActiveColor:
- paletteColor("color03", bar ? bar.urgent : Commons.Color.accent)
- readonly property color pacmanBarColor: bar
- ? bar.foreground : Commons.Color.foreground
- readonly property color pacmanOccupiedColor: pacmanBarColor
- readonly property color pacmanEmptyColor: pacmanBarColor
- readonly property color pacmanHoverColor: pacmanBarColor
- readonly property int renderedWorkspaceCount: workspaceRepeater.count
- readonly property bool panelLoaded: panelLoader.item !== null
- readonly property bool panelLoaderReady: panelLoader.item
- ? panelLoader.item.ready === true : false
- readonly property int workspacePadding: tokens
- ? tokens.workspacePillPadding(workspaceStyle) : 4
- readonly property int workspaceGap: workspaceStyle === "rings"
- || workspaceStyle === "aurora" ? Commons.Style.space(3)
- : workspaceStyle === "pacman" ? Commons.Style.space(2)
- : tokens ? tokens.contentGap : Commons.Style.space(5)
- readonly property real workspaceContentWidth: {
- void(workspaceIds)
- void(workspaceStyle)
- var total = 0
- var visibleCount = 0
- for (var i = 0; i < workspaceRepeater.count; i++) {
- var item = workspaceRepeater.itemAt(i)
- if (!item || item.implicitWidth <= 0) continue
- total += item.implicitWidth
- visibleCount++
- }
- return total + Math.max(0, visibleCount - 1) * workspaceGap
- }
-
- implicitWidth: bar && bar.vertical ? bar.barSize : workspaceSurface.implicitWidth
- implicitHeight: bar && bar.vertical
- ? workspaceSurface.implicitHeight : bar ? bar.barSize : 28
-
- function activateWorkspace(id) {
- return workspaceService ? workspaceService.focusWorkspace(id) : false
- }
-
- function paletteColor(id, fallback) {
- return stateService && typeof stateService.paletteColor === "function"
- ? stateService.paletteColor(id) : fallback
- }
-
- function workspaceState(id) {
- return workspaceService
- ? workspaceService.workspaceState(id)
- : ({ id: Number(id) || 0, focused: false, occupied: false, windowCount: 0 })
- }
-
- function workspaceTooltip(id) {
- const info = workspaceState(id)
- const windows = info.windowCount === 1 ? "1 window"
- : info.windowCount + " windows"
- return "Workspace " + info.id + " · " + windows
- + " · Right-click for workspace panel"
- }
-
- function syncPanelLoader() {
- if (!opened) {
- panelLoader.source = ""
- return
- }
- panelLoader.setSource(panelSource, {
- anchorItem: workspaceSurface,
- bar: root.bar,
- ownerWidget: root,
- workspaceService: root.workspaceService
- })
- }
-
- onOpenedChanged: syncPanelLoader()
-
- Item {
- id: workspaceSurface
- anchors.centerIn: parent
- implicitWidth: root.workspaceContentWidth + 2 * root.workspacePadding
- implicitHeight: root.bar ? root.bar.barSize : Commons.Style.space(28)
- width: implicitWidth
- height: implicitHeight
-
- PillSurface {
- bar: root.bar
- anchors.fill: parent
- anchors.topMargin: Math.round((parent.height - root.tokens.pillHeight) / 2)
- anchors.bottomMargin: Math.round((parent.height - root.tokens.pillHeight) / 2)
- }
-
- MouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.RightButton
- cursorShape: Qt.PointingHandCursor
- onClicked: root.toggle()
- }
-
- Row {
- id: workspaceRow
- anchors.centerIn: parent
- spacing: root.workspaceGap
- width: root.workspaceContentWidth
-
- Repeater {
- id: workspaceRepeater
- model: root.workspaceIds
-
- delegate: Item {
- id: cell
- required property int modelData
- readonly property var workspaceInfo: root.workspaceState(modelData)
- readonly property bool focused: workspaceInfo.focused === true
- readonly property bool occupied: workspaceInfo.occupied === true
- readonly property bool empty: !focused && !occupied
- readonly property int numberWidth: Commons.Style.space(20)
-
- implicitWidth: root.workspaceStyle === "numbers" ? Commons.Style.space(22)
- : root.workspaceStyle === "kanji" ? Commons.Style.space(22)
- : root.workspaceStyle === "magic"
- ? Commons.Style.space(focused ? 20 : 18)
- : root.workspaceStyle === "rings" ? Commons.Style.space(19)
- : root.workspaceStyle === "aurora"
- ? Commons.Style.space(focused ? 38 : 20)
- : root.workspaceStyle === "pacman"
- ? Commons.Style.space(22)
- : Commons.Style.space(focused ? 32 : 16)
- implicitHeight: workspaceSurface.height
-
- Behavior on implicitWidth {
- NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
- }
- Behavior on scale { NumberAnimation { duration: 120 } }
-
- Rectangle {
- visible: root.workspaceStyle === "default"
- anchors.centerIn: parent
- width: Commons.Style.space(cell.focused ? 34 : 16)
- height: Commons.Style.space(16)
- radius: height / 2
- color: root.bar ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, cell.focused ? 0.20 : cell.occupied ? 0.18 : 0.06)
- : "transparent"
- Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
- }
-
- Rectangle {
- visible: root.workspaceStyle === "default"
- anchors.centerIn: parent
- width: Commons.Style.space(cell.focused ? 26 : 8)
- height: Commons.Style.space(8)
- radius: height / 2
- color: root.bar ? (cell.focused || cell.occupied
- ? root.bar.urgent : Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, 0.25)) : Commons.Color.accent
- Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
- }
-
- Rectangle {
- visible: root.workspaceStyle === "numbers"
- anchors.centerIn: parent
- width: cell.numberWidth
- height: Commons.Style.space(20)
- radius: root.tokens.presentation.radius === "small"
- ? Commons.Style.space(5) : height / 2
- color: root.bar ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, cell.focused ? 0.30 : cell.occupied ? 0.12 : 0.04)
- : "transparent"
-
- Text {
- anchors.centerIn: parent
- text: cell.modelData
- color: root.bar ? (cell.focused
- ? Qt.lighter(root.bar.urgent, 1.3)
- : Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, cell.occupied ? 0.5 : 0.28))
- : Commons.Color.foreground
- font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: cell.focused
- ? Commons.Style.font.subtitle : root.tokens.labelSize
- font.weight: cell.focused ? Font.Bold : Font.Normal
- renderType: Text.NativeRendering
- }
- }
-
- Text {
- visible: root.workspaceStyle === "magic"
- anchors.centerIn: parent
- anchors.verticalCenterOffset: cell.focused ? 0 : 1
- text: cell.focused ? "✦" : cell.occupied ? "✧" : "·"
- color: root.bar ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, cell.focused ? 1 : cell.occupied ? 0.7 : 0.3)
- : Commons.Color.foreground
- font.family: "Adwaita Mono"
- font.pixelSize: Commons.Style.space(cell.focused ? 22 : 18)
- renderType: Text.NativeRendering
-
- Behavior on color { ColorAnimation { duration: 200 } }
- }
-
- Text {
- visible: root.workspaceStyle === "kanji"
- anchors.centerIn: parent
- text: cell.modelData >= 1 && cell.modelData <= 10
- ? ["一", "二", "三", "四", "五",
- "六", "七", "八", "九", "十"][cell.modelData - 1]
- : String(cell.modelData)
- color: root.bar ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b, cell.focused ? 1 : cell.occupied ? 0.7 : 0.3)
- : Commons.Color.foreground
- font.family: "Noto Sans CJK JP"
- font.pixelSize: Commons.Style.space(cell.focused ? 15 : 13)
- font.weight: Font.Normal
- renderType: Text.NativeRendering
-
- Behavior on color { ColorAnimation { duration: 200 } }
- }
-
- Rectangle {
- id: ringsMark
- visible: root.workspaceStyle === "rings"
- anchors.centerIn: parent
- width: Commons.Style.space(cell.focused ? 13 : 12)
- height: width
- radius: width / 2
- color: cell.focused && root.bar ? root.bar.urgent : "transparent"
- border.width: cell.focused ? 0 : 1
- border.color: root.bar ? root.bar.urgent : Commons.Color.accent
- opacity: cellPointer.containsMouse ? 0.4
- : cell.focused ? 1 : cell.occupied ? 0.45 : 0.10
- antialiasing: true
-
- Behavior on width {
- NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
- }
- Behavior on opacity {
- NumberAnimation { duration: 300; easing.type: Easing.InOutCubic }
- }
- }
-
- Rectangle {
- visible: root.workspaceStyle === "aurora"
- anchors.centerIn: parent
- width: Commons.Style.space(cell.focused ? 36 : 18)
- height: Commons.Style.space(16)
- radius: height / 2
- antialiasing: true
- color: root.bar ? Qt.rgba(root.bar.urgent.r, root.bar.urgent.g,
- root.bar.urgent.b,
- cell.empty ? (cellPointer.containsMouse ? 0.25 : 0.10)
- : (cellPointer.containsMouse ? 0.55 : 1))
- : Commons.Color.accent
-
- Behavior on width {
- NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
- }
- Behavior on color { ColorAnimation { duration: 250 } }
- }
-
- PacmanWorkspaceMarker {
- visible: root.workspaceStyle === "pacman"
- anchors.centerIn: parent
- focused: cell.focused
- occupied: cell.occupied
- hovered: cellPointer.containsMouse
- activeColor: root.pacmanActiveColor
- occupiedColor: root.pacmanOccupiedColor
- emptyColor: root.pacmanEmptyColor
- hoverColor: root.pacmanHoverColor
- }
-
- MouseArea {
- id: cellPointer
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: {
- cell.scale = root.workspaceStyle === "rings" ? 1.06
- : root.workspaceStyle === "aurora"
- || root.workspaceStyle === "pacman" ? 1.03 : 1.15
- if (root.bar) root.bar.showTooltip(
- workspaceSurface, root.workspaceTooltip(cell.modelData))
- }
- onExited: {
- cell.scale = 1
- if (root.bar) root.bar.hideTooltip(workspaceSurface)
- }
- onClicked: function(mouse) {
- if (root.bar) root.bar.hideTooltip(workspaceSurface)
- if (mouse.button === Qt.RightButton) root.toggle()
- else root.activateWorkspace(cell.modelData)
- }
- }
- }
- }
- }
- }
-
- Loader { id: panelLoader }
-}
diff --git a/widgets/lan.svg b/widgets/lan.svg
deleted file mode 100644
index d697199..0000000
--- a/widgets/lan.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-