Skip to content

Commit 912325d

Browse files
committed
feat: power manager module, refactor ghaf-powercontrol
add new power-manager module to override default logind power management: - implement suspend signal interception and handling - implement shutdown/reboot signal interception and handling - add configurable service activation through nix options - use ghaf-powercontrol for power state management ghaf-powercontrol changes: - refactor to handle any wayland compositor - fix premature exit during suspend if wlopm fails Signed-off-by: Kajus Naujokaitis <kajus.naujokaitis@unikie.com>
1 parent b7cd260 commit 912325d

File tree

3 files changed

+171
-9
lines changed

3 files changed

+171
-9
lines changed

modules/desktop/graphics/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
./ewwbar.nix
99
./fonts.nix
1010
./login-manager.nix
11+
./power-manager.nix
1112
./boot.nix
1213
];
1314
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Copyright 2022-2025 TII (SSRC) and the Ghaf contributors
2+
# SPDX-License-Identifier: Apache-2.0
3+
# This module overrides logind power management using ghaf-powercontrol.
4+
# The specific operations overriden are:
5+
# - Suspend
6+
# - Shutdown
7+
# - Reboot
8+
{
9+
lib,
10+
pkgs,
11+
config,
12+
...
13+
}:
14+
let
15+
cfg = config.ghaf.graphics.power-manager;
16+
inherit (lib)
17+
mkIf
18+
mkOption
19+
types
20+
;
21+
22+
ghaf-powercontrol = pkgs.ghaf-powercontrol.override { ghafConfig = config.ghaf; };
23+
24+
logindSuspendListener = pkgs.writeShellApplication {
25+
# This listener monitors systemd's suspend signals and delays the suspend process
26+
# While the process is delayed, we use ghaf-powercontrol to handle the suspend operation instead
27+
# TODO: Investigate if suspension can be cancelled entirely if caught. Some notes on that:
28+
# - `--mode=block` does not work, as it will block suspension entirely and no signal will be sent
29+
# - `--mode=delay` works, but the system will still suspend after the delay
30+
name = "logind-suspend-listener";
31+
runtimeInputs = [
32+
pkgs.dbus
33+
pkgs.systemd
34+
ghaf-powercontrol
35+
];
36+
text = ''
37+
systemd-inhibit --what=sleep --who="ghaf-powercontrol" \
38+
--why="Handling ghaf suspend" --mode=delay \
39+
dbus-monitor --system "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" | \
40+
while read -r line; do
41+
if echo "$line" | grep -q "boolean true"; then
42+
echo "Found prepare for sleep signal"
43+
echo "Suspending via ghaf-powercontrol"
44+
ghaf-powercontrol suspend
45+
fi
46+
done
47+
'';
48+
};
49+
50+
logindShutdownListener = pkgs.writeShellApplication {
51+
# This listener monitors systemd's shutdown/reboot signals and delays the process
52+
# While the process is delayed, we use ghaf-powercontrol to handle the shutdown/reboot operation instead
53+
name = "logind-shutdown-listener";
54+
runtimeInputs = [
55+
pkgs.dbus
56+
pkgs.systemd
57+
ghaf-powercontrol
58+
];
59+
text = ''
60+
systemd-inhibit --what=shutdown --who="ghaf-powercontrol" \
61+
--why="Handling ghaf shutdown/reboot" --mode=delay \
62+
dbus-monitor --system "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForShutdownWithMetadata'" | \
63+
while read -r line; do
64+
if echo "$line" | grep -q "boolean true"; then
65+
echo "Found prepare for shutdown signal. Checking type..."
66+
while read -r subline; do
67+
if echo "$subline" | grep -q "reboot"; then
68+
echo "Found type: reboot"
69+
echo "Rebooting via ghaf-powercontrol"
70+
ghaf-powercontrol reboot
71+
elif echo "$subline" | grep -q "poweroff"; then
72+
echo "Found type: power-off"
73+
echo "Powering off via ghaf-powercontrol"
74+
ghaf-powercontrol poweroff
75+
fi
76+
done
77+
fi
78+
done
79+
'';
80+
};
81+
in
82+
{
83+
options.ghaf.graphics.power-manager = {
84+
enable = mkOption {
85+
description = ''
86+
Override logind power management using ghaf-powercontrol
87+
'';
88+
type = types.bool;
89+
default = false;
90+
};
91+
enableSuspendListener = mkOption {
92+
description = ''
93+
Enable the suspend signal listener service
94+
'';
95+
type = types.bool;
96+
default = true;
97+
};
98+
99+
enableShutdownListener = mkOption {
100+
description = ''
101+
Enable the shutdown/reboot signal listener service
102+
'';
103+
type = types.bool;
104+
default = true;
105+
};
106+
};
107+
108+
config = mkIf cfg.enable {
109+
systemd.services = {
110+
logind-shutdown-listener = mkIf cfg.enableShutdownListener {
111+
enable = true;
112+
description = "Ghaf logind shutdown listener";
113+
serviceConfig = {
114+
Type = "simple";
115+
Restart = "always";
116+
RestartSec = "5";
117+
ExecStart = "${lib.getExe logindShutdownListener}";
118+
};
119+
partOf = [ "graphical.target" ];
120+
wantedBy = [ "graphical.target" ];
121+
};
122+
123+
logind-suspend-listener = mkIf cfg.enableSuspendListener {
124+
# Currently system continues to suspend even after ghaf-powercontrol suspend is called
125+
# If running on host:
126+
# - Might result in two suspension requests in a row
127+
# If running on VM:
128+
# - Will result in `ghaf-powercontrol suspend` first. If woken up see next point
129+
# - Will result in a non-functional VM suspend, which gets cancelled almost immediately
130+
# - End result - System suspends, wakes up, attempts to suspend again but wakes up immediately
131+
# NOTE:
132+
# As a system service, this will run as root
133+
# This means ghaf-powercontrol will run as root and therefore fail to
134+
# find and turn off the display as part of the suspension procedures
135+
enable = true;
136+
description = "Ghaf logind suspend listener";
137+
serviceConfig = {
138+
Type = "simple";
139+
Restart = "always";
140+
RestartSec = "5";
141+
ExecStart = "${lib.getExe logindSuspendListener}";
142+
};
143+
partOf = [ "graphical.target" ];
144+
wantedBy = [ "graphical.target" ];
145+
};
146+
};
147+
};
148+
}

packages/ghaf-powercontrol/package.nix

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,12 @@
1313
}:
1414
let
1515
useGivc = ghafConfig.givc.enable;
16-
# Handle Wayland display power state
17-
waylandDisplayCmd = command: ''
18-
WAYLAND_DISPLAY=/run/user/${builtins.toString ghafConfig.users.loginUser.uid}/wayland-0 \
19-
wlopm --${command} '*'
20-
'';
2116
in
2217
writeShellApplication {
2318
name = "ghaf-powercontrol";
2419

20+
bashOptions = [ ];
21+
2522
runtimeInputs = [
2623
systemd
2724
wlopm
@@ -50,6 +47,23 @@ writeShellApplication {
5047
exit 0
5148
fi
5249
50+
try_wlopm() {
51+
local cmd="$1"
52+
local uid=${toString ghafConfig.users.loginUser.uid}
53+
54+
# Try each wayland-N socket
55+
for i in {0..9}; do
56+
export WAYLAND_DISPLAY="/run/user/$uid/wayland-$i"
57+
if [ -e "$WAYLAND_DISPLAY" ]; then
58+
if wlopm "$cmd" 2>/dev/null; then
59+
return 0
60+
fi
61+
fi
62+
done
63+
64+
return 1
65+
}
66+
5367
case "$1" in
5468
reboot|poweroff)
5569
${if useGivc then "givc-cli ${ghafConfig.givc.cliArgs}" else "systemctl"} "$1"
@@ -61,15 +75,14 @@ writeShellApplication {
6175
# Lock sessions
6276
loginctl lock-session
6377
64-
# Switch off display before suspension
65-
${waylandDisplayCmd "off"}
78+
try_wlopm "--off '*'" || true
6679
6780
# Send suspend command to host, ensure screen is on in case of failure
6881
${if useGivc then "givc-cli ${ghafConfig.givc.cliArgs}" else "systemctl"} suspend \
69-
|| ${waylandDisplayCmd "on"}
82+
|| try_wlopm "--on '*'" || true
7083
7184
# Switch on display on wakeup
72-
${waylandDisplayCmd "on"}
85+
try_wlopm "--on '*'" || true
7386
''
7487
else
7588
''

0 commit comments

Comments
 (0)