Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ Touchscreen
MPP Protocol: true
```

### Stylus (Framework 12)

```
> sudo framework_tool --versions
[...]
Stylus
Serial Number: 28C1A00-12E71DAE
Vendor ID: 32AC (Framework Computer)
Product ID: 002B (Framework Stylus)
Firmware Version: FF.FF
[...]
```

### Touchpad (Framework 12, Framework 13, Framework 16)

```
Expand Down Expand Up @@ -126,7 +139,6 @@ ALS: 76 Lux
Fan Speed: 0 RPM
```


## Check expansion bay (Framework 16)

```
Expand Down Expand Up @@ -245,3 +257,10 @@ Battery Status
> sudo framework_tool --charge-rate-limit 80 0.8
> sudo framework_tool --charge-current-limit 80 2000
```

## Stylus (Framework 12)

```
> sudo framework_tool --stylus-battery
Stylus Battery Strength: 77%
```
6 changes: 6 additions & 0 deletions framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ struct ClapCli {
#[arg(long)]
touchscreen_enable: Option<bool>,

/// Check stylus battery level (USI 2.0 stylus only)
#[clap(value_enum)]
#[arg(long)]
stylus_battery: bool,

/// Get EC console, choose whether recent or to follow the output
#[clap(value_enum)]
#[arg(long)]
Expand Down Expand Up @@ -390,6 +395,7 @@ pub fn parse(args: &[String]) -> Cli {
rgbkbd: args.rgbkbd,
tablet_mode: args.tablet_mode,
touchscreen_enable: args.touchscreen_enable,
stylus_battery: args.stylus_battery,
console: args.console,
reboot_ec: args.reboot_ec,
hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()),
Expand Down
18 changes: 18 additions & 0 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ pub struct Cli {
pub rgbkbd: Vec<u64>,
pub tablet_mode: Option<TabletModeArg>,
pub touchscreen_enable: Option<bool>,
pub stylus_battery: bool,
pub console: Option<ConsoleArg>,
pub reboot_ec: Option<RebootEcArg>,
pub hash: Option<String>,
Expand Down Expand Up @@ -336,6 +337,18 @@ fn active_mode(mode: &FwMode, reference: FwMode) -> &'static str {
}
}

#[cfg(feature = "hidapi")]
fn print_stylus_battery_level() {
loop {
if let Some(level) = touchscreen::get_battery_level() {
println!("Stylus Battery Strength: {}%", level);
return;
} else {
debug!("Stylus Battery Strength: Unknown");
}
}
}

fn print_versions(ec: &CrosEc) {
println!("UEFI BIOS");
if let Some(smbios) = get_smbios() {
Expand Down Expand Up @@ -816,6 +829,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
if touchscreen::enable_touch(*_enable).is_none() {
error!("Failed to enable/disable touch");
}
} else if args.stylus_battery {
#[cfg(feature = "hidapi")]
print_stylus_battery_level();
#[cfg(not(feature = "hidapi"))]
error!("Not build with hidapi feature");
} else if let Some(console_arg) = &args.console {
match console_arg {
ConsoleArg::Follow => {
Expand Down
1 change: 1 addition & 0 deletions framework_lib/src/commandline/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub fn parse(args: &[String]) -> Cli {
rgbkbd: vec![],
tablet_mode: None,
touchscreen_enable: None,
stylus_battery: false,
console: None,
reboot_ec: None,
hash: None,
Expand Down
120 changes: 112 additions & 8 deletions framework_lib/src/touchscreen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ use crate::touchscreen_win;

pub const ILI_VID: u16 = 0x222A;
pub const ILI_PID: u16 = 0x5539;
const VENDOR_USAGE_PAGE: u16 = 0xFF00;
pub const USI_BITMAP: u8 = 1 << 1;
pub const MPP_BITMAP: u8 = 1 << 2;

const REPORT_ID_FIRMWARE: u8 = 0x27;
const REPORT_ID_USI_VER: u8 = 0x28;

struct HidapiTouchScreen {
device: HidDevice,
}

impl TouchScreen for HidapiTouchScreen {
fn open_device() -> Option<HidapiTouchScreen> {
debug!("Looking for touchscreen HID device");
fn open_device(target_up: u16, skip: u8) -> Option<HidapiTouchScreen> {
debug!(
"Looking for touchscreen HID device {:X} {}",
target_up, skip
);
let mut skip = skip;
match HidApi::new() {
Ok(api) => {
for dev_info in api.device_list() {
Expand All @@ -29,7 +37,7 @@ impl TouchScreen for HidapiTouchScreen {
" Found {:04X}:{:04X} (Usage Page {:04X})",
vid, pid, usage_page
);
if usage_page != 0xFF00 {
if usage_page != target_up {
debug!(" Skipping usage page. Expected {:04X}", 0xFF00);
continue;
}
Expand All @@ -40,6 +48,10 @@ impl TouchScreen for HidapiTouchScreen {
debug!(" Found matching touchscreen HID device");
debug!(" Path: {:?}", dev_info.path());
debug!(" IC Type: {:04X}", pid);
if skip > 0 {
skip -= 1;
continue;
}

// Unwrapping because if we can enumerate it, we should be able to open it
let device = dev_info.open_device(&api).unwrap();
Expand Down Expand Up @@ -97,16 +109,86 @@ impl TouchScreen for HidapiTouchScreen {
debug!(" Read buf: {:X?}", buf);
Some(buf[msg_len..msg_len + read_len].to_vec())
}

fn get_battery_status(&self) -> Option<u8> {
let mut msg = [0u8; 0x40];
msg[0] = 0x0D;
self.device.read(&mut msg).ok()?;
// println!(" Tip Switch {}%", msg[12]);
// println!(" Barrell Switch: {}%", msg[12]);
// println!(" Eraser: {}%", msg[12]);
// println!(" Invert: {}%", msg[12]);
// println!(" In Range: {}%", msg[12]);
// println!(" 2nd Barrel Switch:{}%", msg[12]);
// println!(" X {}%", msg[12]);
// println!(" Y {}%", msg[12]);
// println!(" Tip Pressure: {}%", msg[12]);
// println!(" X Tilt: {}%", msg[12]);
// println!(" Y Tilt: {}%", msg[12]);
debug!(" Battery Strength: {}%", msg[12]);
debug!(
" Barrel Pressure: {}",
u16::from_le_bytes([msg[13], msg[14]])
);
debug!(" Transducer Index: {}", msg[15]);

if msg[12] == 0 {
None
} else {
Some(msg[12])
}
}

fn get_stylus_fw(&self) -> Option<()> {
let mut msg = [0u8; 0x40];
msg[0] = REPORT_ID_USI_VER;
self.device.get_feature_report(&mut msg).ok()?;
let usi_major = msg[2];
let usi_minor = msg[3];
debug!("USI version (Major.Minor): {}.{}", usi_major, usi_minor);

if usi_major != 2 || usi_minor != 0 {
// Probably not USI mode
return None;
}

let mut msg = [0u8; 0x40];
msg[0] = REPORT_ID_FIRMWARE;
self.device.get_feature_report(&mut msg).ok()?;
let sn_low = u32::from_le_bytes([msg[2], msg[3], msg[4], msg[5]]);
let sn_high = u32::from_le_bytes([msg[6], msg[7], msg[8], msg[9]]);
let vid = u16::from_le_bytes([msg[14], msg[15]]);
let vendor = if vid == 0x32AC {
" (Framework Computer)"
} else {
""
};
let pid = u16::from_le_bytes([msg[16], msg[17]]);
let product = if pid == 0x002B {
" (Framework Stylus)"
} else {
""
};
println!("Stylus");
println!(" Serial Number: {:X}-{:X}", sn_high, sn_low);
debug!(" Redundant SN {:X?}", &msg[10..14]);
println!(" Vendor ID: {:04X}{}", vid, vendor);
println!(" Product ID: {:04X}{}", pid, product);
println!(" Firmware Version: {:02X}.{:02X}", &msg[18], msg[19]);

Some(())
}
}

pub trait TouchScreen {
fn open_device() -> Option<Self>
fn open_device(usage_page: u16, skip: u8) -> Option<Self>
where
Self: std::marker::Sized;
fn send_message(&self, message_id: u8, read_len: usize, data: Vec<u8>) -> Option<Vec<u8>>;

fn check_fw_version(&self) -> Option<()> {
println!("Touchscreen");

let res = self.send_message(0x42, 3, vec![0])?;
let ver = res
.iter()
Expand Down Expand Up @@ -135,22 +217,44 @@ pub trait TouchScreen {
self.send_message(0x38, 0, vec![!enable as u8, 0x00])?;
Some(())
}

fn get_stylus_fw(&self) -> Option<()>;
fn get_battery_status(&self) -> Option<u8>;
}

pub fn get_battery_level() -> Option<u8> {
for skip in 0..5 {
if let Some(device) = HidapiTouchScreen::open_device(0x000D, skip) {
if let Some(level) = device.get_battery_status() {
return Some(level);
}
}
}
None
}

pub fn print_fw_ver() -> Option<()> {
for skip in 0..5 {
if let Some(device) = HidapiTouchScreen::open_device(0x000D, skip) {
if device.get_stylus_fw().is_some() {
break;
}
}
}

#[cfg(target_os = "windows")]
let device = touchscreen_win::NativeWinTouchScreen::open_device()?;
let device = touchscreen_win::NativeWinTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?;
#[cfg(not(target_os = "windows"))]
let device = HidapiTouchScreen::open_device()?;
let device = HidapiTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?;

device.check_fw_version()
}

pub fn enable_touch(enable: bool) -> Option<()> {
#[cfg(target_os = "windows")]
let device = touchscreen_win::NativeWinTouchScreen::open_device()?;
let device = touchscreen_win::NativeWinTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?;
#[cfg(not(target_os = "windows"))]
let device = HidapiTouchScreen::open_device()?;
let device = HidapiTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?;

device.enable_touch(enable)
}
41 changes: 38 additions & 3 deletions framework_lib/src/touchscreen_win.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ use windows::{
},
};

const REPORT_ID_FIRMWARE: u8 = 0x27;
const REPORT_ID_USI_VER: u8 = 0x28;

pub struct NativeWinTouchScreen {
handle: HANDLE,
}

impl TouchScreen for NativeWinTouchScreen {
fn open_device() -> Option<Self> {
debug!("Looking for touchscreen HID device");
fn open_device(target_up: u16, skip: u8) -> Option<Self> {
debug!(
"Looking for touchscreen HID device {:X} {}",
target_up, skip
);
let mut skip = skip;
match HidApi::new() {
Ok(api) => {
for dev_info in api.device_list() {
Expand All @@ -37,7 +44,7 @@ impl TouchScreen for NativeWinTouchScreen {
" Found {:04X}:{:04X} (Usage Page {:04X})",
vid, pid, usage_page
);
if usage_page != 0xFF00 {
if usage_page != target_up {
debug!(" Skipping usage page. Expected {:04X}", 0xFF00);
continue;
}
Expand All @@ -48,6 +55,10 @@ impl TouchScreen for NativeWinTouchScreen {
debug!(" Found matching touchscreen HID device");
debug!(" Path: {:?}", dev_info.path());
debug!(" IC Type: {:04X}", pid);
if skip > 0 {
skip -= 1;
continue;
}

// TODO: Enumerate with windows
// Should enumerate and find the right one
Expand Down Expand Up @@ -135,4 +146,28 @@ impl TouchScreen for NativeWinTouchScreen {

Some(buf[msg_len..msg_len + read_len].to_vec())
}

fn get_stylus_fw(&self) -> Option<()> {
let mut msg = [0u8; 0x40];
msg[0] = REPORT_ID_FIRMWARE;
unsafe {
let success = HidD_GetFeature(self.handle, msg.as_mut_ptr() as _, msg.len() as u32);
debug!(" Success: {}", success);
}
println!("Stylus firmware: {:X?}", msg);

let mut msg = [0u8; 0x40];
msg[0] = REPORT_ID_USI_VER;
unsafe {
let success = HidD_GetFeature(self.handle, msg.as_mut_ptr() as _, msg.len() as u32);
debug!(" Success: {}", success);
}
println!("USI Version: {:X?}", msg);

None
}
fn get_battery_status(&self) -> Option<u8> {
error!("Get stylus battery status not supported on Windows");
None
}
}