aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
diff options
context:
space:
mode:
Diffstat (limited to 'desktop')
-rw-r--r--desktop/modules/status/LockRow.qml113
-rw-r--r--desktop/modules/status/README.md24
-rw-r--r--desktop/modules/status/StatusPage.qml10
-rw-r--r--desktop/modules/status/StatusTile.qml1
-rwxr-xr-xdesktop/modules/status/statusctl2
-rwxr-xr-xdesktop/modules/status/test-statusctl.sh7
6 files changed, 151 insertions, 6 deletions
diff --git a/desktop/modules/status/LockRow.qml b/desktop/modules/status/LockRow.qml
new file mode 100644
index 0000000..c59c2f1
--- /dev/null
+++ b/desktop/modules/status/LockRow.qml
@@ -0,0 +1,113 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import QtQuick
+import QtQuick.Controls
+import "../.."
+
+// A switch plus an optional minutes field. The switch is the auto-lock state:
+// on means the screen locks on idle as usual. Turning it off asserts the idle
+// inhibitor, which is the only thing that stops hypridle. A number in the field
+// makes that disable temporary and it re-enables itself; an empty field makes
+// it permanent. The singleton owns the state and the timer, so the effect
+// outlives this row, which dies with the drawer page.
+Item {
+ id: row
+
+ // A tick so the row repaints when a timed disable expires on its own.
+ property double now: Date.now()
+ Timer {
+ interval: 1000
+ running: true
+ repeat: true
+ onTriggered: row.now = Date.now()
+ }
+
+ readonly property bool lockOn: !Status.nolock
+ readonly property bool timed: Status.nolockUntil > row.now
+
+ implicitHeight: Math.max(texts.implicitHeight, controls.implicitHeight) + 16
+
+ function hhmm(ts) {
+ const d = new Date(ts);
+ return ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2);
+ }
+
+ function apply(lockOn) {
+ if (lockOn) {
+ Status.setMode("nolock", false);
+ } else {
+ const n = parseInt(minutes.text, 10);
+ Status.disableLockFor(isNaN(n) || n < 1 ? 0 : n);
+ }
+ }
+
+ Column {
+ id: texts
+ anchors {
+ left: parent.left
+ right: controls.left
+ rightMargin: 12
+ verticalCenter: parent.verticalCenter
+ }
+ spacing: 2
+
+ Text {
+ text: "Screen lock"
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true }
+ color: Theme.text
+ }
+
+ Text {
+ width: parent.width
+ wrapMode: Text.WordWrap
+ text: row.lockOn ? "The screen locks automatically when the session is idle."
+ : row.timed ? "Auto-lock is off until " + row.hhmm(Status.nolockUntil) + "."
+ : "Auto-lock stays off until you turn it back on."
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ color: Theme.subtext
+ }
+ }
+
+ Row {
+ id: controls
+ anchors { right: parent.right; verticalCenter: parent.verticalCenter }
+ spacing: 8
+
+ TextField {
+ id: minutes
+ width: 48
+ height: 28
+ placeholderText: "min"
+ horizontalAlignment: TextInput.AlignHCenter
+ color: Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ background: Rectangle {
+ radius: 6
+ color: Qt.alpha(Theme.surface, 0.9)
+ border.width: 1
+ border.color: Qt.alpha(Theme.text, 0.15)
+ }
+ validator: IntValidator { bottom: 1; top: 1440 }
+ }
+
+ Switch {
+ id: sw
+ checked: row.lockOn
+ onToggled: row.apply(!row.lockOn)
+ }
+ }
+
+ // The shared Switch's click handler writes `checked` directly, which drops
+ // the declarative binding above. Resync on any state change so the timer,
+ // statusctl or presentation mode moves the switch back into agreement.
+ onLockOnChanged: sw.checked = row.lockOn
+}
diff --git a/desktop/modules/status/README.md b/desktop/modules/status/README.md
index 2092890..571f859 100644
--- a/desktop/modules/status/README.md
+++ b/desktop/modules/status/README.md
@@ -1,17 +1,22 @@
# status
-Desktop modes as state: `dnd` and `presentation`, owned by the `Status`
-singleton and stored as files under `$XDG_RUNTIME_DIR`.
+Desktop modes as state: `dnd`, `presentation` and `nolock`, owned by the
+`Status` singleton and stored as files under `$XDG_RUNTIME_DIR`.
## The files are the interface
$XDG_RUNTIME_DIR/status.dnd
$XDG_RUNTIME_DIR/status.presentation
+ $XDG_RUNTIME_DIR/status.nolock
Each holds `0` or `1`; a missing file means off. That directory is tmpfs, so a
reboot resets every mode and there is no cleanup code. A shell restart does
not: the files outlive the process and the singleton reads them back.
+`nolock` is stored as "auto-lock disabled", which is the inverse of the switch
+the page shows. The file keeps the registry's "missing means off" rule, and the
+page inverts it so the label reads as the default: screen lock on.
+
Anything can read a mode with `cat`. `statusctl` is the convenience, not the
mechanism, which is why it keeps working while quickshell is down.
@@ -35,9 +40,18 @@ and reasserted when it returns.
`dnd` has none of its own. It is state the notification daemon reads.
-`presentation` sets `dnd`, asserts a Wayland idle inhibitor, and pauses
-breaktimer. Turning it off restores `dnd` to the value it had before rather
-than clearing it, so hand-set DND survives a presentation.
+`presentation` sets `dnd` and `nolock`, asserts a Wayland idle inhibitor, and
+pauses breaktimer. Turning it off restores `dnd` and `nolock` to the values
+they had before rather than clearing them, so hand-set DND or a hand-set
+disable survives a presentation.
+
+`nolock` disables auto-lock by holding the idle inhibitor. That is the only
+thing that stops hypridle's `loginctl lock-session`, so the mode governs
+idle-triggered locking and nothing else: the `SUPER+l` bind and the lock on
+suspend are separate paths and stay live. The page can set it permanently or
+for a number of minutes, in which case the singleton's timer re-enables it.
+The timer is shell-lifetime: a restart during a timed disable leaves the lock
+off until it is toggled, the same class of limit as `dndBeforePresentation`.
breaktimer owns `$XDG_RUNTIME_DIR/breaktimer.state`. This module calls
`breaktimer.sh pause|resume` and never writes that file: its daemon loop
diff --git a/desktop/modules/status/StatusPage.qml b/desktop/modules/status/StatusPage.qml
index 2fbdc9b..470856d 100644
--- a/desktop/modules/status/StatusPage.qml
+++ b/desktop/modules/status/StatusPage.qml
@@ -47,6 +47,16 @@ Column {
color: Qt.alpha(Theme.text, 0.08)
}
+ LockRow {
+ width: page.width
+ }
+
+ Rectangle {
+ width: page.width
+ height: 1
+ color: Qt.alpha(Theme.text, 0.08)
+ }
+
SnoozeRow {
width: page.width
}
diff --git a/desktop/modules/status/StatusTile.qml b/desktop/modules/status/StatusTile.qml
index c4b87d4..ab3a7af 100644
--- a/desktop/modules/status/StatusTile.qml
+++ b/desktop/modules/status/StatusTile.qml
@@ -19,5 +19,6 @@ Text {
color: Status.activeCount > 0 ? Theme.text : Theme.subtext
text: Status.presentation ? "Presenting"
: Status.dnd ? "Do not disturb"
+ : Status.nolock ? "No lock"
: "All clear"
}
diff --git a/desktop/modules/status/statusctl b/desktop/modules/status/statusctl
index 8877ff3..571beef 100755
--- a/desktop/modules/status/statusctl
+++ b/desktop/modules/status/statusctl
@@ -25,7 +25,7 @@
set -u
-MODES="dnd presentation"
+MODES="dnd presentation nolock"
DIR="${XDG_RUNTIME_DIR:-/tmp}"
usage() {
diff --git a/desktop/modules/status/test-statusctl.sh b/desktop/modules/status/test-statusctl.sh
index 82e2de1..7b1b188 100755
--- a/desktop/modules/status/test-statusctl.sh
+++ b/desktop/modules/status/test-statusctl.sh
@@ -56,6 +56,13 @@ check "toggle from 0" "1" "$("$ctl" dnd get)"
"$ctl" dnd set 0
check "set 0 writes the file" "0" "$("$ctl" dnd get)"
+# nolock is a mode like any other: the mechanism is generic, this only pins
+# that the name is accepted by the mode list.
+"$ctl" nolock set 1
+check "nolock set 1" "1" "$("$ctl" nolock get)"
+"$ctl" nolock toggle
+check "nolock toggle" "0" "$("$ctl" nolock get)"
+
# An unknown mode is an error, not a silent success: a typo must not look
# like a mode that is off.
"$ctl" nosuch get >/dev/null 2>&1