YaLTeR / niri

A scrollable-tiling Wayland compositor.
https://matrix.to/#/#niri:matrix.org
GNU General Public License v3.0
3.29k stars 93 forks source link

Disable internal laptop monitor when lid is closed #145

Open YaLTeR opened 7 months ago

YaLTeR commented 7 months ago

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.

salman-farooq-sh commented 2 months ago

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

YaLTeR commented 2 months ago

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

ThatOneCalculator commented 1 month ago

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 commented 1 month ago

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
salman-farooq-sh commented 1 month ago

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

ThatOneCalculator commented 1 month ago

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

salman-farooq-sh commented 1 month ago

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 commented 1 month ago

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

YaLTeR commented 1 month ago

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

salman-farooq-sh commented 1 month ago

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

SalmanEsketchers commented 4 weeks ago

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

YaLTeR commented 4 weeks ago

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.
salman-farooq-sh commented 3 weeks ago

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.

YaLTeR commented 3 weeks ago

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