From 933a4cf851009d4a8c1b4ce7725556d69d4b82db Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Sun, 3 Sep 2023 20:02:01 +0200 Subject: [PATCH] Add support for controlling the power LED --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 3 +- src/command.rs | 90 +++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 38 +++++++++++++++++++-- 5 files changed, 129 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1b0cb6..d4a846b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,7 +181,7 @@ dependencies = [ [[package]] name = "keylightd" -version = "1.0.2" +version = "1.1.0" dependencies = [ "anyhow", "argh", diff --git a/Cargo.toml b/Cargo.toml index ca4b968..bfbc821 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keylightd" -version = "1.0.2" +version = "1.1.0" edition = "2021" license = "0BSD" readme = "README.md" diff --git a/README.md b/README.md index b674451..9f28839 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,14 @@ Note that `keylightd` needs to be run as root, since it accesses the Embedded Co `keylightd` takes the following command-line arguments: ``` -Usage: keylightd [--brightness ] [--timeout ] +Usage: keylightd [--brightness ] [--timeout ] [--power] keylightd - automatic keyboard backlight daemon for Framework laptops Options: --brightness brightness level when active (0-100) [default=30] --timeout activity timeout in seconds [default=10] + --power also control the power LED in the fingerprint module --help display usage information ``` diff --git a/src/command.rs b/src/command.rs index 2abadc1..1418b6c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,3 +1,11 @@ +//! Commands for the Embedded Controller. +//! +//! Reference: https://github.com/FrameworkComputer/EmbeddedController/blob/hx20-hx30/include/ec_commands.h +//! +//! (command IDs begin with `EC_CMD_`) + +#![allow(dead_code)] + use bytemuck::{NoUninit, Pod, Zeroable}; /// Trait implemented by Embedded Controller commands. @@ -7,7 +15,7 @@ pub trait Command: NoUninit { /// Command version. /// - /// Some commands come in multiple versions (although none of the ones supported here). + /// Some commands come in multiple versions. const VERSION: u32 = 0; /// The associated response type. @@ -23,6 +31,7 @@ pub enum Cmd { // ... GetKeyboardBacklight = 0x0022, SetKeyboardBacklight = 0x0023, + LedControl = 0x0029, } ////////////////////////////////// @@ -106,3 +115,82 @@ impl Command for SetKeyboardBacklight { const CMD: Cmd = Cmd::SetKeyboardBacklight; type Response = SetKeyboardBacklightResponse; } + +////////////////////////////////// +// LedControl +////////////////////////////////// + +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct LedControl { + pub led_id: LedId, + pub flags: LedFlags, + pub brightness: LedBrightnesses, +} + +impl Command for LedControl { + const CMD: Cmd = Cmd::LedControl; + // ectool always uses version 1 for this command, version 0 does not work and returns unexpected + // data. + const VERSION: u32 = 1; + type Response = LedControlResponse; +} + +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(transparent)] +pub struct LedId(u8); + +impl LedId { + pub const BATTERY: Self = Self(0); + pub const POWER: Self = Self(1); + pub const ADAPTER: Self = Self(2); + pub const LEFT: Self = Self(3); + pub const RIGHT: Self = Self(4); + pub const RECOVERY_HW_REINIT: Self = Self(5); + pub const SYSRQ_DEBUG: Self = Self(6); +} + +#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)] +#[repr(transparent)] +pub struct LedFlags(u8); + +impl LedFlags { + pub const NONE: Self = Self(0); + pub const QUERY: Self = Self(1 << 0); + pub const AUTO: Self = Self(1 << 1); +} + +pub struct LedColor(u8); + +impl LedColor { + pub const RED: Self = Self(0); + pub const GREEN: Self = Self(1); + pub const BLUE: Self = Self(2); + pub const YELLOW: Self = Self(3); + pub const WHITE: Self = Self(4); + pub const AMBER: Self = Self(5); + pub const COUNT: usize = 6; +} + +#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)] +#[repr(transparent)] +pub struct LedBrightnesses { + raw: [u8; LedColor::COUNT], +} + +impl LedBrightnesses { + pub fn single(color: LedColor, brightness: u8) -> Self { + Self::default().set(color, brightness) + } + + pub fn set(mut self, color: LedColor, brightness: u8) -> Self { + self.raw[usize::from(color.0)] = brightness; + self + } +} + +#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)] +#[repr(transparent)] +pub struct LedControlResponse { + brightness: LedBrightnesses, +} diff --git a/src/main.rs b/src/main.rs index 15cdd7c..77e5813 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,13 @@ use argh::FromArgs; use command::{GetKeyboardBacklight, SetKeyboardBacklight}; use ec::EmbeddedController; +use crate::command::{LedBrightnesses, LedControl, LedFlags, LedId}; + mod command; mod ec; /// keylightd - automatic keyboard backlight daemon for Framework laptops -#[derive(FromArgs)] +#[derive(Debug, FromArgs)] struct Args { /// brightness level when active (0-100) [default=30] #[argh(option, default = "30", from_str_fn(parse_brightness))] @@ -22,6 +24,10 @@ struct Args { /// activity timeout in seconds [default=10] #[argh(option, default = "10")] timeout: u32, + + /// also control the power LED in the fingerprint module + #[argh(switch)] + power: bool, } fn parse_brightness(s: &str) -> Result { @@ -34,10 +40,18 @@ fn parse_brightness(s: &str) -> Result { fn main() -> anyhow::Result<()> { env_logger::builder() - .filter_module(env!("CARGO_PKG_NAME"), log::LevelFilter::Info) + .filter_module( + env!("CARGO_PKG_NAME"), + if cfg!(debug_assertions) { + log::LevelFilter::Debug + } else { + log::LevelFilter::Info + }, + ) .init(); let args: Args = argh::from_env(); + log::debug!("args={:?}", args); let ec = EmbeddedController::open()?; let fade_to = |target: u8| -> io::Result<()> { @@ -50,7 +64,27 @@ fn main() -> anyhow::Result<()> { cur += 1; } + if args.power { + // The power LED cannot be faded from software (although the beta BIOS apparently + // has a switch for dimming it, so maybe it'll work with the next BIOS update). + // So instead, we treat 0 as off and set it back to auto for any non-zero value. + if cur == 0 { + ec.command(LedControl { + led_id: LedId::POWER, + flags: LedFlags::NONE, + brightness: LedBrightnesses::default(), + })?; + } else if cur == 1 { + ec.command(LedControl { + led_id: LedId::POWER, + flags: LedFlags::AUTO, + brightness: LedBrightnesses::default(), + })?; + } + } + ec.command(SetKeyboardBacklight { percent: cur })?; + thread::sleep(Duration::from_millis(3)); } Ok(())