337 lines
11 KiB
QML
337 lines
11 KiB
QML
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)
|
|
}
|
|
}
|
|
}
|