Add tabs, a performance page and a mixer button to Quick Settings
This commit is contained in:
1 parent
3164e50b36
commit
0ed5e22fc6
11 files changed
+520
-158
No files matched your search
@@ -0,0 +1,137 @@
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property bool polling: false
|
||||
|
||||
signal commandRequested(string command)
|
||||
signal tuiRequested(string command)
|
||||
|
||||
readonly property var profileLabels: ({
|
||||
"power-saver": "Power saver",
|
||||
"balanced": "Balanced",
|
||||
"performance": "Performance"
|
||||
})
|
||||
property var profiles: []
|
||||
property string activeProfile: ""
|
||||
|
||||
spacing: Style.spaceReal(12)
|
||||
|
||||
function readProfiles(raw) {
|
||||
var available = []
|
||||
var active = ""
|
||||
var lines = String(raw).trim().split("\n")
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var fields = lines[i].split("\t")
|
||||
if (!fields[0]) continue
|
||||
available.push({ value: fields[0], label: root.profileLabels[fields[0]] || fields[0] })
|
||||
if (fields[1] === "1") active = fields[0]
|
||||
}
|
||||
root.profiles = available
|
||||
root.activeProfile = active
|
||||
}
|
||||
|
||||
function refreshProfiles() {
|
||||
if (!profileProbe.running) profileProbe.running = true
|
||||
}
|
||||
|
||||
function setProfile(profile) {
|
||||
root.activeProfile = profile
|
||||
root.commandRequested("blob-power-set autodetect " + profile)
|
||||
profileSettleTimer.restart()
|
||||
}
|
||||
|
||||
onPollingChanged: if (root.polling) root.refreshProfiles()
|
||||
|
||||
SystemStats {
|
||||
id: stats
|
||||
polling: root.polling
|
||||
}
|
||||
|
||||
Process {
|
||||
id: profileProbe
|
||||
command: ["blob-power-list", "--active-state"]
|
||||
stdout: StdioCollector { onStreamFinished: root.readProfiles(text) }
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: profileSettleTimer
|
||||
interval: 500
|
||||
onTriggered: root.refreshProfiles()
|
||||
}
|
||||
|
||||
MetricBar {
|
||||
width: parent.width
|
||||
icon: ""
|
||||
name: "CPU"
|
||||
percent: stats.cpuPercent
|
||||
detail: stats.cpuPercent + "%"
|
||||
}
|
||||
|
||||
MetricBar {
|
||||
width: parent.width
|
||||
icon: ""
|
||||
name: "Memory"
|
||||
percent: stats.memoryPercent
|
||||
detail: stats.memoryDetail
|
||||
}
|
||||
|
||||
MetricBar {
|
||||
width: parent.width
|
||||
icon: ""
|
||||
name: "Disk"
|
||||
percent: stats.diskPercent
|
||||
detail: stats.diskDetail
|
||||
}
|
||||
|
||||
MetricBar {
|
||||
width: parent.width
|
||||
icon: ""
|
||||
name: "Temperature"
|
||||
percent: stats.temperature
|
||||
detail: stats.temperature + "°C"
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: stats.loadAverage !== ""
|
||||
text: "Load average " + stats.loadAverage
|
||||
textFormat: Text.PlainText
|
||||
color: Util.alpha(Color.foreground, 0.7)
|
||||
font.family: Style.font.family
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
visible: root.profiles.length > 0
|
||||
spacing: Style.spaceReal(6)
|
||||
|
||||
Repeater {
|
||||
model: root.profiles
|
||||
|
||||
Button {
|
||||
required property var modelData
|
||||
width: (parent.width - parent.spacing * (root.profiles.length - 1)) / root.profiles.length
|
||||
text: modelData.label
|
||||
selected: modelData.value === root.activeProfile
|
||||
bordered: true
|
||||
fontSize: Style.font.bodySmall
|
||||
onClicked: root.setProfile(modelData.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: parent.width
|
||||
text: "Open btop"
|
||||
iconText: ""
|
||||
bordered: true
|
||||
fontSize: Style.font.bodySmall
|
||||
onClicked: root.tuiRequested("btop")
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,18 @@ Item {
|
||||
|
||||
readonly property string pluginId: (manifest && manifest.id) || "blob.quick-settings"
|
||||
|
||||
readonly property var tabs: [
|
||||
{ id: "general", label: "General", icon: "" },
|
||||
{ id: "sound", label: "Sound & Music", icon: "" },
|
||||
{ id: "performance", label: "Performance", icon: "" },
|
||||
{ id: "quick", label: "Quick Settings", icon: "" }
|
||||
]
|
||||
property string currentTab: "general"
|
||||
readonly property bool generalVisible: opened && currentTab === "general"
|
||||
readonly property bool soundVisible: opened && currentTab === "sound"
|
||||
readonly property bool performanceVisible: opened && currentTab === "performance"
|
||||
readonly property bool quickVisible: opened && currentTab === "quick"
|
||||
|
||||
readonly property int barInset: {
|
||||
var bar = root.shell ? root.shell.bar : null
|
||||
if (!bar || bar.barHidden === true) return 0
|
||||
@@ -60,6 +72,8 @@ Item {
|
||||
recordingFlag.refresh()
|
||||
volumeValue.refresh()
|
||||
mutedFlag.refresh()
|
||||
micVolumeValue.refresh()
|
||||
micMutedFlag.refresh()
|
||||
brightnessValue.refresh()
|
||||
mediaValue.refresh()
|
||||
}
|
||||
@@ -77,57 +91,71 @@ Item {
|
||||
|
||||
PolledFlag {
|
||||
id: bluetoothFlag
|
||||
polling: root.opened
|
||||
polling: root.quickVisible
|
||||
command: "bluetoothctl show 2>/dev/null | grep -q 'Powered: yes' && echo on || echo off"
|
||||
}
|
||||
|
||||
PolledFlag {
|
||||
id: dndFlag
|
||||
polling: root.opened
|
||||
polling: root.quickVisible
|
||||
command: "blob-shell notifications dndState 2>/dev/null"
|
||||
}
|
||||
|
||||
PolledFlag {
|
||||
id: nightLightFlag
|
||||
polling: root.opened
|
||||
polling: root.quickVisible
|
||||
command: "blob-toggle-nightlight --status 2>/dev/null | grep -q '\"enabled\":true' && echo on || echo off"
|
||||
}
|
||||
|
||||
PolledFlag {
|
||||
id: stayAwakeFlag
|
||||
polling: root.opened
|
||||
polling: root.quickVisible
|
||||
command: "blob-toggle-idle status 2>/dev/null | grep -q '\"enabled\":true' && echo on || echo off"
|
||||
}
|
||||
|
||||
PolledFlag {
|
||||
id: tabletFlag
|
||||
polling: root.opened
|
||||
polling: root.quickVisible
|
||||
command: "blob-toggle-tablet status 2>/dev/null | grep -q '\"enabled\":true' && echo on || echo off"
|
||||
}
|
||||
|
||||
PolledFlag {
|
||||
id: recordingFlag
|
||||
polling: root.opened
|
||||
polling: root.quickVisible
|
||||
command: "pgrep -f '^gpu-screen-recorder' >/dev/null && echo on || echo off"
|
||||
}
|
||||
|
||||
PolledFlag {
|
||||
id: mutedFlag
|
||||
polling: root.opened
|
||||
polling: root.soundVisible
|
||||
intervalMs: 1000
|
||||
command: "pamixer --get-mute 2>/dev/null | grep -qx true && echo on || echo off"
|
||||
}
|
||||
|
||||
PolledText {
|
||||
id: volumeValue
|
||||
polling: root.opened
|
||||
polling: root.soundVisible
|
||||
intervalMs: 1000
|
||||
command: "pamixer --get-volume 2>/dev/null || echo 0"
|
||||
}
|
||||
|
||||
PolledFlag {
|
||||
id: micMutedFlag
|
||||
polling: root.soundVisible
|
||||
intervalMs: 1000
|
||||
command: "pamixer --default-source --get-mute 2>/dev/null | grep -qx true && echo on || echo off"
|
||||
}
|
||||
|
||||
PolledText {
|
||||
id: micVolumeValue
|
||||
polling: root.soundVisible
|
||||
intervalMs: 1000
|
||||
command: "pamixer --default-source --get-volume 2>/dev/null || echo 0"
|
||||
}
|
||||
|
||||
PolledText {
|
||||
id: brightnessValue
|
||||
polling: root.opened
|
||||
polling: root.generalVisible
|
||||
intervalMs: 2000
|
||||
command: "brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%' || echo 0"
|
||||
}
|
||||
@@ -148,7 +176,7 @@ Item {
|
||||
|
||||
PolledText {
|
||||
id: mediaValue
|
||||
polling: root.opened
|
||||
polling: root.soundVisible
|
||||
intervalMs: 1000
|
||||
command: "playerctl metadata -f '{{title}}|||{{artist}}|||{{status}}' 2>/dev/null || echo 'No Media||||||Stopped'"
|
||||
}
|
||||
@@ -221,13 +249,70 @@ Item {
|
||||
onCloseRequested: root.dismiss()
|
||||
}
|
||||
|
||||
Calendar {
|
||||
TabBar {
|
||||
width: parent.width
|
||||
polling: root.opened
|
||||
tabs: root.tabs
|
||||
currentTab: root.currentTab
|
||||
onTabRequested: function(tabId) { root.currentTab = tabId }
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
visible: root.currentTab === "general"
|
||||
spacing: Style.spaceReal(12)
|
||||
|
||||
Calendar {
|
||||
width: parent.width
|
||||
polling: root.generalVisible
|
||||
}
|
||||
|
||||
SliderRow {
|
||||
width: parent.width
|
||||
icon: "\uf185"
|
||||
value: Number(brightnessValue.value) || 0
|
||||
onValueRequested: function(next) {
|
||||
root.run("brightnessctl set " + next + "%")
|
||||
brightnessValue.value = String(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SoundTab {
|
||||
width: parent.width
|
||||
visible: root.currentTab === "sound"
|
||||
outputVolume: Number(volumeValue.value) || 0
|
||||
outputMuted: mutedFlag.value
|
||||
inputVolume: Number(micVolumeValue.value) || 0
|
||||
inputMuted: micMutedFlag.value
|
||||
mediaParts: root.mediaParts
|
||||
onCommandRequested: function(command) { root.toggleAndRefresh(command) }
|
||||
onOutputVolumeRequested: function(next) {
|
||||
root.run("pamixer --set-volume " + next)
|
||||
volumeValue.value = String(next)
|
||||
}
|
||||
onInputVolumeRequested: function(next) {
|
||||
root.run("pamixer --default-source --set-volume " + next)
|
||||
micVolumeValue.value = String(next)
|
||||
}
|
||||
onMediaControlRequested: function(action) {
|
||||
root.run("playerctl " + action)
|
||||
stateSettleTimer.restart()
|
||||
}
|
||||
onPanelRequested: function(pluginId) { root.openPanel(pluginId) }
|
||||
onAppRequested: function(command) { root.runAndDismiss("uwsm-app -- " + command) }
|
||||
}
|
||||
|
||||
PerformanceTab {
|
||||
width: parent.width
|
||||
visible: root.currentTab === "performance"
|
||||
polling: root.performanceVisible
|
||||
onCommandRequested: function(command) { root.run(command) }
|
||||
onTuiRequested: function(command) { root.runAndDismiss("blob-launch-or-focus-tui " + command) }
|
||||
}
|
||||
|
||||
Tiles {
|
||||
width: parent.width
|
||||
visible: root.currentTab === "quick"
|
||||
bluetoothOn: bluetoothFlag.value
|
||||
doNotDisturb: dndFlag.value
|
||||
nightLight: nightLightFlag.value
|
||||
@@ -238,39 +323,6 @@ Item {
|
||||
onPanelRequested: function(pluginId) { root.openPanel(pluginId) }
|
||||
onToggleRequested: function(command) { root.toggleAndRefresh(command) }
|
||||
}
|
||||
|
||||
SliderRow {
|
||||
width: parent.width
|
||||
icon: mutedFlag.value ? "\uf026" : "\uf028"
|
||||
dimmed: mutedFlag.value
|
||||
value: Number(volumeValue.value) || 0
|
||||
onIconActivated: root.toggleAndRefresh("pamixer --toggle-mute")
|
||||
onValueRequested: function(next) {
|
||||
root.run("pamixer --set-volume " + next)
|
||||
volumeValue.value = String(next)
|
||||
}
|
||||
}
|
||||
|
||||
SliderRow {
|
||||
width: parent.width
|
||||
icon: "\uf185"
|
||||
value: Number(brightnessValue.value) || 0
|
||||
onValueRequested: function(next) {
|
||||
root.run("brightnessctl set " + next + "%")
|
||||
brightnessValue.value = String(next)
|
||||
}
|
||||
}
|
||||
|
||||
MediaRow {
|
||||
width: parent.width
|
||||
title: root.mediaParts.length > 0 ? root.mediaParts[0] : ""
|
||||
artist: root.mediaParts.length > 1 ? root.mediaParts[1] : ""
|
||||
status: root.mediaParts.length > 2 ? root.mediaParts[2] : "Stopped"
|
||||
onControlRequested: function(action) {
|
||||
root.run("playerctl " + action)
|
||||
stateSettleTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property int outputVolume: 0
|
||||
property bool outputMuted: false
|
||||
property int inputVolume: 0
|
||||
property bool inputMuted: false
|
||||
property var mediaParts: []
|
||||
|
||||
signal commandRequested(string command)
|
||||
signal outputVolumeRequested(int value)
|
||||
signal inputVolumeRequested(int value)
|
||||
signal mediaControlRequested(string action)
|
||||
signal panelRequested(string pluginId)
|
||||
signal appRequested(string command)
|
||||
|
||||
spacing: Style.spaceReal(12)
|
||||
|
||||
SliderRow {
|
||||
width: parent.width
|
||||
icon: root.outputMuted ? "" : ""
|
||||
dimmed: root.outputMuted
|
||||
value: root.outputVolume
|
||||
onIconActivated: root.commandRequested("pamixer --toggle-mute")
|
||||
onValueRequested: function(next) { root.outputVolumeRequested(next) }
|
||||
}
|
||||
|
||||
SliderRow {
|
||||
width: parent.width
|
||||
icon: root.inputMuted ? "" : ""
|
||||
dimmed: root.inputMuted
|
||||
value: root.inputVolume
|
||||
onIconActivated: root.commandRequested("pamixer --default-source --toggle-mute")
|
||||
onValueRequested: function(next) { root.inputVolumeRequested(next) }
|
||||
}
|
||||
|
||||
MediaRow {
|
||||
id: mediaRow
|
||||
width: parent.width
|
||||
title: root.mediaParts.length > 0 ? root.mediaParts[0] : ""
|
||||
artist: root.mediaParts.length > 1 ? root.mediaParts[1] : ""
|
||||
status: root.mediaParts.length > 2 ? root.mediaParts[2] : "Stopped"
|
||||
onControlRequested: function(action) { root.mediaControlRequested(action) }
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: !mediaRow.visible
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: "Nothing playing"
|
||||
textFormat: Text.PlainText
|
||||
color: Util.alpha(Color.foreground, 0.6)
|
||||
font.family: Style.font.family
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Style.spaceReal(6)
|
||||
|
||||
Button {
|
||||
width: (parent.width - parent.spacing) / 2
|
||||
text: "Audio devices"
|
||||
iconText: ""
|
||||
bordered: true
|
||||
fontSize: Style.font.bodySmall
|
||||
onClicked: root.panelRequested("blob.audio")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: (parent.width - parent.spacing) / 2
|
||||
text: "Mixer"
|
||||
iconText: ""
|
||||
bordered: true
|
||||
fontSize: Style.font.bodySmall
|
||||
onClicked: root.appRequested("pavucontrol")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property var tabs: []
|
||||
property string currentTab: ""
|
||||
signal tabRequested(string tabId)
|
||||
|
||||
spacing: Style.spaceReal(6)
|
||||
|
||||
Repeater {
|
||||
model: root.tabs
|
||||
|
||||
Button {
|
||||
required property var modelData
|
||||
width: (root.width - root.spacing * (root.tabs.length - 1)) / root.tabs.length
|
||||
text: modelData.label
|
||||
iconText: modelData.icon
|
||||
selected: modelData.id === root.currentTab
|
||||
bordered: true
|
||||
fontSize: Style.font.bodySmall
|
||||
onClicked: root.tabRequested(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user