Fork ii's Session singleton and add a sessionctl.* surface.
The IPC bug is the important half. Quickshell marshals exactly five types
(string/int/bool/double/color) and maps a `var` return to VOID, discarding
the payload with no error -- src/io/ipc.cpp, "void and var get mixed by qml
engine". dock.*, shell.* and apps.* were all declared `: var`, so they
registered as `(): void` and returned nothing at all. The {ok, reason}
contract has never once reached a caller. All of them now return JSON as a
string, which is what actually crosses the socket.
Session: upstream fires `systemctl X || loginctl X` detached and throws the
exit code away. Fine on a desktop with someone at the keyboard, not fine on
a phone where the shell is the session manager and a verb that silently does
nothing leaves you believing the machine is suspending when it isn't. So:
probe loginctl/systemctl/hibernate once instead of assuming, run verbs
through a Process that logs the exit code, and refuse honestly when the
machine can't do the thing (the phone has no swap -- hibernate now says so
instead of no-opping). Every upstream verb keeps its name and call sites.
Inhibits carry a mandatory reason and get a cookie; state() lists who is
holding the machine awake and why. "Why didn't it sleep" is now answerable.
unlock() is refused by design -- the lock is the credential gate, so no IPC
caller routes around the PIN pad.
Named sessionctl, not session: ii's SessionScreen already owns "session",
and quickshell drops duplicate targets silently rather than erroring.
Idle: drop the 2>/dev/null and run hypridle through a Process, so a unit
that fails to come back is a log line instead of a flat battery.
Verified on the laptop: inhibit stops hypridle, uninhibit brings it back.
87 lines
2.9 KiB
QML
87 lines
2.9 KiB
QML
pragma Singleton
|
|
import qs.modules.common
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Wayland
|
|
|
|
/**
|
|
* A nice wrapper for date and time strings.
|
|
*/
|
|
Singleton {
|
|
id: root
|
|
|
|
property alias inhibit: idleInhibitor.enabled
|
|
inhibit: false
|
|
|
|
// Pixel 3: the Wayland idle-inhibitor on an invisible 0x0 surface is
|
|
// not honored by our compositor build, and screen-off is driven by
|
|
// hypridle scripts anyway — so Keep System Awake stops/starts hypridle.
|
|
onInhibitChanged: {
|
|
// Keep System Awake toggles the systemd-managed hypridle unit.
|
|
// (Was pkill/setsid, which orphaned hypridle across Hyprland restarts
|
|
// and wedged wake — see hyprland.lua hyprland.start.)
|
|
//
|
|
// Runs through a Process, not execDetached, because this is the
|
|
// mechanism that decides whether the phone sleeps: if the unit fails
|
|
// to come back we need to know, not discover it via a flat battery.
|
|
// reset-failed is expected to be a no-op when the unit is healthy, so
|
|
// its failure is not interesting — but the restart's is, so the two
|
|
// are separated rather than hidden behind one silencing redirect.
|
|
hypridleProc.action = root.inhibit ? "stop" : "restart";
|
|
hypridleProc.command = ["sh", "-c", root.inhibit
|
|
? "systemctl --user stop hypridle.service"
|
|
: "systemctl --user reset-failed hypridle.service || true; systemctl --user restart hypridle.service"];
|
|
hypridleProc.running = true;
|
|
}
|
|
|
|
Process {
|
|
id: hypridleProc
|
|
property string action: ""
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (exitCode !== 0)
|
|
console.log(`[idle] hypridle ${hypridleProc.action} failed (exit ${exitCode}); `
|
|
+ `idle handling may be wedged — inhibit=${root.inhibit}`);
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: Persistent
|
|
function onReadyChanged() {
|
|
if (!Persistent.isNewHyprlandInstance) {
|
|
root.inhibit = Persistent.states.idle.inhibit;
|
|
} else {
|
|
Persistent.states.idle.inhibit = root.inhibit;
|
|
}
|
|
}
|
|
}
|
|
|
|
function toggleInhibit(active = null) {
|
|
if (active !== null) {
|
|
root.inhibit = active;
|
|
} else {
|
|
root.inhibit = !root.inhibit;
|
|
}
|
|
Persistent.states.idle.inhibit = root.inhibit;
|
|
}
|
|
|
|
IdleInhibitor {
|
|
id: idleInhibitor
|
|
window: PanelWindow {
|
|
// Inhibitor requires a "visible" surface
|
|
// Actually not lol
|
|
implicitWidth: 0
|
|
implicitHeight: 0
|
|
color: "transparent"
|
|
// Just in case...
|
|
anchors {
|
|
right: true
|
|
bottom: true
|
|
}
|
|
// Make it not interactable
|
|
mask: Region {
|
|
item: null
|
|
}
|
|
}
|
|
}
|
|
}
|