Skip to content

Commit

Permalink
Add support for controlling the power LED
Browse files Browse the repository at this point in the history
  • Loading branch information
jonas-schievink committed Sep 3, 2023
1 parent 31be7e6 commit 933a4cf
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "keylightd"
version = "1.0.2"
version = "1.1.0"
edition = "2021"
license = "0BSD"
readme = "README.md"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
90 changes: 89 additions & 1 deletion src/command.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
Expand All @@ -23,6 +31,7 @@ pub enum Cmd {
// ...
GetKeyboardBacklight = 0x0022,
SetKeyboardBacklight = 0x0023,
LedControl = 0x0029,
}

//////////////////////////////////
Expand Down Expand Up @@ -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 changes: 36 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand All @@ -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> {
Expand All @@ -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<()> {
Expand All @@ -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(())
Expand Down

0 comments on commit 933a4cf

Please sign in to comment.