niri icon indicating copy to clipboard operation
niri copied to clipboard

Disable internal laptop monitor when lid is closed

Open YaLTeR opened this issue 2 years ago • 14 comments

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.

YaLTeR avatar Jan 30 '24 14:01 YaLTeR

My thinkpad z16 gen 1 goes to sleep without any config when lid is closed.

salman-farooq-sh avatar Jun 30 '24 05:06 salman-farooq-sh

It shouldn't if you have an external monitor connected. (Also this is managed by logind)

YaLTeR avatar Jun 30 '24 05:06 YaLTeR

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.

ThatOneCalculator avatar Aug 06 '24 22:08 ThatOneCalculator

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 avatar Aug 06 '24 23:08 ThatOneCalculator

@ThatOneCalculator so you weren't able to do this with logind? let me know if you need help with it

salman-farooq-sh avatar Aug 07 '24 06:08 salman-farooq-sh

I don't actually know how to do this properly with logind lol

ThatOneCalculator avatar Aug 07 '24 06:08 ThatOneCalculator

To get started https://man.archlinux.org/man/logind.conf.5.en search here for idle and lidswitch (systemd is awesome, thank you systemd)

salman-farooq-sh avatar Aug 07 '24 06:08 salman-farooq-sh

@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?

salman-farooq-sh avatar Aug 07 '24 06:08 salman-farooq-sh

This issue is not about that, it's about disabling the internal monitor when you close the lid while having an external monitor connected.

YaLTeR avatar Aug 07 '24 06:08 YaLTeR

Yes you are right, I confused his issue @ThatOneCalculator mentioned above with this.

salman-farooq-sh avatar Aug 07 '24 06:08 salman-farooq-sh

@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?

SalmanEsketchers avatar Aug 09 '24 01:08 SalmanEsketchers

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:

  1. Closing/opening with no external monitors suspends/unsuspends and nothing breaks or gets stuck.
  2. Closing/opening with external monitors turns off/on the internal output (can't switch to it, disabled in niri msg outputs).
  3. Closing with external, suspending, unsuspending, preserves the fact that the monitor is disabled.
  4. Closing with external, suspending, opening, unsuspending turns it on.
  5. Closing without external, plugging in external, unsuspending turns it off.

YaLTeR avatar Aug 09 '24 05:08 YaLTeR

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.

salman-farooq-sh avatar Aug 10 '24 07:08 salman-farooq-sh

1 is not handled by niri, rather by logind. 2 can have some config flag but I'm not sure how necessary that is.

YaLTeR avatar Aug 10 '24 07:08 YaLTeR

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.

YaLTeR avatar Nov 04 '24 06:11 YaLTeR

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.

YaLTeR avatar Nov 04 '24 07:11 YaLTeR

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.

YaLTeR avatar Nov 05 '24 07:11 YaLTeR

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.

YaLTeR avatar Nov 05 '24 07:11 YaLTeR