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]]
|
[[package]]
|
||||||
name = "keylightd"
|
name = "keylightd"
|
||||||
version = "1.0.2"
|
version = "1.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argh",
|
"argh",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "keylightd"
|
name = "keylightd"
|
||||||
version = "1.0.2"
|
version = "1.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "0BSD"
|
license = "0BSD"
|
||||||
readme = "README.md"
|
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:
|
`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
|
keylightd - automatic keyboard backlight daemon for Framework laptops
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--brightness brightness level when active (0-100) [default=30]
|
--brightness brightness level when active (0-100) [default=30]
|
||||||
--timeout activity timeout in seconds [default=10]
|
--timeout activity timeout in seconds [default=10]
|
||||||
|
--power also control the power LED in the fingerprint module
|
||||||
--help display usage information
|
--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};
|
use bytemuck::{NoUninit, Pod, Zeroable};
|
||||||
|
|
||||||
/// Trait implemented by Embedded Controller commands.
|
/// Trait implemented by Embedded Controller commands.
|
||||||
|
@ -7,7 +15,7 @@ pub trait Command: NoUninit {
|
||||||
|
|
||||||
/// Command version.
|
/// 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;
|
const VERSION: u32 = 0;
|
||||||
|
|
||||||
/// The associated response type.
|
/// The associated response type.
|
||||||
|
@ -23,6 +31,7 @@ pub enum Cmd {
|
||||||
// ...
|
// ...
|
||||||
GetKeyboardBacklight = 0x0022,
|
GetKeyboardBacklight = 0x0022,
|
||||||
SetKeyboardBacklight = 0x0023,
|
SetKeyboardBacklight = 0x0023,
|
||||||
|
LedControl = 0x0029,
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
|
@ -106,3 +115,82 @@ impl Command for SetKeyboardBacklight {
|
||||||
const CMD: Cmd = Cmd::SetKeyboardBacklight;
|
const CMD: Cmd = Cmd::SetKeyboardBacklight;
|
||||||
type Response = SetKeyboardBacklightResponse;
|
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 command::{GetKeyboardBacklight, SetKeyboardBacklight};
|
||||||
use ec::EmbeddedController;
|
use ec::EmbeddedController;
|
||||||
|
|
||||||
|
use crate::command::{LedBrightnesses, LedControl, LedFlags, LedId};
|
||||||
|
|
||||||
mod command;
|
mod command;
|
||||||
mod ec;
|
mod ec;
|
||||||
|
|
||||||
/// keylightd - automatic keyboard backlight daemon for Framework laptops
|
/// keylightd - automatic keyboard backlight daemon for Framework laptops
|
||||||
#[derive(FromArgs)]
|
#[derive(Debug, FromArgs)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// brightness level when active (0-100) [default=30]
|
/// brightness level when active (0-100) [default=30]
|
||||||
#[argh(option, default = "30", from_str_fn(parse_brightness))]
|
#[argh(option, default = "30", from_str_fn(parse_brightness))]
|
||||||
|
@ -22,6 +24,10 @@ struct Args {
|
||||||
/// activity timeout in seconds [default=10]
|
/// activity timeout in seconds [default=10]
|
||||||
#[argh(option, default = "10")]
|
#[argh(option, default = "10")]
|
||||||
timeout: u32,
|
timeout: u32,
|
||||||
|
|
||||||
|
/// also control the power LED in the fingerprint module
|
||||||
|
#[argh(switch)]
|
||||||
|
power: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_brightness(s: &str) -> Result<u8, String> {
|
fn parse_brightness(s: &str) -> Result<u8, String> {
|
||||||
|
@ -34,10 +40,18 @@ fn parse_brightness(s: &str) -> Result<u8, String> {
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
env_logger::builder()
|
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();
|
.init();
|
||||||
|
|
||||||
let args: Args = argh::from_env();
|
let args: Args = argh::from_env();
|
||||||
|
log::debug!("args={:?}", args);
|
||||||
|
|
||||||
let ec = EmbeddedController::open()?;
|
let ec = EmbeddedController::open()?;
|
||||||
let fade_to = |target: u8| -> io::Result<()> {
|
let fade_to = |target: u8| -> io::Result<()> {
|
||||||
|
@ -50,7 +64,27 @@ fn main() -> anyhow::Result<()> {
|
||||||
cur += 1;
|
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 })?;
|
ec.command(SetKeyboardBacklight { percent: cur })?;
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(3));
|
thread::sleep(Duration::from_millis(3));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue