import QtQuick import Quickshell import Quickshell.Io import qs.Commons import qs.Ui BarWidget { id: root moduleName: "omarchy.indicators" readonly property int indicatorSlotExtent: Style.space(22) readonly property var defaultIndicatorEntries: [ "Dnd", "Reminder", "NightLight", "StayAwake", "ScreenRecording", "Dictation" ] readonly property int inactiveSlotExtent: indicatorEntries.length * indicatorSlotExtent readonly property var indicatorEntries: indicatorEntriesFromSettings(settings) property var activeIndicatorIds: [] property var indicatorActiveStates: ({}) property bool indicatorAreaHovered: false property bool indicatorItemHovered: false readonly property bool alwaysShowIndicators: setting("alwaysShow", false) === true readonly property bool revealInactiveIndicators: alwaysShowIndicators || indicatorAreaHovered || indicatorItemHovered signal refreshRequested() ListModel { id: activeIndicatorModel } function entryId(entry) { if (typeof entry === "string") return entry if (Util.isPlainObject(entry)) { var id = entry["id"] if (id !== undefined && id !== null && String(id) !== "") return String(id) } return "" } function entrySettings(entry) { if (!Util.isPlainObject(entry)) return {} var copy = {} for (var key in entry) { if (key === "id") continue copy[key] = entry[key] } return copy } function indicatorEntriesFromSettings(settings) { var source = defaultIndicatorEntries if (settings.items && typeof settings.items.length === "number" && settings.items.length > 0) source = settings.items else if (settings.indicators && typeof settings.indicators.length === "number" && settings.indicators.length > 0) source = settings.indicators var result = [] for (var i = 0; i < source.length; i++) { var item = source[i] if (typeof item !== "string" && item !== null && typeof item === "object") { try { item = JSON.parse(JSON.stringify(item)) } catch (error) { } } var id = entryId(item) if (id !== "") result.push(item) } return result } function setIndicatorAreaHovered(hovered) { indicatorAreaHovered = hovered if (hovered) indicatorHideTimer.stop() else indicatorHideTimer.restart() } function setIndicatorItemHovered(hovered) { if (hovered) { indicatorItemHovered = true indicatorHideTimer.stop() } else { indicatorHideTimer.restart() } } function hasIndicatorId(id) { for (var i = 0; i < indicatorEntries.length; i++) { if (entryId(indicatorEntries[i]) === id) return true } return false } function entryForId(id) { for (var i = 0; i < indicatorEntries.length; i++) { var entry = indicatorEntries[i] if (entryId(entry) === id) return entry } return { id: id } } function activeModelIndex(id) { for (var i = 0; i < activeIndicatorModel.count; i++) { if (activeIndicatorModel.get(i).activeId === id) return i } return -1 } function copyActiveStates() { var states = {} for (var id in indicatorActiveStates) { if (indicatorActiveStates[id] === true) states[id] = true } return states } function orderedActiveIds(states, preferredOrder) { var ids = [] for (var i = 0; i < preferredOrder.length; i++) { var id = preferredOrder[i] if (ids.indexOf(id) === -1 && hasIndicatorId(id) && states[id] === true) ids.push(id) } return ids } function syncActiveIndicatorModel() { for (var i = activeIndicatorModel.count - 1; i >= 0; i--) { if (activeIndicatorIds.indexOf(activeIndicatorModel.get(i).activeId) === -1) activeIndicatorModel.remove(i) } for (var j = 0; j < activeIndicatorIds.length; j++) { var id = activeIndicatorIds[j] var index = activeModelIndex(id) if (index === -1) activeIndicatorModel.insert(j, { activeId: id }) else if (index !== j) activeIndicatorModel.move(index, j, 1) } } function setIndicatorActive(entry, active) { var id = entryId(entry) if (id === "") return var states = copyActiveStates() if (active) states[id] = true else delete states[id] indicatorActiveStates = states var ids = orderedActiveIds(states, activeIndicatorIds) if (active && ids.indexOf(id) === -1 && hasIndicatorId(id)) ids.push(id) activeIndicatorIds = ids syncActiveIndicatorModel() } function syncActiveIndicatorOrder() { activeIndicatorIds = orderedActiveIds(indicatorActiveStates, activeIndicatorIds) syncActiveIndicatorModel() } onIndicatorEntriesChanged: syncActiveIndicatorOrder() implicitWidth: root.vertical ? verticalIndicators.implicitWidth : horizontalIndicators.implicitWidth implicitHeight: root.vertical ? verticalIndicators.implicitHeight : horizontalIndicators.implicitHeight IpcHandler { target: "omarchy.indicators" function refresh(): void { root.refreshRequested() } } Timer { id: indicatorHideTimer interval: 120 onTriggered: { if (!root.indicatorAreaHovered) root.indicatorItemHovered = false } } Timer { interval: 2000 running: true repeat: true triggeredOnStart: true onTriggered: root.refreshRequested() } Row { id: horizontalIndicators visible: !root.vertical spacing: 0 HoverHandler { onHoveredChanged: root.setIndicatorAreaHovered(hovered) } ActiveIndicatorBlock { indicatorsModule: root indicatorModel: activeIndicatorModel horizontal: true reportActiveState: !root.vertical } Item { id: inactiveHorizontalArea implicitWidth: Math.max(inactiveHorizontalBlock.implicitWidth, root.inactiveSlotExtent) implicitHeight: Math.max(inactiveHorizontalBlock.implicitHeight, root.barSize) width: implicitWidth height: implicitHeight IndicatorBlock { id: inactiveHorizontalBlock anchors.fill: parent indicatorsModule: root indicatorEntries: root.indicatorEntries indicatorBlock: "inactive" horizontal: true reportActiveState: !root.vertical } HoverHandler { onHoveredChanged: root.setIndicatorAreaHovered(hovered) } } } Column { id: verticalIndicators visible: root.vertical spacing: 0 HoverHandler { onHoveredChanged: root.setIndicatorAreaHovered(hovered) } ActiveIndicatorBlock { indicatorsModule: root indicatorModel: activeIndicatorModel horizontal: false reportActiveState: root.vertical } Item { id: inactiveVerticalArea implicitWidth: Math.max(inactiveVerticalBlock.implicitWidth, root.barSize) implicitHeight: Math.max(inactiveVerticalBlock.implicitHeight, root.inactiveSlotExtent) width: implicitWidth height: implicitHeight IndicatorBlock { id: inactiveVerticalBlock anchors.fill: parent indicatorsModule: root indicatorEntries: root.indicatorEntries indicatorBlock: "inactive" horizontal: false reportActiveState: root.vertical } HoverHandler { onHoveredChanged: root.setIndicatorAreaHovered(hovered) } } } HoverHandler { onHoveredChanged: root.setIndicatorAreaHovered(hovered) } component ActiveIndicatorBlock: Item { id: activeIndicatorBlockRoot property var indicatorModel: null property var indicatorsModule: null property bool horizontal: true property bool reportActiveState: false implicitWidth: blockLoader.item ? blockLoader.item.childrenRect.width : 0 implicitHeight: blockLoader.item ? blockLoader.item.childrenRect.height : 0 width: implicitWidth height: implicitHeight Loader { id: blockLoader anchors.fill: parent sourceComponent: activeIndicatorBlockRoot.horizontal ? horizontalActiveIndicatorBlock : verticalActiveIndicatorBlock } Component { id: horizontalActiveIndicatorBlock Row { spacing: 0 Repeater { model: activeIndicatorBlockRoot.indicatorModel IndicatorLoader { required property string activeId indicatorsModule: activeIndicatorBlockRoot.indicatorsModule entry: activeIndicatorBlockRoot.indicatorsModule.entryForId(activeId) indicatorBlock: "active" reportActiveState: activeIndicatorBlockRoot.reportActiveState } } } } Component { id: verticalActiveIndicatorBlock Column { spacing: 0 Repeater { model: activeIndicatorBlockRoot.indicatorModel IndicatorLoader { required property string activeId indicatorsModule: activeIndicatorBlockRoot.indicatorsModule entry: activeIndicatorBlockRoot.indicatorsModule.entryForId(activeId) indicatorBlock: "active" reportActiveState: activeIndicatorBlockRoot.reportActiveState } } } } } component IndicatorBlock: Item { id: indicatorBlockRoot property var indicatorEntries: [] property var indicatorsModule: null property string indicatorBlock: "active" property bool horizontal: true property bool reportActiveState: false implicitWidth: blockLoader.item ? blockLoader.item.childrenRect.width : 0 implicitHeight: blockLoader.item ? blockLoader.item.childrenRect.height : 0 width: implicitWidth height: implicitHeight Loader { id: blockLoader anchors.fill: parent sourceComponent: indicatorBlockRoot.horizontal ? horizontalIndicatorBlock : verticalIndicatorBlock } Component { id: horizontalIndicatorBlock Row { spacing: 0 Repeater { model: indicatorBlockRoot.indicatorEntries IndicatorLoader { required property var modelData indicatorsModule: indicatorBlockRoot.indicatorsModule entry: modelData indicatorBlock: indicatorBlockRoot.indicatorBlock reportActiveState: indicatorBlockRoot.reportActiveState } } } } Component { id: verticalIndicatorBlock Column { spacing: 0 Repeater { model: indicatorBlockRoot.indicatorEntries IndicatorLoader { required property var modelData indicatorsModule: indicatorBlockRoot.indicatorsModule entry: modelData indicatorBlock: indicatorBlockRoot.indicatorBlock reportActiveState: indicatorBlockRoot.reportActiveState } } } } } component IndicatorLoader: Item { id: indicatorSlot required property var entry property var indicatorsModule: null required property string indicatorBlock property bool reportActiveState: false property bool activeStateObserved: false readonly property string indicatorId: root.entryId(entry) readonly property var indicatorSettings: root.entrySettings(entry) implicitWidth: indicatorSource.item && indicatorSource.item.visible ? indicatorSource.item.implicitWidth : 0 implicitHeight: indicatorSource.item && indicatorSource.item.visible ? indicatorSource.item.implicitHeight : 0 width: implicitWidth height: implicitHeight onEntryChanged: { activeStateObserved = false injectProps() syncActiveState() } onIndicatorBlockChanged: injectProps() onIndicatorSettingsChanged: injectProps() onIndicatorsModuleChanged: syncActiveState() onReportActiveStateChanged: syncActiveState() Loader { id: indicatorSource anchors.fill: parent source: indicatorSlot.indicatorId ? Qt.resolvedUrl("../indicators/" + indicatorSlot.indicatorId + ".qml") : "" onLoaded: { indicatorSlot.injectProps() indicatorSlot.syncActiveState() } onStatusChanged: if (status === Loader.Error) console.warn("Indicator loader error", indicatorSlot.indicatorId, source) } Connections { target: indicatorSource.item ignoreUnknownSignals: true function onActiveChanged() { indicatorSlot.syncActiveState() } } function injectProps() { var target = indicatorSource.item if (!target) return if ("bar" in target) target.bar = root.bar if ("moduleName" in target) target.moduleName = indicatorId if ("settings" in target) target.settings = indicatorSettings if ("indicatorBlock" in target) target.indicatorBlock = indicatorBlock if ("indicatorHost" in target) target.indicatorHost = root if ("activeOverride" in target) target.activeOverride = indicatorBlock === "active" ? true : null } function syncActiveState() { if (!reportActiveState || !indicatorsModule || !indicatorsModule.setIndicatorActive) return var active = !!indicatorSource.item && indicatorSource.item.active === true if (indicatorBlock === "active") { if (active) activeStateObserved = true else if (!activeStateObserved) return } indicatorsModule.setIndicatorActive(entry, active) } } }