Disable internal laptop monitor when lid is closed
Smithay recently got the lid close event, so that can be used. Need to verify that it interacts properly with systemd lid sleep and whatnot.
My thinkpad z16 gen 1 goes to sleep without any config when lid is closed.
It shouldn't if you have an external monitor connected. (Also this is managed by logind)
As of niri 0.1.7, my screen does sleep on close, but I would also really like a way to automatically run swaylock or something like that on lid close.
Here's a bit of a workaround I did -- I modified this to be:
use std::process::Command;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
println!("Cmd example: lid_event /proc/acpi/button/lid/LID0/state 'your_command_here'");
std::process::exit(1);
}
let state_path = &args[1];
let command = args[2..].join(" ");
let mut last_state = is_open(state_path);
loop {
std::thread::sleep(std::time::Duration::new(1, 0));
let state = is_open(state_path);
if last_state && !state {
println!("Lid closed, executing command: {}", command);
match Command::new("sh")
.arg("-c")
.arg(&command)
.output()
{
Ok(output) => {
println!("Command executed successfully");
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}
Err(e) => println!("Failed to execute command: {}", e),
}
}
last_state = state;
}
}
fn is_open(filename: &str) -> bool {
let contents = std::fs::read_to_string(filename)
.expect("failed reading the state file");
contents.contains("open")
}
Compiled it as lid_event, moved it to /usr/bin, then added
spawn-at-startup "lid_event" "/proc/acpi/button/lid/LID/state" "swaylockniri"
to my Niri config (swaylockniri is just a program that does the Niri transition then runs swaylock with options:)
#!/usr/bin/env bash
niri msg action do-screen-transition
swaylock \
-fSle \
--indicator \
--indicator-radius 110 \
--indicator-idle-visible \
--clock \
--font 'Cartograph CF' \
--timestr "%-l:%M %p" \
--datestr "%a, %B %-e" \
--effect-blur 30x10 \
--ring-color 31748f \
--key-hl-color 9ccfd8 \
--line-color 908caa \
--text-color e0def4 \
--inside-color 00000000 \
--separator-color 00000000
@ThatOneCalculator so you weren't able to do this with logind? let me know if you need help with it
I don't actually know how to do this properly with logind lol
To get started https://man.archlinux.org/man/logind.conf.5.en search here for idle and lidswitch (systemd is awesome, thank you systemd)
@YaLTeR come to think of it, why not do this with logind and avoid redoing it in niri? if systemd dependecy is undesirable then elogind can be used?
This issue is not about that, it's about disabling the internal monitor when you close the lid while having an external monitor connected.
Yes you are right, I confused his issue @ThatOneCalculator mentioned above with this.
@YaLTeR any pointers on where to get started on this? i imagine the place to start is to acquire a low-level lock on lidswitch from logind, am i saying it correctly?
In input.rs, handle this event: https://smithay.github.io/smithay/smithay/backend/input/enum.Switch.html#variant.Lid
Apply output on/off on the right output similar to how niri msg output eDP-1 off would work.
Then do a lot of testing:
- Closing/opening with no external monitors suspends/unsuspends and nothing breaks or gets stuck.
- Closing/opening with external monitors turns off/on the internal output (can't switch to it, disabled in
niri msg outputs). - Closing with external, suspending, unsuspending, preserves the fact that the monitor is disabled.
- Closing with external, suspending, opening, unsuspending turns it on.
- Closing without external, plugging in external, unsuspending turns it off.
Should all this be user configurable?
I can see 1 and 2 not being wanted by everyone, at least sometimes, though they are sensible as defaults.
Implementation wise I think we need to make a function which takes as input all these parameters and does the appropriate thing (basically a big switch-case), and then this function gets called whenever any of lid-switch, suspend/resume, external monitors added/removed etc. have a change.
This also looks like it will translate into an easy configuration UI considering how much complicated/complex logic for this can turn into. Sensible defaults definitely a must-have.
1 is not handled by niri, rather by logind. 2 can have some config flag but I'm not sure how necessary that is.
There's also a LidClosed property provided by logind on the org.freedesktop.login1 d-bus interface. As far as I can tell, it uses the same underlying evdev event as the libinput switch.
According to libinput docs, the switch defaults to off and if it's on during initialization, libinput will send a switch toggle event right away. In case of the laptop lid, it is assumed to be open, and libinput will send a close event right away if necessary.
Implemented in cd90dfc7be11147936c2b637ae822ff0adf5a7b7. Everyone following this issue, please give it a good test. My comment above gives some ideas on what to test.
So far I've hit one issue, that closing/opening the lid just to put the laptop to sleep will reset the focused workspace, but that's just #63 showing up now that the monitor is disabled/enabled. EDIT: implemented preserving the workspace.
Another thing is that closing/opening the lid just to put the laptop to sleep will make the screen locker re-create its surface on resume (since the monitor is disabled/enabled). Not necessarily an issue, but a little weird I guess.
One way would be to not disable/enable when the laptop monitor is the only monitor connected. But I would like to avoid the complexity in the code that this would entail. Since it would mean that on every monitor connect/disconnect, niri will need to potentially connect/disconnect other monitors.
EDIT: ended up doing this change in 5ff8b89aafca0143866f93dd7e2f27aee6c2b0f5.