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
28 changes: 28 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,34 @@ framework_tool --inputdeck-mode auto
framework_tool --inputdeck-mode resets
```

## Haptic touchpad (Laptop 13 Pro)

Just like our clickpads, the Laptop 13 Pro haptic touchpad supports tap to
click, but for tactile clicking, instead of a physical button it uses piezo
crystals for sensing your click and responding with a haptic click sensation.

To configure that feeling, it exposes two configuration knobs:

- Sensitivity: How hard you have to press to trigger a click
- Intensity: How strong the feedback vibration is

```
# Disable haptic feedback
> framework_tool --haptic-intensity 0

# Set haptic feedback intensity back to default
# Only 0/off, 25, 50, 75, 100 are accepted
> framework_tool --haptic-intensity 75
```

```
# Set click force / sensitivity (low / medium / high)
> framework_tool --click-force high

# Set back to default
> framework_tool --click-force medium
```

## Checking board ID

Most inputdeck checking is implemented by Board ID. To read those directly for
Expand Down
24 changes: 22 additions & 2 deletions framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! as well as on the UEFI shell tool.
use std::io;

use clap::builder::TypedValueParser;
use clap::error::ErrorKind;
use clap::Parser;
use clap::{command, Arg, Args, FromArgMatches};
Expand All @@ -12,8 +13,8 @@ use clap_num::maybe_hex;
use crate::chromium_ec::commands::SetGpuSerialMagic;
use crate::chromium_ec::CrosEcDriverType;
use crate::commandline::{
Cli, ConsoleArg, FpBrightnessArg, HardwareDeviceType, InputDeckModeArg, LogLevel, RebootEcArg,
TabletModeArg,
Cli, ClickForceArg, ConsoleArg, FpBrightnessArg, HardwareDeviceType, InputDeckModeArg,
LogLevel, RebootEcArg, TabletModeArg,
};

/// Swiss army knife for Framework laptops
Expand Down Expand Up @@ -236,6 +237,23 @@ struct ClapCli {
#[arg(long)]
touchscreen_enable: Option<bool>,

/// Set touchpad haptic feedback intensity
#[arg(
long,
value_name = "INTENSITY",
value_parser = clap::builder::PossibleValuesParser::new(["0", "25", "50", "75", "100"])
.try_map(|s| {
s.parse::<u8>()
.map_err(|_| format!("invalid haptic intensity value: {s}"))
}),
)]
haptic_intensity: Option<u8>,

/// Set touchpad click force / sensitivity
#[clap(value_enum)]
#[arg(long)]
click_force: Option<ClickForceArg>,

/// Check stylus battery level (USI 2.0 stylus only)
#[clap(value_enum)]
#[arg(long)]
Expand Down Expand Up @@ -534,6 +552,8 @@ pub fn parse(args: &[String]) -> Cli {
ps2_enable: args.ps2_enable,
tablet_mode: args.tablet_mode,
touchscreen_enable: args.touchscreen_enable,
haptic_intensity: args.haptic_intensity,
click_force: args.click_force,
stylus_battery: args.stylus_battery,
console: args.console,
reboot_ec: args.reboot_ec,
Expand Down
34 changes: 34 additions & 0 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ impl From<FpBrightnessArg> for FpLedBrightnessLevel {
}
}

#[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ClickForceArg {
Low,
Medium,
High,
}
#[cfg(feature = "hidapi")]
impl From<ClickForceArg> for crate::touchpad::ClickForce {
fn from(w: ClickForceArg) -> crate::touchpad::ClickForce {
match w {
ClickForceArg::Low => crate::touchpad::ClickForce::Low,
ClickForceArg::Medium => crate::touchpad::ClickForce::Medium,
ClickForceArg::High => crate::touchpad::ClickForce::High,
}
}
}

#[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum InputDeckModeArg {
Expand Down Expand Up @@ -215,6 +233,8 @@ pub struct Cli {
pub ps2_enable: Option<bool>,
pub tablet_mode: Option<TabletModeArg>,
pub touchscreen_enable: Option<bool>,
pub haptic_intensity: Option<u8>,
pub click_force: Option<ClickForceArg>,
pub stylus_battery: bool,
pub console: Option<ConsoleArg>,
pub reboot_ec: Option<RebootEcArg>,
Expand Down Expand Up @@ -1487,6 +1507,20 @@ 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 let Some(_intensity) = &args.haptic_intensity {
#[cfg(feature = "hidapi")]
if let Err(e) = crate::touchpad::set_haptic_intensity(*_intensity) {
error!("Failed to set haptic intensity: {}", e);
}
#[cfg(not(feature = "hidapi"))]
error!("Not built with hidapi feature");
} else if let Some(_force) = &args.click_force {
#[cfg(feature = "hidapi")]
if let Err(e) = crate::touchpad::set_click_force((*_force).into()) {
error!("Failed to set click force: {}", e);
}
#[cfg(not(feature = "hidapi"))]
error!("Not built with hidapi feature");
} else if args.stylus_battery {
#[cfg(feature = "hidapi")]
print_stylus_battery_level();
Expand Down
2 changes: 2 additions & 0 deletions framework_lib/src/commandline/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub fn parse(args: &[String]) -> Cli {
ps2_enable: None,
tablet_mode: None,
touchscreen_enable: None,
haptic_intensity: None,
click_force: None,
stylus_battery: false,
console: None,
reboot_ec: None,
Expand Down
78 changes: 78 additions & 0 deletions framework_lib/src/touchpad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,84 @@ pub const PIX_VID: u16 = 0x093A;
pub const P274_REPORT_ID: u8 = 0x43;
pub const P239_REPORT_ID: u8 = 0x42;

// Standard HID Precision Touchpad (PTP) interface — every PTP-compliant touchpad
// reports on this usage. Only haptic touchpads expose the feature reports below.
const TOUCHPAD_USAGE_PAGE: u16 = 0x000D; // Digitizers
const TOUCHPAD_USAGE: u16 = 0x0005; // Touch Pad

// Haptic feedback intensity (HID Haptic page 0x0E, Usage 0x23 Intensity).
// Descriptor says logical range 0..100, but the Boreas haptic firmware
// only implements five steps: 0%, 25%, 50%, 75%, 100%.
const HAPTIC_INTENSITY_REPORT_ID: u8 = 0x09;
pub const HAPTIC_INTENSITY_LEVELS: [u8; 5] = [0, 25, 50, 75, 100];

// Button press threshold / click force (HID Digitizer page 0x0D, Usage 0xB0).
// 2-bit field, firmware accepts 1=Low, 2=Medium, 3=High.
const CLICK_FORCE_REPORT_ID: u8 = 0x08;

#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
pub enum ClickForce {
Low = 1,
Medium = 2,
High = 3,
}

/// Open the PTP HID interface of the touchpad. Note: every modern touchpad
/// exposes this interface; only haptic touchpads respond to the feature
/// reports used by `set_haptic_intensity` / `set_click_force`.
fn open_haptic_touchpad() -> Option<HidDevice> {
let api = HidApi::new().ok()?;
for dev_info in api.device_list() {
if dev_info.usage_page() != TOUCHPAD_USAGE_PAGE || dev_info.usage() != TOUCHPAD_USAGE {
continue;
}
Comment thread
JohnAZoidberg marked this conversation as resolved.
debug!(
" Touchpad candidate {:04X}:{:04X} (Usage Page {:04X}, Usage {:04X})",
dev_info.vendor_id(),
dev_info.product_id(),
dev_info.usage_page(),
dev_info.usage()
);
if let Ok(device) = dev_info.open_device(&api) {
return Some(device);
}
}
None
}

// The firmware accepts SET_FEATURE for these reports but doesn't reply
// to GET_FEATURE, so both controls are write-only.

fn hid_err(message: impl Into<String>) -> HidError {
HidError::HidApiError {
message: message.into(),
}
}

pub fn set_haptic_intensity(value: u8) -> Result<(), HidError> {
if !HAPTIC_INTENSITY_LEVELS.contains(&value) {
return Err(hid_err(format!(
"Haptic intensity must be one of: {:?}",
HAPTIC_INTENSITY_LEVELS
)));
}
let device =
open_haptic_touchpad().ok_or_else(|| hid_err("Could not find a haptic touchpad"))?;
let buf = [HAPTIC_INTENSITY_REPORT_ID, value];
debug!(" send_feature_report (haptic intensity) {:X?}", buf);
device.send_feature_report(&buf)
}
Comment thread
JohnAZoidberg marked this conversation as resolved.

pub fn set_click_force(force: ClickForce) -> Result<(), HidError> {
let device =
open_haptic_touchpad().ok_or_else(|| hid_err("Could not find a haptic touchpad"))?;
// Field is 2 bits at the bottom of the report payload
let buf = [CLICK_FORCE_REPORT_ID, force as u8];
debug!(" send_feature_report (click force) {:X?}", buf);
device.send_feature_report(&buf)
}
Comment thread
JohnAZoidberg marked this conversation as resolved.

fn read_byte(device: &HidDevice, report_id: u8, addr: u8) -> Result<u8, HidError> {
device.send_feature_report(&[report_id, addr, 0x10, 0])?;

Expand Down
10 changes: 9 additions & 1 deletion framework_tool/completions/bash/framework_tool
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ _framework_tool() {

case "${cmd}" in
framework_tool)
opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --info --meinfo --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-full-ec --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --nvidia --host-command --generate-completions --help"
opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --info --meinfo --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-full-ec --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --nvidia --host-command --generate-completions --help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -165,6 +165,14 @@ _framework_tool() {
COMPREPLY=($(compgen -W "true false" -- "${cur}"))
return 0
;;
--haptic-intensity)
COMPREPLY=($(compgen -W "0 25 50 75 100" -- "${cur}"))
return 0
;;
--click-force)
COMPREPLY=($(compgen -W "low medium high" -- "${cur}"))
return 0
;;
--console)
COMPREPLY=($(compgen -W "recent follow" -- "${cur}"))
return 0
Expand Down
8 changes: 8 additions & 0 deletions framework_tool/completions/fish/framework_tool.fish
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ tablet\t''
laptop\t''"
complete -c framework_tool -l touchscreen-enable -d 'Enable/disable touchscreen' -r -f -a "true\t''
false\t''"
complete -c framework_tool -l haptic-intensity -d 'Set touchpad haptic feedback intensity' -r -f -a "0\t''
25\t''
50\t''
75\t''
100\t''"
complete -c framework_tool -l click-force -d 'Set touchpad click force / sensitivity' -r -f -a "low\t''
medium\t''
high\t''"
complete -c framework_tool -l console -d 'Get EC console, choose whether recent or to follow the output' -r -f -a "recent\t''
follow\t''"
complete -c framework_tool -l reboot-ec -d 'Control EC RO/RW jump' -r -f -a "reboot\t''
Expand Down
2 changes: 2 additions & 0 deletions framework_tool/completions/zsh/_framework_tool
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ _framework_tool() {
'--ps2-enable=[Control PS2 touchpad emulation (DEBUG COMMAND, if touchpad not working, reboot system)]:PS2_ENABLE:(true false)' \
'--tablet-mode=[Set tablet mode override]:TABLET_MODE:(auto tablet laptop)' \
'--touchscreen-enable=[Enable/disable touchscreen]:TOUCHSCREEN_ENABLE:(true false)' \
'--haptic-intensity=[Set touchpad haptic feedback intensity]:INTENSITY:(0 25 50 75 100)' \
'--click-force=[Set touchpad click force / sensitivity]:CLICK_FORCE:(low medium high)' \
'--console=[Get EC console, choose whether recent or to follow the output]:CONSOLE:(recent follow)' \
'--reboot-ec=[Control EC RO/RW jump]:REBOOT_EC:(reboot jump-ro jump-rw cancel-jump disable-jump)' \
'--ec-hib-delay=[Get or set EC hibernate delay (S5 to G3)]::EC_HIB_DELAY:_default' \
Expand Down
Loading