Add support for controlling the power LED
This commit is contained in:
parent
31be7e68a7
commit
933a4cf851
5 changed files with 129 additions and 6 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -181,7 +181,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "keylightd"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argh",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keylightd"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
edition = "2021"
|
||||
license = "0BSD"
|
||||
readme = "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 <brightness>] [--timeout <timeout>]
|
||||
Usage: keylightd [--brightness <brightness>] [--timeout <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
|
||||
```
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
38
src/main.rs
38
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<u8, String> {
|
||||
|
@ -34,10 +40,18 @@ fn parse_brightness(s: &str) -> Result<u8, String> {
|
|||
|
||||
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(())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue