Files
arthur-os/default/quickshell/omarchy-shell/plugins/bar/widgets/controlCenter.qml
T

462 lines
13 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Bluetooth
import Quickshell.Io
import Quickshell.Services.Pipewire
import "../common" as Common
Item {
id: root
property QtObject bar: null
property string moduleName: "controlCenter"
property var settings: ({})
property bool popupOpen: false
function closePopout() { popupOpen = false }
function run(command) {
if (root.bar) root.bar.run(command)
}
function setting(name, fallback) {
var value = settings ? settings[name] : undefined
return value === undefined || value === null ? fallback : value
}
// Volume controls live in the audioPanel bar widget, so the quick-settings
// popup just hosts brightness + toggles. Bluetooth adapter status is
// exposed via Quickshell.Bluetooth.
readonly property var btAdapter: Bluetooth.defaultAdapter
readonly property bool btEnabled: btAdapter ? btAdapter.enabled : false
property int currentBrightness: -1
property int pendingBrightness: -1
// DND state is bound straight to the notifications service so toggling
// from the quick-settings tile or the bar widget reflects instantly.
readonly property var hostShell: bar && bar.shell ? bar.shell : null
readonly property var notificationService: hostShell && typeof hostShell.firstPartyServiceFor === "function"
? hostShell.firstPartyServiceFor("omarchy.notifications")
: null
readonly property bool dndActive: notificationService ? notificationService.doNotDisturb : false
property bool idleInhibited: false
property bool nightLightActive: false
property bool nightLightAvailable: false
property string themeName: ""
property string backgroundName: ""
function setBrightness(percent) {
var clamped = Math.max(1, Math.min(100, Math.round(percent)))
currentBrightness = clamped
pendingBrightness = clamped
brightnessWriteTimer.restart()
}
function refresh() {
if (!brightnessProc.running) brightnessProc.running = true
if (!idleProc.running) idleProc.running = true
if (!nightLightProc.running) nightLightProc.running = true
if (!themeProc.running) themeProc.running = true
if (!backgroundProc.running) backgroundProc.running = true
}
Component.onCompleted: refresh()
Process {
id: brightnessProc
command: ["bash", "-lc", "command -v brightnessctl >/dev/null || { echo missing; exit; }; cur=$(brightnessctl get); max=$(brightnessctl max); [[ $max -gt 0 ]] && echo $((cur*100/max)) || echo 0"]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
var v = String(text || "").trim()
root.currentBrightness = v === "missing" ? -1 : (parseInt(v, 10) || 0)
}
}
}
Process {
id: brightnessWriteProc
}
Timer {
id: brightnessWriteTimer
interval: 60
repeat: false
onTriggered: {
if (brightnessWriteProc.running) { brightnessWriteTimer.restart(); return }
if (root.pendingBrightness < 0) return
brightnessWriteProc.command = ["bash", "-lc", "brightnessctl set " + root.pendingBrightness + "% >/dev/null"]
root.pendingBrightness = -1
brightnessWriteProc.running = true
}
}
Process {
id: idleProc
command: ["bash", "-lc", "if pgrep -x hypridle >/dev/null 2>&1; then echo running; else echo inhibited; fi"]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: root.idleInhibited = String(text || "").trim() === "inhibited"
}
}
Process {
id: nightLightProc
command: ["bash", "-lc", "command -v hyprsunset >/dev/null || { echo missing; exit; }; if pgrep -x hyprsunset >/dev/null 2>&1; then hyprctl hyprsunset temperature 2>/dev/null | grep -oE '[0-9]+' | head -1; else echo idle; fi"]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
var state = String(text || "").trim()
if (state === "missing") {
root.nightLightAvailable = false
root.nightLightActive = false
return
}
root.nightLightAvailable = true
var temp = parseInt(state, 10)
root.nightLightActive = !isNaN(temp) && temp < 6000
}
}
}
Process {
id: themeProc
command: ["omarchy-theme-current"]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: root.themeName = String(text || "").trim()
}
}
Process {
id: themeSetProc
command: ["bash", "-lc", "theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""]
onExited: root.refresh()
}
Process {
id: backgroundProc
command: ["omarchy-theme-bg-current"]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: root.backgroundName = String(text || "").trim()
}
}
Process {
id: backgroundSetProc
command: ["bash", "-lc", "background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""]
onExited: root.refresh()
}
Timer {
interval: 3000
running: true
repeat: true
triggeredOnStart: true
onTriggered: root.refresh()
}
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
Common.WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
text: "󰙪"
fontSize: 12
onPressed: function() { root.popupOpen = !root.popupOpen }
}
Common.PopupCard {
id: popup
anchorItem: button
bar: root.bar
owner: root
open: root.popupOpen
contentWidth: 320
contentHeight: layout.implicitHeight + 28
Column {
id: layout
anchors.fill: parent
spacing: 14
Column {
width: parent.width
spacing: 10
visible: root.currentBrightness >= 0
Row {
width: parent.width
spacing: 8
Common.PillButton {
iconText: "󰃠"
foreground: root.bar.foreground
horizontalPadding: 8
verticalPadding: 6
iconSize: 16
}
Common.Slider {
bar: root.bar
width: parent.width - 90
value: Math.max(0, root.currentBrightness / 100)
minimum: 0.01
maximum: 1
step: 0.05
anchors.verticalCenter: parent.verticalCenter
onMoved: function(v) { root.setBrightness(v * 100) }
onReleased: function(v) { root.setBrightness(v * 100) }
}
Text {
text: root.currentBrightness + "%"
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: 11
anchors.verticalCenter: parent.verticalCenter
width: 32
horizontalAlignment: Text.AlignRight
}
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
}
Grid {
id: tileGrid
width: parent.width
columns: 2
columnSpacing: 8
rowSpacing: 8
Tile {
width: (tileGrid.width - tileGrid.columnSpacing) / 2
glyph: root.dndActive ? "󰂛" : "󰂚"
title: "Do Not Disturb"
subtitle: root.dndActive ? "On" : "Off"
active: root.dndActive
onClicked: {
if (!root.notificationService) return
root.notificationService.setDoNotDisturb(!root.notificationService.doNotDisturb)
}
}
Tile {
width: (tileGrid.width - tileGrid.columnSpacing) / 2
glyph: root.nightLightActive ? "󰖔" : "󰖙"
title: "Night Light"
subtitle: !root.nightLightAvailable ? "—" : (root.nightLightActive ? "On" : "Off")
active: root.nightLightActive
tileEnabled: root.nightLightAvailable
onClicked: { root.run("omarchy-toggle-nightlight"); nightLightProc.running = true }
}
Tile {
width: (tileGrid.width - tileGrid.columnSpacing) / 2
glyph: root.idleInhibited ? "󰅶" : "󰾪"
title: "Keep Awake"
subtitle: root.idleInhibited ? "On" : "Off"
active: root.idleInhibited
onClicked: { root.run("omarchy-toggle-idle"); idleProc.running = true }
}
Tile {
width: (tileGrid.width - tileGrid.columnSpacing) / 2
glyph: root.btEnabled ? "󰂯" : "󰂲"
title: "Bluetooth"
subtitle: !root.btAdapter ? "—" : (root.btEnabled ? "On" : "Off")
active: root.btEnabled
tileEnabled: root.btAdapter !== null
onClicked: {
if (!root.btAdapter) return
root.btAdapter.enabled = !root.btAdapter.enabled
}
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
}
Row {
width: parent.width
spacing: 8
AppearancePill {
width: (parent.width - parent.spacing) / 2
label: "Theme"
currentValue: root.themeName || "—"
onClicked: {
root.popupOpen = false
if (!themeSetProc.running) themeSetProc.running = true
}
}
AppearancePill {
width: (parent.width - parent.spacing) / 2
label: "Background"
currentValue: root.backgroundName || "—"
onClicked: {
root.popupOpen = false
if (!backgroundSetProc.running) backgroundSetProc.running = true
}
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
}
Common.PillButton {
width: parent.width
iconText: "󰙪"
text: "Settings…"
foreground: root.bar.foreground
horizontalPadding: 10
verticalPadding: 8
onClicked: { root.run("omarchy-launch-settings"); root.popupOpen = false }
}
}
}
component AppearancePill: Rectangle {
id: appearancePill
property string label: ""
property string currentValue: "—"
signal clicked()
implicitHeight: 56
height: 56
radius: 6
color: appearanceArea.containsMouse
? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.04)
border.color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
border.width: 1
Behavior on color { ColorAnimation { duration: 120 } }
Column {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 10
spacing: 2
Text {
text: appearancePill.label
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: 11
font.bold: true
elide: Text.ElideRight
width: parent.width
}
Text {
text: appearancePill.currentValue
color: Qt.darker(root.bar.foreground, 1.35)
font.family: root.bar.fontFamily
font.pixelSize: 10
elide: Text.ElideRight
width: parent.width
}
}
MouseArea {
id: appearanceArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: appearancePill.clicked()
}
}
component Tile: Rectangle {
id: tile
property string glyph: ""
property string title: ""
property string subtitle: ""
property bool active: false
property bool tileEnabled: true
signal clicked()
implicitHeight: 56
radius: 6
color: tileArea.containsMouse
? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.16)
: (active ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.10)
: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.04))
border.color: active ? root.bar.foreground : Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
border.width: 1
opacity: tileEnabled ? 1 : 0.4
Behavior on color { ColorAnimation { duration: 120 } }
Row {
anchors.fill: parent
anchors.margins: 10
spacing: 8
Text {
text: tile.glyph
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: 18
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - parent.children[0].implicitWidth - 8
Text {
text: tile.title
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: 11
font.bold: true
elide: Text.ElideRight
width: parent.width
}
Text {
text: tile.subtitle
color: Qt.darker(root.bar.foreground, 1.4)
font.family: root.bar.fontFamily
font.pixelSize: 10
elide: Text.ElideRight
width: parent.width
}
}
}
MouseArea {
id: tileArea
anchors.fill: parent
hoverEnabled: true
cursorShape: tile.tileEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: tile.tileEnabled
onClicked: tile.clicked()
}
}
}