Add idle timeouts to the Quick Settings General tab

This commit is contained in:
SirBlob committed 2026-09-24 12:49:45 -04:00
1 parent 1f86bfd151
commit 4f2d319a35
4 files changed
+185 -19

No files matched your search

+9 -2
View File
@@ -9,7 +9,7 @@ autostarted.
| Was | Is | Note | | Was | Is | Note |
| --- | --- | --- | | --- | --- | --- |
| `QuickSettings.tsx` (523 lines) | `shell/plugins/panels/quick-settings/` | split across 13 files, none over 350 lines, with four tabs | | `QuickSettings.tsx` (523 lines) | `shell/plugins/panels/quick-settings/` | split across 15 files, none over 350 lines, with four tabs |
| `SysMonitor.tsx` (142) | `shell/plugins/panels/sysmon/` | CPU, memory, disk, temperature; the probes and bars are `Ui/SystemStats.qml` and `Ui/MetricBar.qml`, shared with the Performance tab | | `SysMonitor.tsx` (142) | `shell/plugins/panels/sysmon/` | CPU, memory, disk, temperature; the probes and bars are `Ui/SystemStats.qml` and `Ui/MetricBar.qml`, shared with the Performance tab |
| `Notifications.tsx` (160) | `shell/plugins/notification-center/` | reads the notification service's own history files | | `Notifications.tsx` (160) | `shell/plugins/notification-center/` | reads the notification service's own history files |
| `WallPicker.tsx` (106) | the existing image picker | `blob-wallpaper-set --menu` now calls `blob-menu-images`, so wallpapers get the image grid instead of a text list | | `WallPicker.tsx` (106) | the existing image picker | `blob-wallpaper-set --menu` now calls `blob-menu-images`, so wallpapers get the image grid instead of a text list |
@@ -41,13 +41,20 @@ running `pamixer` and `playerctl` every second.
| Tab | Holds | | Tab | Holds |
| --- | --- | | --- | --- |
| General | the calendar and the brightness slider | | General | the calendar, the brightness slider, and the screensaver and lock timeouts |
| Sound & Music | output and microphone volume (click the icon to mute), the media row with its controls, and buttons for the audio panel (picking devices) and the pavucontrol mixer | | Sound & Music | output and microphone volume (click the icon to mute), the media row with its controls, and buttons for the audio panel (picking devices) and the pavucontrol mixer |
| Performance | CPU, memory, disk and temperature bars, the load average, the power profile (Power saver, Balanced, Performance, whichever the machine offers), and a button that opens btop | | Performance | CPU, memory, disk and temperature bars, the load average, the power profile (Power saver, Balanced, Performance, whichever the machine offers), and a button that opens btop |
| Quick Settings | the toggle tiles: Wi-Fi, Bluetooth, Wallpaper, Silence, Night Light, Record, Stay Awake, Pick Color, Theme, Tablet Lock | | Quick Settings | the toggle tiles: Wi-Fi, Bluetooth, Wallpaper, Silence, Night Light, Record, Stay Awake, Pick Color, Theme, Tablet Lock |
The weather card beside it is not part of the tabs and stays visible. The weather card beside it is not part of the tabs and stays visible.
The two timeouts on the General tab are the `idle.screensaver` and `idle.lock`
values in `~/.config/blob/shell.json`, in seconds. The minus and plus buttons
step through 1, 2, 5, 10, 15, 30 and 60 minutes (and 2 hours for the lock),
starting from whatever the file holds, and the idle service picks the new value
up straight away. A value set by hand that is not a preset is shown as it is.
While Stay Awake is on, the tab says so, since neither timeout runs then.
## Opening them ## Opening them
| Panel | Opened by | | Panel | Opened by |
@@ -0,0 +1,59 @@
import QtQuick
import qs.Commons
Column {
id: root
property bool polling: false
property int brightness: 0
property int screensaverSeconds: 0
property int lockSeconds: 0
property bool stayAwake: false
signal brightnessRequested(int value)
signal timeoutRequested(string key, int seconds)
spacing: Style.spaceReal(12)
Calendar {
width: parent.width
polling: root.polling
}
SliderRow {
width: parent.width
icon: ""
value: root.brightness
onValueRequested: function(next) { root.brightnessRequested(next) }
}
TimeoutRow {
width: parent.width
icon: ""
label: "Screensaver after"
seconds: root.screensaverSeconds
presetMinutes: [1, 2, 5, 10, 15, 30, 60]
onSecondsRequested: function(seconds) { root.timeoutRequested("screensaver", seconds) }
}
TimeoutRow {
width: parent.width
icon: ""
label: "Lock after"
seconds: root.lockSeconds
presetMinutes: [1, 2, 5, 10, 15, 30, 60, 120]
onSecondsRequested: function(seconds) { root.timeoutRequested("lock", seconds) }
}
Text {
width: parent.width
visible: root.stayAwake
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
text: "Stay Awake is on, so neither starts until it is turned off."
textFormat: Text.PlainText
color: Util.alpha(Color.foreground, 0.6)
font.family: Style.font.family
font.pixelSize: Style.font.bodySmall
}
}
@@ -27,6 +27,11 @@ Item {
readonly property bool performanceVisible: opened && currentTab === "performance" readonly property bool performanceVisible: opened && currentTab === "performance"
readonly property bool quickVisible: opened && currentTab === "quick" readonly property bool quickVisible: opened && currentTab === "quick"
readonly property var idleConfig: root.shell && root.shell.shellConfig && root.shell.shellConfig.idle
? root.shell.shellConfig.idle : ({})
readonly property int screensaverSeconds: root.timeoutSeconds(idleConfig.screensaver, 150)
readonly property int lockSeconds: root.timeoutSeconds(idleConfig.lock, 300)
readonly property int barInset: { readonly property int barInset: {
var bar = root.shell ? root.shell.bar : null var bar = root.shell ? root.shell.bar : null
if (!bar || bar.barHidden === true) return 0 if (!bar || bar.barHidden === true) return 0
@@ -78,6 +83,19 @@ Item {
mediaValue.refresh() mediaValue.refresh()
} }
function timeoutSeconds(value, fallback) {
var seconds = Number(value)
return isFinite(seconds) && seconds >= 0 ? Math.floor(seconds) : fallback
}
function setIdleTimeout(key, seconds) {
if (!root.shell || typeof root.shell.mutateShellConfig !== "function") return
root.shell.mutateShellConfig(function(config) {
if (!config.idle || typeof config.idle !== "object") config.idle = {}
config.idle[key] = seconds
})
}
function toggleAndRefresh(command) { function toggleAndRefresh(command) {
root.run(command) root.run(command)
stateSettleTimer.restart() stateSettleTimer.restart()
@@ -109,7 +127,7 @@ Item {
PolledFlag { PolledFlag {
id: stayAwakeFlag id: stayAwakeFlag
polling: root.quickVisible polling: root.quickVisible || root.generalVisible
command: "blob-toggle-idle status 2>/dev/null | grep -q '\"enabled\":true' && echo on || echo off" command: "blob-toggle-idle status 2>/dev/null | grep -q '\"enabled\":true' && echo on || echo off"
} }
@@ -256,25 +274,19 @@ Item {
onTabRequested: function(tabId) { root.currentTab = tabId } onTabRequested: function(tabId) { root.currentTab = tabId }
} }
Column { GeneralTab {
width: parent.width width: parent.width
visible: root.currentTab === "general" visible: root.currentTab === "general"
spacing: Style.spaceReal(12) polling: root.generalVisible
brightness: Number(brightnessValue.value) || 0
Calendar { screensaverSeconds: root.screensaverSeconds
width: parent.width lockSeconds: root.lockSeconds
polling: root.generalVisible stayAwake: stayAwakeFlag.value
} onBrightnessRequested: function(next) {
root.run("brightnessctl set " + next + "%")
SliderRow { brightnessValue.value = String(next)
width: parent.width
icon: "\uf185"
value: Number(brightnessValue.value) || 0
onValueRequested: function(next) {
root.run("brightnessctl set " + next + "%")
brightnessValue.value = String(next)
}
} }
onTimeoutRequested: function(key, seconds) { root.setIdleTimeout(key, seconds) }
} }
SoundTab { SoundTab {
@@ -0,0 +1,88 @@
import QtQuick
import qs.Commons
import qs.Ui
Item {
id: root
property string icon: ""
property string label: ""
property int seconds: 0
property var presetMinutes: []
signal secondsRequested(int seconds)
implicitHeight: Math.max(labelText.implicitHeight, controls.implicitHeight)
function formatSeconds(value) {
if (value >= 3600 && value % 3600 === 0) return (value / 3600) + " h"
if (value % 60 === 0) return (value / 60) + " min"
return (Math.round(value / 6) / 10) + " min"
}
function step(delta) {
var minutes = root.seconds / 60
var presets = root.presetMinutes
var next = -1
if (delta > 0) {
for (var i = 0; i < presets.length; i++)
if (presets[i] > minutes) { next = presets[i]; break }
} else {
for (var j = presets.length - 1; j >= 0; j--)
if (presets[j] < minutes) { next = presets[j]; break }
}
if (next > 0) root.secondsRequested(next * 60)
}
Text {
id: iconText
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Style.spaceReal(22)
text: root.icon
textFormat: Text.PlainText
color: Color.magenta
font.family: Style.font.family
font.pixelSize: Style.font.body
}
Text {
id: labelText
anchors.left: iconText.right
anchors.leftMargin: Style.spaceReal(10)
anchors.verticalCenter: parent.verticalCenter
text: root.label
textFormat: Text.PlainText
color: Color.foreground
font.family: Style.font.family
font.pixelSize: Style.font.bodySmall
}
Row {
id: controls
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Style.spaceReal(8)
CardIconButton {
icon: "-"
onActivated: root.step(-1)
}
Text {
anchors.verticalCenter: parent.verticalCenter
width: Style.spaceReal(52)
horizontalAlignment: Text.AlignHCenter
text: root.formatSeconds(root.seconds)
textFormat: Text.PlainText
color: Color.foreground
font.family: Style.font.family
font.pixelSize: Style.font.bodySmall
}
CardIconButton {
icon: "+"
onActivated: root.step(1)
}
}
}