import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Bluetooth import Quickshell.Io import Quickshell.Networking import Quickshell.Services.Pipewire import Quickshell.Services.UPower import Quickshell.Wayland Scope { id: root property bool open: false property bool panelVisible: false property bool contentReady: false property bool mediaOpen: false property bool notificationsOpen: false property bool osdOpen: false property string osdKind: "volume" property int pageIndex: 0 property int brightness: 0 property var pendingNetwork: null property string wifiPassword: "" property var wifiDevice: Networking.devices.values.find(device => device.type === DeviceType.Wifi) property var bluetoothAdapter: Bluetooth.defaultAdapter property var audioSink: Pipewire.defaultAudioSink property var settings: statusSettings property alias cpuTemperature: statusBar.temperature property alias gpuTemperature: statusBar.gpuTemperature Settings { id: statusSettings } UiTokens { id: uiTheme } Bar { id: statusBar controlCenter: root settings: root.settings mediaPopup: mediaPopup toggleMedia: function() { root.mediaOpen = !root.mediaOpen } notificationCenter: notificationCenter } MediaPopup { id: mediaPopup open: root.mediaOpen onDismissed: root.mediaOpen = false } Notifications { id: notificationCenter settings: root.settings open: root.notificationsOpen onDismissed: root.notificationsOpen = false } Osd { open: root.osdOpen kind: root.osdKind value: root.osdKind === "brightness" ? root.brightness / 100 : (root.audioSink && root.audioSink.audio ? root.audioSink.audio.volume : 0) muted: root.audioSink && root.audioSink.audio ? root.audioSink.audio.muted : false } function setBrightness(value) { const level = Math.max(0, Math.min(100, Math.round(value))); Quickshell.execDetached(["ddcutil", "setvcp", "10", String(level)]); root.brightness = level; } function connectWifi(network) { if (network.security === WifiSecurityType.Open) { network.connect(); return; } root.pendingNetwork = network; root.wifiPassword = ""; } function submitWifiPassword() { if (root.pendingNetwork && root.wifiPassword.length > 0) { root.pendingNetwork.connectWithPsk(root.wifiPassword); root.pendingNetwork = null; root.wifiPassword = ""; } } function showOsd(kind) { root.osdKind = kind; root.osdOpen = true; osdTimer.restart(); } function adjustBrightness(delta) { const current = root.brightness > 0 ? root.brightness : 50; root.setBrightness(current + delta); root.showOsd("brightness"); } function temperatureColor(value) { const match = String(value).match(/(\d+)C\s*$/); const temperature = match ? Number(match[1]) : -1; if (temperature < 0) return uiTheme.mutedText; if (temperature >= root.settings.temperatureCritical) return uiTheme.critical; if (temperature >= root.settings.temperatureWarning) return uiTheme.warning; return uiTheme.success; } function openPanel() { closePanelTimer.stop(); root.panelVisible = true; root.contentReady = false; openPanelTimer.restart(); } function closePanel() { openPanelTimer.stop(); contentReadyTimer.stop(); root.contentReady = false; root.open = false; closePanelTimer.restart(); } function togglePanel() { if (root.open || root.panelVisible) root.closePanel(); else root.openPanel(); } Timer { id: openPanelTimer interval: 1 repeat: false onTriggered: { root.open = true; contentReadyTimer.restart(); } } Timer { id: contentReadyTimer interval: 220 repeat: false onTriggered: root.contentReady = true } Timer { id: closePanelTimer interval: 240 repeat: false onTriggered: root.panelVisible = false } IpcHandler { target: "controlcenter" function toggle() { root.togglePanel(); } function open() { root.openPanel(); } function close() { root.closePanel(); } } IpcHandler { target: "osd" function volume_up() { Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%+"]); root.showOsd("volume"); } function volume_down() { Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%-"]); root.showOsd("volume"); } function volume_mute() { Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]); root.showOsd("volume"); } function mic_mute() { Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SOURCE@", "toggle"]); root.showOsd("mic"); } function brightness_up() { root.adjustBrightness(5); } function brightness_down() { root.adjustBrightness(-5); } } Timer { id: osdTimer; interval: 1400; repeat: false; onTriggered: root.osdOpen = false } PwObjectTracker { objects: [root.audioSink] } Process { id: brightnessReader command: ["sh", "-c", "ddcutil getvcp 10 2>/dev/null | sed -n 's/.*current value = *\\([0-9]*\\).*/\\1/p'"] stdout: StdioCollector { onStreamFinished: { const value = parseInt(text.trim()); if (!isNaN(value)) root.brightness = value; } } } Timer { id: brightnessPoll interval: 3000 repeat: true running: root.open onTriggered: if (!brightnessReader.running) brightnessReader.running = true } PanelWindow { visible: root.panelVisible color: "transparent" anchors { top: true; bottom: true; left: true; right: true } exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: root.panelVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None WlrLayershell.namespace: "quickshell-control-center" MouseArea { anchors.fill: parent onClicked: root.closePanel() } Rectangle { id: card width: Math.min(root.settings.panelWidth, parent.width - 24) height: parent.height anchors.top: parent.top x: root.open ? parent.width - width - 16 : parent.width + 24 radius: root.settings.panelRadius color: Qt.rgba(uiTheme.background.r, uiTheme.background.g, uiTheme.background.b, root.settings.panelOpacity) border.color: uiTheme.border border.width: 1 z: 1 Behavior on x { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } } MouseArea { anchors.fill: parent; onClicked: mouse.accepted = true } RowLayout { anchors.fill: parent anchors.margins: 12 spacing: 12 NavigationRail { id: navigation theme: uiTheme currentIndex: root.pageIndex Layout.fillHeight: true onSelected: root.pageIndex = index } Rectangle { Layout.fillHeight: true Layout.preferredWidth: 1 color: uiTheme.border } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 10 RowLayout { Layout.fillWidth: true ColumnLayout { Layout.fillWidth: true spacing: 0 Label { text: navigation.names[root.pageIndex] color: uiTheme.text font.pixelSize: 22 font.bold: true } Label { text: "Control center / Niri" color: uiTheme.mutedText font.pixelSize: 11 } } CCButton { theme: uiTheme; text: "Close"; onClicked: root.closePanel() } } ScrollView { id: pageScroll Layout.fillWidth: true Layout.fillHeight: true clip: true contentWidth: availableWidth Loader { id: pageLoader width: pageScroll.availableWidth active: root.contentReady sourceComponent: [quickPage, barPage, panelPage, notificationsPage, statusPage][root.pageIndex] opacity: root.contentReady ? 1 : 0 Behavior on opacity { NumberAnimation { duration: 140 } } } } } } } } PanelWindow { visible: root.settings.panelOpenOnCorner && !root.panelVisible implicitWidth: 30 implicitHeight: 30 anchors { top: true; right: true } color: "transparent" exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.namespace: "quickshell-control-center-hot-corner" Rectangle { anchors.fill: parent anchors.margins: 4 radius: 8 color: uiTheme.primary opacity: 0.35 MouseArea { anchors.fill: parent; onClicked: root.openPanel() } } } Component { id: quickPage QuickPage { width: pageLoader.width; controlCenter: root; settings: root.settings; theme: uiTheme } } Component { id: barPage BarPage { width: pageLoader.width; settings: root.settings; theme: uiTheme; availableModules: root.settings.defaultBarModules } } Component { id: panelPage PanelPage { width: pageLoader.width; settings: root.settings; theme: uiTheme } } Component { id: notificationsPage NotificationsPage { width: pageLoader.width; settings: root.settings; theme: uiTheme; notificationCenter: notificationCenter } } Component { id: statusPage StatusPage { width: pageLoader.width settings: root.settings theme: uiTheme cpuTemperature: root.cpuTemperature gpuTemperature: root.gpuTemperature temperatureColorCpu: root.temperatureColor(root.cpuTemperature) temperatureColorGpu: root.temperatureColor(root.gpuTemperature) } } }