1use std::{fs, io, path::PathBuf, thread::sleep, time::Duration};
4
5#[derive(thiserror::Error, Debug)]
7pub enum Error {
8 #[error("Unexpected IO error '{0}'")]
9 IoError(#[from] io::Error),
10 #[error("Failed to interpret LED state with path '{0}'")]
11 InvalidLedState(PathBuf),
12 #[error("Unexpected UInput Error '{0}'")]
13 UInputError(#[from] uinput::Error),
14 #[error("No valid LEDs found")]
15 NoLedsFound,
16 #[error("Failed to change numlock state")]
17 FailedToPressNumlock,
18}
19
20pub type Res<T> = std::result::Result<T, Error>;
22
23#[derive(Debug, Copy, Clone, Eq, PartialEq)]
25pub enum State {
26 OFF,
27 ON,
28}
29
30impl State {
31 pub fn toggled(&self) -> State {
32 match self {
33 State::OFF => State::ON,
34 State::ON => State::OFF,
35 }
36 }
37}
38
39pub fn get_led_state(path: &PathBuf) -> Res<State> {
41 let brightness_bytes = fs::read(path)?;
42 let Ok(state) = String::from_utf8(brightness_bytes.clone()) else {
43 return Err(Error::InvalidLedState(path.clone()));
44 };
45 if state == "0\n" {
46 Ok(State::OFF)
47 } else if state == "1\n" {
48 Ok(State::ON)
49 } else {
50 return Err(Error::InvalidLedState(path.clone()));
51 }
52}
53
54pub fn get_led_path_and_state() -> Res<(PathBuf, State)> {
56 const LEDS: &str = "/sys/class/leds/";
57 let entries = std::fs::read_dir(LEDS)?;
58 for entry in entries {
59 let entry = entry?;
60 let file_name_os_str = entry.file_name();
61 let file_name = file_name_os_str.to_string_lossy();
62 if !file_name.contains("numlock") {
63 continue;
64 }
65 let metadata = entry.metadata()?;
66 let metadata = if metadata.is_symlink() {
67 fs::metadata(entry.path())?
68 } else {
69 metadata
70 };
71 if !metadata.is_dir() {
72 continue;
73 }
74 let brightness_path = entry.path().join("brightness");
75 let state = get_led_state(&brightness_path)?;
76 return Ok((brightness_path, state));
77 }
78
79 Err(Error::NoLedsFound)
80}
81
82pub fn press_numlock(led_path_and_state: Option<(&PathBuf, State)>) -> Res<()> {
85 let device = uinput::default()?;
86 let device = device.name("numlocklrs")?;
87
88 let numlock_key = uinput::event::Keyboard::Key(uinput::event::keyboard::Key::NumLock);
89 let device = device.event(numlock_key)?;
90 let mut device = device.create()?;
91
92 sleep(Duration::from_micros(100000));
93
94 device.synchronize()?;
95 device.press(&numlock_key)?;
96 device.synchronize()?;
97 device.release(&numlock_key)?;
98 device.synchronize()?;
99
100 if let Some((path, state)) = led_path_and_state {
101 for _ in 0..10 {
102 sleep(Duration::from_micros(10000));
103 if get_led_state(&path)? != state {
104 return Ok(());
105 }
106 }
107 Err(Error::FailedToPressNumlock)
108 } else {
109 sleep(Duration::from_micros(100000));
110 Ok(())
111 }
112}