Skip to content

Commit 38030db

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 38030db

File tree

3 files changed

+167
-9
lines changed

3 files changed

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

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)