diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-15 12:11:03 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-15 12:11:03 +0200 |
| commit | e78962e28eafb2cb0b1bf8304dd1e0c4936044cf (patch) | |
| tree | e9c1364c342d19da61e39f31965271ffd5e02ad6 /shared | |
| parent | 0aa0328a0fa3e80768efd7f76f713fe30e5ca8b2 (diff) | |
| download | quickshell-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.qml | 63 |
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" } } |
