Allow configuration of intel pstates paired with fan-modes

This commit is contained in:
Luke
2020-05-02 19:31:40 +12:00
parent 1a967f315b
commit f12bb1d4f6
10 changed files with 169 additions and 92 deletions

4
Cargo.lock generated
View File

@@ -667,7 +667,7 @@ checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
[[package]] [[package]]
name = "rog-aura" name = "rog-aura"
version = "0.8.0" version = "0.9.0"
dependencies = [ dependencies = [
"dbus", "dbus",
"gumdrop", "gumdrop",
@@ -678,7 +678,7 @@ dependencies = [
[[package]] [[package]]
name = "rog-daemon" name = "rog-daemon"
version = "0.8.0" version = "0.9.0"
dependencies = [ dependencies = [
"dbus", "dbus",
"dbus-tokio", "dbus-tokio",

View File

@@ -23,6 +23,10 @@ $ sudo systemctl start rog-core.service
$ sudo systemctl enable rog-core.service $ sudo systemctl enable rog-core.service
``` ```
## Updating
Occasionally I might break things for you by tweaking or changing the config file layout. Usually this will mean you need to remove `/etc/rog-core.toml' and restart the daemon to create a new one. You *can* back up the old one and copy settings back over (then restart daemon again).
## Use ## Use
Running the program as a daemon manually will require root. Standard (non-daemon) mode expects to be communicating with the daemon mode over dbus. Running the program as a daemon manually will require root. Standard (non-daemon) mode expects to be communicating with the daemon mode over dbus.
@@ -116,7 +120,9 @@ Bus 001 Device 005: ID 0b05:1866 ASUSTek Computer, Inc. N-KEY Device
Then do `sudo lsusb -vd 0b05:1866 > ~/laptop_info` and give that to me. Then do `sudo lsusb -vd 0b05:1866 > ~/laptop_info` and give that to me.
Also required: Other helpful info can be gained from `sudo usbhid-dump`, for which you may need to unload kernel drivers. Please google this.
Also required (for my book-keeping of data):
- `cat /sys/class/dmi/id/product_name` - `cat /sys/class/dmi/id/product_name`
- `cat /sys/class/dmi/id/product_family` - `cat /sys/class/dmi/id/product_family`
- `cat /sys/class/dmi/id/board_name` - `cat /sys/class/dmi/id/board_name`

View File

@@ -3,6 +3,33 @@ use gumdrop::Options;
use std::fmt::Debug; use std::fmt::Debug;
use std::str::FromStr; use std::str::FromStr;
#[derive(Debug, Options)]
pub struct LedBrightness {
level: u8,
}
impl LedBrightness {
pub fn level(&self) -> u8 {
self.level
}
}
impl FromStr for LedBrightness {
type Err = AuraError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"off" => Ok(LedBrightness { level: 0x00 }),
"low" => Ok(LedBrightness { level: 0x01 }),
"med" => Ok(LedBrightness { level: 0x02 }),
"high" => Ok(LedBrightness { level: 0x03 }),
_ => {
println!("Missing required argument, must be one of:\noff,low,med,high\n");
Err(AuraError::ParseBrightness)
}
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Colour(pub u8, pub u8, pub u8); pub struct Colour(pub u8, pub u8, pub u8);
impl Default for Colour { impl Default for Colour {

View File

@@ -11,10 +11,13 @@ pub struct Config {
pub brightness: u8, pub brightness: u8,
pub current_mode: [u8; 4], pub current_mode: [u8; 4],
pub builtin_modes: BuiltInModeBytes, pub builtin_modes: BuiltInModeBytes,
pub mode_performance: FanModeSettings,
} }
impl Config { impl Config {
pub fn read(mut self) -> Self { /// `load` will attempt to read the config, but if it is not found it
/// will create a new default config and write that out.
pub fn load(mut self) -> Self {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
@@ -33,12 +36,30 @@ impl Config {
.expect("Writing default config failed"); .expect("Writing default config failed");
self = c; self = c;
} else { } else {
self = toml::from_str(&buf).unwrap(); self =
toml::from_str(&buf).expect(&format!("Could not deserialise {}", CONFIG_PATH));
} }
} }
self self
} }
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&CONFIG_PATH)
.expect("config file error");
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
panic!("Missing {}", CONFIG_PATH);
} else {
let x: Config =
toml::from_str(&buf).expect(&format!("Could not deserialise {}", CONFIG_PATH));
*self = x;
}
}
}
pub fn write(&self) { pub fn write(&self) {
let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config"); let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config");
let toml = toml::to_string(self).expect("Parse config to JSON failed"); let toml = toml::to_string(self).expect("Parse config to JSON failed");
@@ -55,3 +76,27 @@ impl Config {
} }
} }
} }
#[derive(Default, Deserialize, Serialize)]
pub struct FanModeSettings {
pub normal: IntelPState,
pub boost: IntelPState,
pub silent: IntelPState,
}
#[derive(Deserialize, Serialize)]
pub struct IntelPState {
pub min_percentage: u8,
pub max_percentage: u8,
pub no_turbo: bool,
}
impl Default for IntelPState {
fn default() -> Self {
IntelPState {
min_percentage: 0,
max_percentage: 100,
no_turbo: false,
}
}
}

View File

@@ -37,18 +37,14 @@ static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode
/// - `LED_INIT4` /// - `LED_INIT4`
/// - `LED_INIT2` /// - `LED_INIT2`
/// - `LED_INIT4` /// - `LED_INIT4`
pub(crate) struct RogCore { pub struct RogCore {
handle: DeviceHandle<rusb::GlobalContext>, handle: DeviceHandle<rusb::GlobalContext>,
virt_keys: VirtKeys, virt_keys: VirtKeys,
_pin: PhantomPinned, _pin: PhantomPinned,
} }
impl RogCore { impl RogCore {
pub(crate) fn new( pub fn new(vendor: u16, product: u16, led_endpoint: u8) -> Result<RogCore, Box<dyn Error>> {
vendor: u16,
product: u16,
led_endpoint: u8,
) -> Result<RogCore, Box<dyn Error>> {
let mut dev_handle = RogCore::get_device(vendor, product)?; let mut dev_handle = RogCore::get_device(vendor, product)?;
dev_handle.set_active_configuration(0).unwrap_or(()); dev_handle.set_active_configuration(0).unwrap_or(());
@@ -81,16 +77,7 @@ impl RogCore {
}) })
} }
pub(crate) async fn reload(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> { pub async fn reload(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
// let mode_curr = self.config.current_mode[3];
// let mode = self
// .config
// .builtin_modes
// .get_field_from(BuiltInModeByte::from(mode_curr).into())
// .unwrap()
// .to_owned();
// self.aura_write_messages(&[&mode])?;
let path = if Path::new(FAN_TYPE_1_PATH).exists() { let path = if Path::new(FAN_TYPE_1_PATH).exists() {
FAN_TYPE_1_PATH FAN_TYPE_1_PATH
} else if Path::new(FAN_TYPE_2_PATH).exists() { } else if Path::new(FAN_TYPE_2_PATH).exists() {
@@ -101,12 +88,12 @@ impl RogCore {
let mut file = OpenOptions::new().write(true).open(path)?; let mut file = OpenOptions::new().write(true).open(path)?;
file.write_all(format!("{:?}\n", config.fan_mode).as_bytes())?; file.write_all(format!("{:?}\n", config.fan_mode).as_bytes())?;
self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode))?; self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?;
info!("Reloaded last saved settings"); info!("Reloaded last saved settings");
Ok(()) Ok(())
} }
pub(crate) fn virt_keys(&mut self) -> &mut VirtKeys { pub fn virt_keys(&mut self) -> &mut VirtKeys {
&mut self.virt_keys &mut self.virt_keys
} }
@@ -123,7 +110,7 @@ impl RogCore {
Err(rusb::Error::NoDevice) Err(rusb::Error::NoDevice)
} }
pub(crate) fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> { pub fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
let path = if Path::new(FAN_TYPE_1_PATH).exists() { let path = if Path::new(FAN_TYPE_1_PATH).exists() {
FAN_TYPE_1_PATH FAN_TYPE_1_PATH
} else if Path::new(FAN_TYPE_2_PATH).exists() { } else if Path::new(FAN_TYPE_2_PATH).exists() {
@@ -146,34 +133,55 @@ impl RogCore {
} }
info!("Fan mode stepped to: {:#?}", FanLevel::from(n)); info!("Fan mode stepped to: {:#?}", FanLevel::from(n));
fan_ctrl.write_all(format!("{:?}\n", n).as_bytes())?; fan_ctrl.write_all(format!("{:?}\n", n).as_bytes())?;
self.set_pstate_for_fan_mode(FanLevel::from(n))?; self.set_pstate_for_fan_mode(FanLevel::from(n), config)?;
config.fan_mode = n; config.fan_mode = n;
config.write(); config.write();
} }
Ok(()) Ok(())
} }
fn set_pstate_for_fan_mode(&self, mode: FanLevel) -> Result<(), Box<dyn Error>> { fn set_pstate_for_fan_mode(
&self,
mode: FanLevel,
config: &mut Config,
) -> Result<(), Box<dyn Error>> {
// Set CPU pstate // Set CPU pstate
if let Ok(pstate) = intel_pstate::PState::new() { if let Ok(pstate) = intel_pstate::PState::new() {
// re-read the config here in case a user changed the pstate settings
config.read();
match mode { match mode {
FanLevel::Normal => { FanLevel::Normal => {
pstate.set_min_perf_pct(0)?; pstate.set_min_perf_pct(config.mode_performance.normal.min_percentage)?;
pstate.set_max_perf_pct(100)?; pstate.set_max_perf_pct(config.mode_performance.normal.max_percentage)?;
pstate.set_no_turbo(false)?; pstate.set_no_turbo(config.mode_performance.normal.no_turbo)?;
info!("CPU pstate: normal"); info!(
"CPU Power: min-freq: {:?}, max-freq: {:?}, turbo: {:?}",
config.mode_performance.normal.min_percentage,
config.mode_performance.normal.max_percentage,
!config.mode_performance.normal.no_turbo
);
} }
FanLevel::Boost => { FanLevel::Boost => {
pstate.set_min_perf_pct(50)?; pstate.set_min_perf_pct(config.mode_performance.boost.min_percentage)?;
pstate.set_max_perf_pct(100)?; pstate.set_max_perf_pct(config.mode_performance.boost.max_percentage)?;
pstate.set_no_turbo(false)?; pstate.set_no_turbo(config.mode_performance.boost.no_turbo)?;
info!("CPU pstate: boost"); info!(
"CPU Power: min-freq: {:?}, max-freq: {:?}, turbo: {:?}",
config.mode_performance.boost.min_percentage,
config.mode_performance.boost.max_percentage,
!config.mode_performance.boost.no_turbo
);
} }
FanLevel::Silent => { FanLevel::Silent => {
pstate.set_min_perf_pct(0)?; pstate.set_min_perf_pct(config.mode_performance.silent.min_percentage)?;
pstate.set_max_perf_pct(70)?; pstate.set_max_perf_pct(config.mode_performance.silent.max_percentage)?;
pstate.set_no_turbo(true)?; pstate.set_no_turbo(config.mode_performance.silent.no_turbo)?;
info!("CPU pstate: silent, no-turbo"); info!(
"CPU Power: min-freq: {:?}, max-freq: {:?}, turbo: {:?}",
config.mode_performance.silent.min_percentage,
config.mode_performance.silent.max_percentage,
!config.mode_performance.silent.no_turbo
);
} }
} }
} }
@@ -184,7 +192,7 @@ impl RogCore {
/// ///
/// This avoids desktop environments being required to handle it /// This avoids desktop environments being required to handle it
/// (which means it works while in a TTY also) /// (which means it works while in a TTY also)
pub(crate) fn suspend_with_systemd(&self) { pub fn suspend_with_systemd(&self) {
std::process::Command::new("systemctl") std::process::Command::new("systemctl")
.arg("suspend") .arg("suspend")
.spawn() .spawn()
@@ -195,7 +203,7 @@ impl RogCore {
/// ///
/// This avoids desktop environments being required to handle it (which /// This avoids desktop environments being required to handle it (which
/// means it works while in a TTY also) /// means it works while in a TTY also)
pub(crate) fn toggle_airplane_mode(&self) { pub fn toggle_airplane_mode(&self) {
match Command::new("rfkill").arg("list").output() { match Command::new("rfkill").arg("list").output() {
Ok(output) => { Ok(output) => {
if output.status.success() { if output.status.success() {
@@ -230,7 +238,7 @@ impl RogCore {
} }
} }
pub(crate) fn get_raw_device_handle(&mut self) -> NonNull<DeviceHandle<rusb::GlobalContext>> { pub fn get_raw_device_handle(&mut self) -> NonNull<DeviceHandle<rusb::GlobalContext>> {
// Breaking every damn lifetime guarantee rust gives us // Breaking every damn lifetime guarantee rust gives us
unsafe { unsafe {
NonNull::new_unchecked(&mut self.handle as *mut DeviceHandle<rusb::GlobalContext>) NonNull::new_unchecked(&mut self.handle as *mut DeviceHandle<rusb::GlobalContext>)
@@ -239,7 +247,7 @@ impl RogCore {
} }
/// Lifetime is tied to `DeviceHandle` from `RogCore` /// Lifetime is tied to `DeviceHandle` from `RogCore`
pub(crate) struct KeyboardReader<'d, C: 'd> pub struct KeyboardReader<'d, C: 'd>
where where
C: rusb::UsbContext, C: rusb::UsbContext,
{ {
@@ -271,7 +279,7 @@ where
/// ///
/// `report_filter_bytes` is used to filter the data read from the interupt so /// `report_filter_bytes` is used to filter the data read from the interupt so
/// only the relevant byte array is returned. /// only the relevant byte array is returned.
pub(crate) async fn poll_keyboard(&self) -> Option<[u8; 32]> { pub async fn poll_keyboard(&self) -> Option<[u8; 32]> {
let mut buf = [0u8; 32]; let mut buf = [0u8; 32];
match unsafe { self.handle.as_ref() }.read_interrupt( match unsafe { self.handle.as_ref() }.read_interrupt(
self.endpoint, self.endpoint,
@@ -299,7 +307,7 @@ where
/// Because we're holding a pointer to something that *may* go out of scope while the /// Because we're holding a pointer to something that *may* go out of scope while the
/// pointer is held. We're relying on access to struct to be behind a Mutex, and for behaviour /// pointer is held. We're relying on access to struct to be behind a Mutex, and for behaviour
/// that may cause invalididated pointer to cause the program to panic rather than continue. /// that may cause invalididated pointer to cause the program to panic rather than continue.
pub(crate) struct LedWriter<'d, C: 'd> pub struct LedWriter<'d, C: 'd>
where where
C: rusb::UsbContext, C: rusb::UsbContext,
{ {
@@ -326,7 +334,8 @@ where
} }
} }
async fn aura_write(&mut self, message: &[u8]) -> Result<(), AuraError> { /// Should only be used if the bytes you are writing are verified correct
pub async fn aura_write(&mut self, message: &[u8]) -> Result<(), AuraError> {
match unsafe { self.handle.as_ref() }.write_interrupt( match unsafe { self.handle.as_ref() }.write_interrupt(
self.led_endpoint, self.led_endpoint,
message, message,
@@ -384,7 +393,8 @@ where
Ok(()) Ok(())
} }
pub(crate) async fn aura_set_and_save( /// Used to set a builtin mode and save the settings for it
pub async fn aura_set_and_save(
&mut self, &mut self,
supported_modes: &[BuiltInModeByte], supported_modes: &[BuiltInModeByte],
bytes: &[u8], bytes: &[u8],
@@ -405,7 +415,7 @@ where
Err(AuraError::NotSupported) Err(AuraError::NotSupported)
} }
pub(crate) async fn aura_bright_inc( pub async fn aura_bright_inc(
&mut self, &mut self,
supported_modes: &[BuiltInModeByte], supported_modes: &[BuiltInModeByte],
max_bright: u8, max_bright: u8,
@@ -423,7 +433,7 @@ where
Ok(()) Ok(())
} }
pub(crate) async fn aura_bright_dec( pub async fn aura_bright_dec(
&mut self, &mut self,
supported_modes: &[BuiltInModeByte], supported_modes: &[BuiltInModeByte],
min_bright: u8, min_bright: u8,
@@ -444,7 +454,7 @@ where
/// Select next Aura effect /// Select next Aura effect
/// ///
/// If the current effect is the last one then the effect selected wraps around to the first. /// If the current effect is the last one then the effect selected wraps around to the first.
pub(crate) async fn aura_mode_next( pub async fn aura_mode_next(
&mut self, &mut self,
supported_modes: &[BuiltInModeByte], supported_modes: &[BuiltInModeByte],
config: &mut Config, config: &mut Config,
@@ -471,7 +481,7 @@ where
/// Select previous Aura effect /// Select previous Aura effect
/// ///
/// If the current effect is the first one then the effect selected wraps around to the last. /// If the current effect is the first one then the effect selected wraps around to the last.
pub(crate) async fn aura_mode_prev( pub async fn aura_mode_prev(
&mut self, &mut self,
supported_modes: &[BuiltInModeByte], supported_modes: &[BuiltInModeByte],
config: &mut Config, config: &mut Config,
@@ -496,33 +506,6 @@ where
} }
} }
#[derive(Debug, Options)]
pub struct LedBrightness {
level: u8,
}
impl LedBrightness {
pub fn level(&self) -> u8 {
self.level
}
}
impl FromStr for LedBrightness {
type Err = AuraError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"off" => Ok(LedBrightness { level: 0x00 }),
"low" => Ok(LedBrightness { level: 0x01 }),
"med" => Ok(LedBrightness { level: 0x02 }),
"high" => Ok(LedBrightness { level: 0x03 }),
_ => {
println!("Missing required argument, must be one of:\noff,low,med,high\n");
Err(AuraError::ParseBrightness)
}
}
}
}
#[derive(Debug)] #[derive(Debug)]
enum FanLevel { enum FanLevel {
Normal, Normal,

View File

@@ -5,7 +5,7 @@ use dbus::{
}; };
use dbus_tokio::connection; use dbus_tokio::connection;
use log::{error, info, warn}; use log::{error, info, warn};
use rog_aura::{DBUS_IFACE, DBUS_PATH}; use rog_aura::{BuiltInModeByte, DBUS_IFACE, DBUS_PATH};
use std::error::Error; use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -25,7 +25,8 @@ type EffectType = Arc<Mutex<Option<Vec<Vec<u8>>>>>;
// DBUS processing takes 6ms if not tokiod // DBUS processing takes 6ms if not tokiod
pub async fn start_daemon() -> Result<(), Box<dyn Error>> { pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
let laptop = match_laptop(); let laptop = match_laptop();
let mut config = Config::default().read(); let mut config = Config::default().load();
info!("Config loaded");
let mut rogcore = RogCore::new( let mut rogcore = RogCore::new(
laptop.usb_vendor(), laptop.usb_vendor(),
@@ -42,14 +43,22 @@ pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
daemon daemon
}, },
); );
// Reload settings // Reload settings
rogcore.reload(&mut config).await?; rogcore.reload(&mut config).await?;
let mut led_writer = LedWriter::new(rogcore.get_raw_device_handle(), laptop.led_endpoint());
{
let mode_curr = config.current_mode[3];
let mode = config
.builtin_modes
.get_field_from(BuiltInModeByte::from(mode_curr).into())
.unwrap()
.to_owned();
led_writer.aura_write(&mode).await?;
}
// Set up the mutexes // Set up the mutexes
let led_writer = Arc::new(Mutex::new(LedWriter::new( let led_writer = Arc::new(Mutex::new(led_writer));
rogcore.get_raw_device_handle(),
laptop.led_endpoint(),
)));
let config = Arc::new(Mutex::new(config)); let config = Arc::new(Mutex::new(config));
let (resource, connection) = connection::new_system_sync()?; let (resource, connection) = connection::new_system_sync()?;
tokio::spawn(async { tokio::spawn(async {

View File

@@ -272,7 +272,7 @@ impl LaptopBase {
} }
} }
pub(crate) enum GX502Keys { pub enum GX502Keys {
Rog = 0x38, Rog = 0x38,
MicToggle = 0x7C, MicToggle = 0x7C,
Fan = 0xAE, Fan = 0xAE,

View File

@@ -1,7 +1,11 @@
/// Configuration loading, saving
mod config; mod config;
/// The core module which allows writing to LEDs or polling the /// The core module which allows writing to LEDs or polling the
/// laptop keyboard attached devices /// laptop keyboard attached devices
pub mod core; mod core;
/// Start the daemon loop
pub mod daemon; pub mod daemon;
pub mod laptops; /// Laptop matching to determine capabilities
mod laptops;
/// A virtual "consumer device" to help emit the correct key codes
mod virt_device; mod virt_device;

View File

@@ -1,8 +1,11 @@
use daemon::{core::LedBrightness, daemon::start_daemon}; use daemon::daemon::start_daemon;
use env_logger::{Builder, Target}; use env_logger::{Builder, Target};
use gumdrop::Options; use gumdrop::Options;
use log::LevelFilter; use log::LevelFilter;
use rog_aura::{cli_options::SetAuraBuiltin, AuraDbusWriter, LED_MSG_LEN}; use rog_aura::{
cli_options::{LedBrightness, SetAuraBuiltin},
AuraDbusWriter, LED_MSG_LEN,
};
static VERSION: &'static str = "0.8.0"; static VERSION: &'static str = "0.8.0";

View File

@@ -24,12 +24,12 @@ use uhid_virt::{Bus, CreateParams, UHIDDevice};
/// `rogcore.virt_keys().press([0x01, 0, 0, 0x82, 0, 0, 0, 0]); // Sleep` /// `rogcore.virt_keys().press([0x01, 0, 0, 0x82, 0, 0, 0, 0]); // Sleep`
/// ///
/// `rogcore.virt_keys().press([0x01, 0, 0, 0x66, 0, 0, 0, 0]); // Power (menu)` /// `rogcore.virt_keys().press([0x01, 0, 0, 0x66, 0, 0, 0, 0]); // Power (menu)`
pub(crate) struct VirtKeys { pub struct VirtKeys {
device: UHIDDevice<std::fs::File>, device: UHIDDevice<std::fs::File>,
} }
impl VirtKeys { impl VirtKeys {
pub(crate) fn new() -> Self { pub fn new() -> Self {
VirtKeys { VirtKeys {
device: UHIDDevice::create(CreateParams { device: UHIDDevice::create(CreateParams {
name: String::from("Virtual ROG buttons"), name: String::from("Virtual ROG buttons"),
@@ -96,7 +96,7 @@ impl VirtKeys {
} }
/// A single on/off key press /// A single on/off key press
pub(crate) fn press(&mut self, input: [u8; 32]) { pub fn press(&mut self, input: [u8; 32]) {
self.device.write(&input).unwrap(); self.device.write(&input).unwrap();
let mut reset = [0u8; 32]; let mut reset = [0u8; 32];
reset[0] = input[0]; reset[0] = input[0];
@@ -106,7 +106,7 @@ impl VirtKeys {
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub(crate) enum ConsumerKeys { pub enum ConsumerKeys {
Power = 0x30, Power = 0x30,
Sleep = 0x32, Sleep = 0x32,
Menu = 0x0040, Menu = 0x0040,