aboutsummaryrefslogtreecommitdiffstats
path: root/shared
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-15 12:11:03 +0200
committerDanilo M. <danix@danix.xyz>2026-09-15 12:11:03 +0200
commite78962e28eafb2cb0b1bf8304dd1e0c4936044cf (patch)
treee9c1364c342d19da61e39f31965271ffd5e02ad6 /shared
parent0aa0328a0fa3e80768efd7f76f713fe30e5ca8b2 (diff)
downloadquickshell-e78962e28eafb2cb0b1bf8304dd1e0c4936044cf.tar.gz
quickshell-e78962e28eafb2cb0b1bf8304dd1e0c4936044cf.zip
feat(desktop): add presentation mode and its effects
Presentation mode sets DND, asserts a Wayland idle inhibitor and pauses breaktimer. The effects hang off the mode property rather than the setter, so a mode set with statusctl while the drawer is closed asserts them too. DND has two writers once presentation mode exists, so turning presentation off restores the value DND had before rather than clearing it, or an afternoon of hand-set DND would vanish when a talk ends. That prior value lives in the singleton, not in a file: it means nothing once presentation mode is off, and presentation mode does not survive a reboot. breaktimer owns its own state file and is driven only through its verbs. Two writers on that file would race with its daemon loop.
Diffstat (limited to 'shared')
-rw-r--r--shared/Status.qml63
1 files changed, 56 insertions, 7 deletions
diff --git a/shared/Status.qml b/shared/Status.qml
index 03194e7..5df1847 100644
--- a/shared/Status.qml
+++ b/shared/Status.qml
@@ -13,6 +13,7 @@ pragma Singleton
import Quickshell
import Quickshell.Io
+import Quickshell.Wayland
import QtQuick
// Desktop modes as state. One file per mode under $XDG_RUNTIME_DIR, holding
@@ -21,27 +22,74 @@ import QtQuick
//
// The files are the interface, not this singleton: statusctl reads and writes
// them directly so it works while the shell is down, and the FileView watch
-// means an external write repaints the drawer with no polling.
+// means an external write repaints the drawer with no polling. It also means
+// a mode set from outside still fires its effects, because the watch reaches
+// the same handler a tile click would.
Singleton {
id: root
readonly property string dir: Quickshell.env("XDG_RUNTIME_DIR") || "/tmp"
readonly property bool dnd: dndFile.value
+ readonly property bool presentation: presFile.value
- // Number of modes currently on. The tile shows this.
- readonly property int activeCount: (root.dnd ? 1 : 0)
+ readonly property int activeCount: (root.dnd ? 1 : 0) + (root.presentation ? 1 : 0)
+
+ // Set once by shell.qml. IdleInhibitor does nothing with a null window,
+ // and the singleton has no window of its own to offer.
+ property var inhibitWindow: null
+
+ // What dnd was before presentation mode turned it on, so turning
+ // presentation mode off restores it rather than clearing it. Held here
+ // rather than in a file: it is meaningful only while presentation mode is
+ // on, and presentation mode does not survive a reboot.
+ property bool dndBeforePresentation: false
function setMode(name, on) {
- if (name === "dnd") dndFile.write(on);
+ if (name === "dnd") {
+ dndFile.write(on);
+ } else if (name === "presentation") {
+ presFile.write(on);
+ }
}
function toggleMode(name) {
- if (name === "dnd") dndFile.write(!root.dnd);
+ if (name === "dnd") root.setMode("dnd", !root.dnd);
+ else if (name === "presentation") root.setMode("presentation", !root.presentation);
+ }
+
+ // Effects follow the mode rather than the setter, so a mode set by
+ // statusctl while the drawer is closed asserts them too.
+ onPresentationChanged: {
+ if (root.presentation) {
+ root.dndBeforePresentation = root.dnd;
+ root.setMode("dnd", true);
+ root.runBreaktimer("pause");
+ } else {
+ root.setMode("dnd", root.dndBeforePresentation);
+ root.runBreaktimer("resume");
+ }
+ }
+
+ function runBreaktimer(verb) {
+ breakProc.command = ["breaktimer.sh", verb];
+ breakProc.running = false;
+ breakProc.running = true;
+ }
+
+ // breaktimer owns its own state file; this only calls its verbs. Two
+ // writers on that file would race with its daemon loop, which rewrites it
+ // on every phase change.
+ Process { id: breakProc }
+
+ // Wayland idle inhibit. The compositor advertises
+ // zwp_idle_inhibit_manager_v1 and hypridle honours it, so no D-Bus path
+ // is needed even though elogind runs here.
+ IdleInhibitor {
+ window: root.inhibitWindow
+ enabled: root.presentation && root.inhibitWindow !== null
}
- // One mode file. Reads "1" as true and anything else, including a missing
- // file, as false.
component ModeFile: FileView {
id: mf
@@ -77,4 +125,5 @@ Singleton {
}
ModeFile { id: dndFile; path: root.dir + "/status.dnd" }
+ ModeFile { id: presFile; path: root.dir + "/status.presentation" }
}