mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Fix reloading last keyboard brightness on boot
This commit is contained in:
2
.idea/rog-core.iml
generated
2
.idea/rog-core.iml
generated
@@ -14,6 +14,8 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/rog-lib/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/rog-lib/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/rog-lib/benches" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/aura/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/aura/examples" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/rog-core/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/rog-lib/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
|
||||
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.9.4] - 2020-05-05
|
||||
### Changed
|
||||
- Fix reloading last keyboard brightness on boot
|
||||
|
||||
## [0.9.3] - 2020-05-04
|
||||
### Changed
|
||||
- Fixed return of rog-core in client mode
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -678,7 +678,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rog-daemon"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"dbus",
|
||||
"dbus-tokio",
|
||||
|
||||
98
README.md
98
README.md
@@ -2,36 +2,77 @@
|
||||
|
||||
rog-core is a utility for Linux to control many aspects (eventually) of the ASUS ROG laptops like the Zephyrus GX502GW.
|
||||
|
||||
One of the benefits of this app (for me at least) is that you *don't* require a kernel with correct support for the laptop, or custom patched modules. The app reads and writes direct to the device interrupts, and can be customised (in source) quite extensively to do what you want such as directly controlling your laptop backlight rather than emitting a key-press for the DE to handle. There is also the possibility of rebinding fn keys to be macros which emit a series of keyboard presses.
|
||||
One of the benefits of this app (for me at least) is that you *don't* require a kernel with correct support for the
|
||||
laptop, or custom patched modules. The app reads and writes direct to the device interrupts, and can be customised
|
||||
(in source) quite extensively to do what you want such as directly controlling your laptop backlight rather than
|
||||
emitting a key-press for the DE to handle. There is also the possibility of rebinding fn keys to be macros which emit a
|
||||
series of keyboard presses.
|
||||
|
||||
The laptop I currently have is the GX502RW and so I'll be using that for the basis of this app. If I get wireshark captures from others with different ROG laptops then I should be able to add them.
|
||||
The laptop I currently have is the GX502RW and so I'll be using that for the basis of this app. If I get wireshark
|
||||
captures from others with different ROG laptops then I should be able to add them.
|
||||
|
||||
I'm now looking at the kernel source to see if I can add the inputs correctly so they show up as proper evdev events.
|
||||
|
||||
## Requirements
|
||||
|
||||
- `rustc`, `cargo`
|
||||
- `libusb-1.0-0-dev` or equivalent package for your distro
|
||||
- `libdbus-1-dev` or equivalent package for your distro
|
||||
- `rustc` + `cargo` + `make`
|
||||
- `libusb-1.0-0-dev`
|
||||
- `libdbus-1-dev`
|
||||
- `llvm`
|
||||
- `libclang-dev`
|
||||
|
||||
## Installing
|
||||
|
||||
Run `make` then `sudo make install`. If you want to use the daemon mode on system boot you'll need to enable and start the systemd service with:
|
||||
Run `make` then `sudo make install`. If you want to use the daemon mode on system boot you'll need to enable and start
|
||||
the systemd service with:
|
||||
|
||||
```
|
||||
$ sudo systemctl start rog-core.service
|
||||
$ sudo systemctl enable rog-core.service
|
||||
```
|
||||
|
||||
You may also need to activate the service for debian install. If running Pop!_OS, I suggest disabling `system76-power` gnome-shell extension, or at least limiting use of the power-management parts as `rog-core` lets you set the same things (one or the other will overwrite pstates). I will create a shell extension at some point similar to system76, but using the rog-core parts. It is safe to leave `system76-power.service` enabled and use for switching between graphics modes.
|
||||
You may also need to activate the service for debian install. If running Pop!_OS, I suggest disabling `system76-power`
|
||||
gnome-shell extension, or at least limiting use of the power-management parts as `rog-core` lets you set the same things
|
||||
(one or the other will overwrite pstates). I will create a shell extension at some point similar to system76, but using
|
||||
the rog-core parts. It is safe to leave `system76-power.service` enabled and use for switching between graphics modes.
|
||||
|
||||
### Ubuntu PPA
|
||||
|
||||
Alternatively, instead of building manually you can use the PPA.
|
||||
|
||||
```
|
||||
sudo add-apt-repository ppa:lukedjones/rog-core
|
||||
sudo apt-get update
|
||||
sudo install rog-core
|
||||
```
|
||||
|
||||
and if the service isn't enabled:
|
||||
|
||||
```
|
||||
sudo systemctl start rog-core.service
|
||||
sudo systemctl enable rog-core.service
|
||||
```
|
||||
|
||||
I can't guarantee stability of updating via PPA yet.
|
||||
|
||||
## 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).
|
||||
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
|
||||
|
||||
Running the program as a daemon manually will require root. Standard (non-daemon) mode expects to be communicating with the daemon mode over dbus.
|
||||
**NOTE! Fan mode toggling requires a newer kernel**. I'm unsure when the patches required for it got merged - I've
|
||||
tested with the 5.6.6 kernel and above only. To see if the fan-mode changed cat either:
|
||||
|
||||
- `cat /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy` or
|
||||
- `cat /sys/devices/platform/asus-nb-wmi/fan_boost_mode`
|
||||
|
||||
The numbers are 0 = Normal/Balanced, 1 = Boost, 2 = Silent.
|
||||
|
||||
Running the program as a daemon manually will require root. Standard (non-daemon) mode expects to be communicating with
|
||||
the daemon mode over dbus.
|
||||
|
||||
Commands are given by:
|
||||
|
||||
@@ -54,7 +95,14 @@ rog-core <command> <subcommand> --help
|
||||
|
||||
## Daemon mode
|
||||
|
||||
Currently the last used brightness and builtin mode will be saved when set, and loaded when the daemon is started. The effect here is the last settings used are the ones loaded on boot. The daemon also saves the settings per mode as the keyboard does not do this itself - this means cycling through modes with the Aura keys will use the settings that were used via CLI.
|
||||
If the daemon service is enabled then on boot the following will be reloaded from save:
|
||||
|
||||
- LED brightness
|
||||
- Last used built-in mode
|
||||
- fan-boost/thermal mode
|
||||
|
||||
The daemon also saves the settings per mode as the keyboard does not do this itself - this means cycling through modes
|
||||
with the Aura keys will use the settings that were used via CLI.
|
||||
|
||||
## Implemented
|
||||
|
||||
@@ -77,25 +125,6 @@ Currently the last used brightness and builtin mode will be saved when set, and
|
||||
+ [X] Volume + media controls work
|
||||
- [X] Logging - required for journalctl
|
||||
|
||||
Fan mode toggling requires a newer kernel. I'm unsure when the patches required for it got merged - I've tested with the 5.6.6 kernel only.
|
||||
|
||||
## Ubuntu PPA
|
||||
|
||||
```
|
||||
sudo add-apt-repository ppa:lukedjones/rog-core
|
||||
sudo apt-get update
|
||||
sudo install rog-core
|
||||
```
|
||||
|
||||
and if the service isn't enabled:
|
||||
|
||||
```
|
||||
sudo systemctl start rog-core.service
|
||||
sudo systemctl enable rog-core.service
|
||||
```
|
||||
|
||||
I can't guarantee stability of updating via PPA yet.
|
||||
|
||||
## Other Laptops
|
||||
|
||||
**Supported:**
|
||||
@@ -107,7 +136,8 @@ I can't guarantee stability of updating via PPA yet.
|
||||
- GL703(0x1869), GA502 (attempts to use same profile as GX502GW)
|
||||
- GL553(0x1854) GL753 (attempted support from researching 2nd-hand info, multizone may work)
|
||||
|
||||
If the USB product ID is 0x1866 or 0x1869 then the per-key profile with hotkeys *should* work - 0x1866 is tested as this is what I have.
|
||||
If the USB product ID is 0x1866 or 0x1869 then the per-key profile with hotkeys *should* work - 0x1866 is tested as this
|
||||
is what I have.
|
||||
|
||||
### Wireshark captures
|
||||
|
||||
@@ -115,7 +145,8 @@ TODO: see `./wireshark_data/` for some captures.
|
||||
|
||||
### Supporting more laptops
|
||||
|
||||
At a minimum it probably needs to be a Zephyrus laptop. If there is enough interest I will remove the restriction on board names so that anyone can try the app.
|
||||
At a minimum it probably needs to be a Zephyrus laptop. If there is enough interest I will remove the restriction on
|
||||
board names so that anyone can try the app.
|
||||
|
||||
From there I'll need wireshark captures from Windows using Armoury Crate if possible:
|
||||
|
||||
@@ -133,13 +164,14 @@ lsusb:
|
||||
|
||||
First do `lsusb |grep 0b05` and check the part after `0b05:`, output looks like:
|
||||
|
||||
```asm
|
||||
```text
|
||||
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.
|
||||
|
||||
Other helpful info can be gained from `sudo usbhid-dump`, for which you may need to unload kernel drivers. Please google this.
|
||||
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`
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/// A `KeyColourArray` contains all data to change the full set of keyboard
|
||||
/// key colours individually.
|
||||
///
|
||||
/// Each row of the internal array is a full HID packet that can be sent
|
||||
/// to the keyboard EC. One row controls one group of keys, these keys are not
|
||||
/// neccessarily all on the same row of the keyboard, with some splitting between
|
||||
/// two rows.
|
||||
#[derive(Clone)]
|
||||
pub struct KeyColourArray([[u8; 64]; 10]);
|
||||
impl KeyColourArray {
|
||||
@@ -37,6 +44,8 @@ impl KeyColourArray {
|
||||
*bb = b;
|
||||
}
|
||||
|
||||
/// Indexes in to `KeyColourArray` at the correct row and column
|
||||
/// to set a series of three bytes to the chosen R,G,B values
|
||||
pub fn key(&mut self, key: Key) -> (&mut u8, &mut u8, &mut u8) {
|
||||
// Tuples are indexes in to array
|
||||
let (row, col) = match key {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Description=ROG Core Daemon
|
||||
|
||||
[Service]
|
||||
Environment="ROGCORE_LOG=info"
|
||||
ExecStart=/usr/bin/rog-core -d
|
||||
Restart=on-failure
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rog-daemon"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Return show-stopping errors, otherwise map error to a log level
|
||||
|
||||
use crate::{config::Config, virt_device::VirtKeys};
|
||||
use log::{debug, error, info, warn};
|
||||
use rog_aura::{aura_brightness_bytes, error::AuraError, BuiltInModeByte};
|
||||
use log::{error, info, warn};
|
||||
use rusb::DeviceHandle;
|
||||
use std::error::Error;
|
||||
use std::fs::OpenOptions;
|
||||
@@ -13,16 +12,6 @@ use std::process::Command;
|
||||
use std::ptr::NonNull;
|
||||
use std::time::Duration;
|
||||
|
||||
static LED_INIT1: [u8; 2] = [0x5d, 0xb9];
|
||||
static LED_INIT2: &str = "]ASUS Tech.Inc."; // ] == 0x5d
|
||||
static LED_INIT3: [u8; 6] = [0x5d, 0x05, 0x20, 0x31, 0, 0x08];
|
||||
static LED_INIT4: &str = "^ASUS Tech.Inc."; // ^ == 0x5e
|
||||
static LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
|
||||
|
||||
// Only these two packets must be 17 bytes
|
||||
static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy";
|
||||
static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode";
|
||||
|
||||
@@ -75,22 +64,6 @@ impl RogCore {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn reload(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
let path = if Path::new(FAN_TYPE_1_PATH).exists() {
|
||||
FAN_TYPE_1_PATH
|
||||
} else if Path::new(FAN_TYPE_2_PATH).exists() {
|
||||
FAN_TYPE_2_PATH
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let mut file = OpenOptions::new().write(true).open(path)?;
|
||||
file.write_all(format!("{:?}\n", config.fan_mode).as_bytes())?;
|
||||
self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?;
|
||||
info!("Reloaded last saved settings");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn virt_keys(&mut self) -> &mut VirtKeys {
|
||||
&mut self.virt_keys
|
||||
}
|
||||
@@ -108,15 +81,30 @@ impl RogCore {
|
||||
Err(rusb::Error::NoDevice)
|
||||
}
|
||||
|
||||
pub fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
let path = if Path::new(FAN_TYPE_1_PATH).exists() {
|
||||
FAN_TYPE_1_PATH
|
||||
fn get_fan_path() -> Result<&'static str, std::io::Error> {
|
||||
if Path::new(FAN_TYPE_1_PATH).exists() {
|
||||
Ok(FAN_TYPE_1_PATH)
|
||||
} else if Path::new(FAN_TYPE_2_PATH).exists() {
|
||||
FAN_TYPE_2_PATH
|
||||
Ok(FAN_TYPE_2_PATH)
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Fan mode not available",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fan_mode_reload(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
let path = RogCore::get_fan_path()?;
|
||||
let mut file = OpenOptions::new().write(true).open(path)?;
|
||||
file.write_all(format!("{:?}\n", config.fan_mode).as_bytes())?;
|
||||
self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?;
|
||||
info!("Reloaded last saved settings");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
let path = RogCore::get_fan_path()?;
|
||||
let mut fan_ctrl = OpenOptions::new().read(true).write(true).open(path)?;
|
||||
|
||||
let mut buf = String::new();
|
||||
@@ -300,220 +288,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AuraCommand {
|
||||
BrightInc,
|
||||
BrightDec,
|
||||
BuiltinNext,
|
||||
BuiltinPrev,
|
||||
WriteBytes(Vec<u8>),
|
||||
WriteEffect(Vec<Vec<u8>>),
|
||||
ReloadLast,
|
||||
}
|
||||
|
||||
/// UNSAFE: Must live as long as RogCore
|
||||
///
|
||||
/// 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
|
||||
/// that may cause invalididated pointer to cause the program to panic rather than continue.
|
||||
pub struct LedWriter<'d, C: 'd>
|
||||
where
|
||||
C: rusb::UsbContext,
|
||||
{
|
||||
handle: NonNull<DeviceHandle<C>>,
|
||||
bright_min_max: (u8, u8),
|
||||
supported_modes: Vec<BuiltInModeByte>,
|
||||
led_endpoint: u8,
|
||||
initialised: bool,
|
||||
_phantom: PhantomData<&'d DeviceHandle<C>>,
|
||||
}
|
||||
|
||||
/// UNSAFE
|
||||
unsafe impl<'d, C> Send for LedWriter<'d, C> where C: rusb::UsbContext {}
|
||||
unsafe impl<'d, C> Sync for LedWriter<'d, C> where C: rusb::UsbContext {}
|
||||
|
||||
impl<'d, C> LedWriter<'d, C>
|
||||
where
|
||||
C: rusb::UsbContext,
|
||||
{
|
||||
pub fn new(
|
||||
device_handle: NonNull<DeviceHandle<C>>,
|
||||
led_endpoint: u8,
|
||||
bright_min_max: (u8, u8),
|
||||
supported_modes: Vec<BuiltInModeByte>,
|
||||
) -> Self {
|
||||
LedWriter {
|
||||
handle: device_handle,
|
||||
led_endpoint,
|
||||
bright_min_max,
|
||||
supported_modes,
|
||||
initialised: false,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn do_command(
|
||||
&mut self,
|
||||
command: AuraCommand,
|
||||
config: &mut Config,
|
||||
) -> Result<(), AuraError> {
|
||||
if !self.initialised {
|
||||
self.write_bytes(&LED_INIT1).await?;
|
||||
self.write_bytes(LED_INIT2.as_bytes()).await?;
|
||||
self.write_bytes(&LED_INIT3).await?;
|
||||
self.write_bytes(LED_INIT4.as_bytes()).await?;
|
||||
self.write_bytes(&LED_INIT5).await?;
|
||||
self.initialised = true;
|
||||
}
|
||||
|
||||
match command {
|
||||
AuraCommand::BrightInc => {
|
||||
let mut bright = config.brightness;
|
||||
if bright < self.bright_min_max.1 {
|
||||
bright += 1;
|
||||
config.brightness = bright;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.set_and_save(&bytes, config).await?;
|
||||
info!("Increased LED brightness to {:#?}", bright);
|
||||
}
|
||||
}
|
||||
AuraCommand::BrightDec => {
|
||||
let mut bright = config.brightness;
|
||||
if bright > self.bright_min_max.0 {
|
||||
bright -= 1;
|
||||
config.brightness = bright;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.set_and_save(&bytes, config).await?;
|
||||
info!("Decreased LED brightness to {:#?}", bright);
|
||||
}
|
||||
}
|
||||
AuraCommand::BuiltinNext => {
|
||||
// TODO: different path for multi-zone (byte 2 controlled, non-zero)
|
||||
let mode_curr = config.current_mode[3];
|
||||
let idx = self
|
||||
.supported_modes
|
||||
.binary_search(&mode_curr.into())
|
||||
.unwrap();
|
||||
let idx_next = if idx < self.supported_modes.len() - 1 {
|
||||
idx + 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.set_builtin(config, idx_next).await?;
|
||||
}
|
||||
AuraCommand::BuiltinPrev => {
|
||||
// TODO: different path for multi-zone (byte 2 controlled, non-zero)
|
||||
let mode_curr = config.current_mode[3];
|
||||
let idx = self
|
||||
.supported_modes
|
||||
.binary_search(&mode_curr.into())
|
||||
.unwrap();
|
||||
let idx_next = if idx > 0 {
|
||||
idx - 1
|
||||
} else {
|
||||
self.supported_modes.len() - 1
|
||||
};
|
||||
self.set_builtin(config, idx_next).await?;
|
||||
}
|
||||
AuraCommand::WriteBytes(bytes) => self.set_and_save(&bytes, config).await?,
|
||||
AuraCommand::WriteEffect(effect) => self.write_effect(effect).await?,
|
||||
AuraCommand::ReloadLast => self.reload_last_builtin(&config).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Should only be used if the bytes you are writing are verified correct
|
||||
async fn write_bytes(&mut self, message: &[u8]) -> Result<(), AuraError> {
|
||||
match unsafe { self.handle.as_ref() }.write_interrupt(
|
||||
self.led_endpoint,
|
||||
message,
|
||||
Duration::from_millis(5),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
rusb::Error::Timeout => {}
|
||||
_ => error!("Failed to write to led interrupt: {:?}", err),
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_array_of_bytes(&mut self, messages: &[&[u8]]) -> Result<(), AuraError> {
|
||||
for message in messages {
|
||||
self.write_bytes(*message).await?;
|
||||
self.write_bytes(&LED_SET).await?;
|
||||
}
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write an effect block
|
||||
///
|
||||
/// `aura_effect_init` must be called any effect routine, and called only once.
|
||||
async fn write_effect(&self, effect: Vec<Vec<u8>>) -> Result<(), AuraError> {
|
||||
for row in effect.iter() {
|
||||
match unsafe { self.handle.as_ref() }.write_interrupt(
|
||||
self.led_endpoint,
|
||||
row,
|
||||
Duration::from_millis(1),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
rusb::Error::Timeout => {}
|
||||
_ => error!("Failed to write LED interrupt: {:?}", err),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used to set a builtin mode and save the settings for it
|
||||
async fn set_and_save(&mut self, bytes: &[u8], config: &mut Config) -> Result<(), AuraError> {
|
||||
let mode = BuiltInModeByte::from(bytes[3]);
|
||||
// safety pass-through of possible effect write
|
||||
if bytes[1] == 0xbc {
|
||||
self.write_bytes(bytes).await?;
|
||||
return Ok(());
|
||||
} else if self.supported_modes.contains(&mode) || bytes[1] == 0xba {
|
||||
let messages = [bytes];
|
||||
self.write_array_of_bytes(&messages).await?;
|
||||
config.set_field_from(bytes);
|
||||
config.write();
|
||||
return Ok(());
|
||||
}
|
||||
warn!("{:?} not supported", mode);
|
||||
Err(AuraError::NotSupported)
|
||||
}
|
||||
|
||||
/// Used to set a builtin mode and save the settings for it
|
||||
async fn reload_last_builtin(&mut self, config: &Config) -> Result<(), AuraError> {
|
||||
let mode_curr = config.current_mode[3];
|
||||
let mode = config
|
||||
.builtin_modes
|
||||
.get_field_from(mode_curr)
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
self.write_bytes(&mode).await?;
|
||||
info!("Reloaded last built-in mode");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Select next Aura effect
|
||||
///
|
||||
/// If the current effect is the last one then the effect selected wraps around to the first.
|
||||
async fn set_builtin(&mut self, config: &mut Config, index: usize) -> Result<(), AuraError> {
|
||||
let mode_next = config
|
||||
.builtin_modes
|
||||
.get_field_from(self.supported_modes[index].into())
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
println!("{:X?}", &mode_next);
|
||||
self.set_and_save(&mode_next, config).await?;
|
||||
info!("Switched LED mode to {:#?}", self.supported_modes[index]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FanLevel {
|
||||
Normal,
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
use crate::{config::Config, core::*, laptops::match_laptop};
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::*,
|
||||
laptops::match_laptop,
|
||||
led_control::{AuraCommand, LedWriter},
|
||||
};
|
||||
|
||||
use dbus::{
|
||||
channel::Sender,
|
||||
nonblock::Process,
|
||||
tree::{Factory, MTSync, Method, MethodErr, Signal, Tree},
|
||||
};
|
||||
|
||||
use dbus_tokio::connection;
|
||||
use log::{error, info, warn};
|
||||
use rog_aura::{DBUS_IFACE, DBUS_PATH};
|
||||
@@ -46,7 +53,7 @@ pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
);
|
||||
|
||||
// Reload settings
|
||||
rogcore.reload(&mut config).await?;
|
||||
rogcore.fan_mode_reload(&mut config).await?;
|
||||
let mut led_writer = LedWriter::new(
|
||||
rogcore.get_raw_device_handle(),
|
||||
laptop.led_endpoint(),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::config::Config;
|
||||
use crate::core::{AuraCommand, RogCore};
|
||||
use crate::{config::Config, core::RogCore, led_control::AuraCommand};
|
||||
use rog_aura::{error::AuraError, BuiltInModeByte};
|
||||
//use keycode::{KeyMap, KeyMappingId, KeyState, KeyboardState};
|
||||
use crate::virt_device::ConsumerKeys;
|
||||
|
||||
235
rog-core/src/led_control.rs
Normal file
235
rog-core/src/led_control.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
static LED_INIT1: [u8; 2] = [0x5d, 0xb9];
|
||||
static LED_INIT2: &str = "]ASUS Tech.Inc."; // ] == 0x5d
|
||||
static LED_INIT3: [u8; 6] = [0x5d, 0x05, 0x20, 0x31, 0, 0x08];
|
||||
static LED_INIT4: &str = "^ASUS Tech.Inc."; // ^ == 0x5e
|
||||
static LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
|
||||
|
||||
// Only these two packets must be 17 bytes
|
||||
static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
use crate::config::Config;
|
||||
use log::{error, info, warn};
|
||||
use rog_aura::{aura_brightness_bytes, error::AuraError, BuiltInModeByte};
|
||||
use rusb::DeviceHandle;
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr::NonNull;
|
||||
use std::time::Duration;
|
||||
|
||||
pub enum AuraCommand {
|
||||
BrightInc,
|
||||
BrightDec,
|
||||
BuiltinNext,
|
||||
BuiltinPrev,
|
||||
WriteBytes(Vec<u8>),
|
||||
WriteEffect(Vec<Vec<u8>>),
|
||||
ReloadLast,
|
||||
}
|
||||
|
||||
/// UNSAFE: Must live as long as RogCore
|
||||
///
|
||||
/// 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
|
||||
/// that may cause invalididated pointer to cause the program to panic rather than continue.
|
||||
pub struct LedWriter<'d, C: 'd>
|
||||
where
|
||||
C: rusb::UsbContext,
|
||||
{
|
||||
handle: NonNull<DeviceHandle<C>>,
|
||||
bright_min_max: (u8, u8),
|
||||
supported_modes: Vec<BuiltInModeByte>,
|
||||
led_endpoint: u8,
|
||||
initialised: bool,
|
||||
_phantom: PhantomData<&'d DeviceHandle<C>>,
|
||||
}
|
||||
|
||||
/// UNSAFE
|
||||
unsafe impl<'d, C> Send for LedWriter<'d, C> where C: rusb::UsbContext {}
|
||||
unsafe impl<'d, C> Sync for LedWriter<'d, C> where C: rusb::UsbContext {}
|
||||
|
||||
impl<'d, C> LedWriter<'d, C>
|
||||
where
|
||||
C: rusb::UsbContext,
|
||||
{
|
||||
pub fn new(
|
||||
device_handle: NonNull<DeviceHandle<C>>,
|
||||
led_endpoint: u8,
|
||||
bright_min_max: (u8, u8),
|
||||
supported_modes: Vec<BuiltInModeByte>,
|
||||
) -> Self {
|
||||
LedWriter {
|
||||
handle: device_handle,
|
||||
led_endpoint,
|
||||
bright_min_max,
|
||||
supported_modes,
|
||||
initialised: false,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn do_command(
|
||||
&mut self,
|
||||
command: AuraCommand,
|
||||
config: &mut Config,
|
||||
) -> Result<(), AuraError> {
|
||||
if !self.initialised {
|
||||
self.write_bytes(&LED_INIT1).await?;
|
||||
self.write_bytes(LED_INIT2.as_bytes()).await?;
|
||||
self.write_bytes(&LED_INIT3).await?;
|
||||
self.write_bytes(LED_INIT4.as_bytes()).await?;
|
||||
self.write_bytes(&LED_INIT5).await?;
|
||||
self.initialised = true;
|
||||
}
|
||||
|
||||
match command {
|
||||
AuraCommand::BrightInc => {
|
||||
let mut bright = config.brightness;
|
||||
if bright < self.bright_min_max.1 {
|
||||
bright += 1;
|
||||
config.brightness = bright;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.set_and_save(&bytes, config).await?;
|
||||
info!("Increased LED brightness to {:#?}", bright);
|
||||
}
|
||||
}
|
||||
AuraCommand::BrightDec => {
|
||||
let mut bright = config.brightness;
|
||||
if bright > self.bright_min_max.0 {
|
||||
bright -= 1;
|
||||
config.brightness = bright;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.set_and_save(&bytes, config).await?;
|
||||
info!("Decreased LED brightness to {:#?}", bright);
|
||||
}
|
||||
}
|
||||
AuraCommand::BuiltinNext => {
|
||||
// TODO: different path for multi-zone (byte 2 controlled, non-zero)
|
||||
let mode_curr = config.current_mode[3];
|
||||
let idx = self
|
||||
.supported_modes
|
||||
.binary_search(&mode_curr.into())
|
||||
.unwrap();
|
||||
let idx_next = if idx < self.supported_modes.len() - 1 {
|
||||
idx + 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.set_builtin(config, idx_next).await?;
|
||||
}
|
||||
AuraCommand::BuiltinPrev => {
|
||||
// TODO: different path for multi-zone (byte 2 controlled, non-zero)
|
||||
let mode_curr = config.current_mode[3];
|
||||
let idx = self
|
||||
.supported_modes
|
||||
.binary_search(&mode_curr.into())
|
||||
.unwrap();
|
||||
let idx_next = if idx > 0 {
|
||||
idx - 1
|
||||
} else {
|
||||
self.supported_modes.len() - 1
|
||||
};
|
||||
self.set_builtin(config, idx_next).await?;
|
||||
}
|
||||
AuraCommand::WriteBytes(bytes) => self.set_and_save(&bytes, config).await?,
|
||||
AuraCommand::WriteEffect(effect) => self.write_effect(effect).await?,
|
||||
AuraCommand::ReloadLast => self.reload_last_builtin(&config).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Should only be used if the bytes you are writing are verified correct
|
||||
async fn write_bytes(&mut self, message: &[u8]) -> Result<(), AuraError> {
|
||||
match unsafe { self.handle.as_ref() }.write_interrupt(
|
||||
self.led_endpoint,
|
||||
message,
|
||||
Duration::from_millis(5),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
rusb::Error::Timeout => {}
|
||||
_ => error!("Failed to write to led interrupt: {:?}", err),
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_array_of_bytes(&mut self, messages: &[&[u8]]) -> Result<(), AuraError> {
|
||||
for message in messages {
|
||||
self.write_bytes(*message).await?;
|
||||
self.write_bytes(&LED_SET).await?;
|
||||
}
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write an effect block
|
||||
///
|
||||
/// `aura_effect_init` must be called any effect routine, and called only once.
|
||||
async fn write_effect(&self, effect: Vec<Vec<u8>>) -> Result<(), AuraError> {
|
||||
for row in effect.iter() {
|
||||
match unsafe { self.handle.as_ref() }.write_interrupt(
|
||||
self.led_endpoint,
|
||||
row,
|
||||
Duration::from_millis(1),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
rusb::Error::Timeout => {}
|
||||
_ => error!("Failed to write LED interrupt: {:?}", err),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used to set a builtin mode and save the settings for it
|
||||
async fn set_and_save(&mut self, bytes: &[u8], config: &mut Config) -> Result<(), AuraError> {
|
||||
let mode = BuiltInModeByte::from(bytes[3]);
|
||||
// safety pass-through of possible effect write
|
||||
if bytes[1] == 0xbc {
|
||||
self.write_bytes(bytes).await?;
|
||||
return Ok(());
|
||||
} else if self.supported_modes.contains(&mode) || bytes[1] == 0xba {
|
||||
let messages = [bytes];
|
||||
self.write_array_of_bytes(&messages).await?;
|
||||
config.set_field_from(bytes);
|
||||
config.write();
|
||||
return Ok(());
|
||||
}
|
||||
warn!("{:?} not supported", mode);
|
||||
Err(AuraError::NotSupported)
|
||||
}
|
||||
|
||||
/// Used to set a builtin mode and save the settings for it
|
||||
async fn reload_last_builtin(&mut self, config: &Config) -> Result<(), AuraError> {
|
||||
let mode_curr = config.current_mode[3];
|
||||
let mode = config
|
||||
.builtin_modes
|
||||
.get_field_from(mode_curr)
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
self.write_bytes(&mode).await?;
|
||||
// Reload brightness too
|
||||
let bright = config.brightness;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.write_bytes(&bytes).await?;
|
||||
info!("Reloaded last used mode and brightness");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Select next Aura effect
|
||||
///
|
||||
/// If the current effect is the last one then the effect selected wraps around to the first.
|
||||
async fn set_builtin(&mut self, config: &mut Config, index: usize) -> Result<(), AuraError> {
|
||||
let mode_next = config
|
||||
.builtin_modes
|
||||
.get_field_from(self.supported_modes[index].into())
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
println!("{:X?}", &mode_next);
|
||||
self.set_and_save(&mode_next, config).await?;
|
||||
info!("Switched LED mode to {:#?}", self.supported_modes[index]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,7 @@ mod core;
|
||||
pub mod daemon;
|
||||
/// Laptop matching to determine capabilities
|
||||
mod laptops;
|
||||
///
|
||||
mod led_control;
|
||||
/// A virtual "consumer device" to help emit the correct key codes
|
||||
mod virt_device;
|
||||
|
||||
@@ -7,7 +7,7 @@ use rog_aura::{
|
||||
AuraDbusWriter, LED_MSG_LEN,
|
||||
};
|
||||
|
||||
static VERSION: &'static str = "0.9.3";
|
||||
static VERSION: &'static str = "0.9.4";
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
struct CLIStart {
|
||||
|
||||
Reference in New Issue
Block a user