Compare commits

...

30 Commits

Author SHA1 Message Date
Luke D. Jones 2584d69930 Update deps 2022-07-25 21:38:00 +12:00
Luke D. Jones 523f39cf9c Version bump for RC 2022-07-25 21:34:33 +12:00
Luke D. Jones 669760223e Clean up erroneously included files 2022-07-25 21:32:58 +12:00
Luke D. Jones 71ec13fa9f ROGCC: Attempt to add LED brightness 2022-07-25 21:21:32 +12:00
Luke D. Jones 409528b286 ROGCC: Better control of notifs, add panel_od 2022-07-25 20:55:30 +12:00
Luke D. Jones 17df3cf01d Add rog-control-center to the workspace 2022-07-25 16:43:48 +12:00
Luke D. Jones 808a1d2470 Fix misnamed led dbus method 2022-07-25 14:22:29 +12:00
Luke D. Jones 840c500b5e Switch a keyboard prod_id to enum 2022-07-25 14:07:29 +12:00
Luke D. Jones 42dc360d16 Bump daemon version 2022-07-25 12:43:17 +12:00
Luke D. Jones f6183597c9 Trial BTreeMap<AuraModeNum, AuraEffect> return for led dbus 2022-07-25 10:26:47 +12:00
Luke D. Jones 79a45c4f10 Add to/from [f32;3] for Colour] 2022-07-25 09:51:25 +12:00
Luke D. Jones 19370215c0 Cleanup 2022-07-24 20:55:09 +12:00
Luke D. Jones 030dd661b8 Switch zbus led_mode to return AuraModeNum 2022-07-24 20:45:20 +12:00
Luke D. Jones 1fc12d9855 Make CurveData members public 2022-07-24 10:50:28 +12:00
Luke D. Jones 6c1b2b70ea Make FanCurveSet members public 2022-07-24 10:47:40 +12:00
Luke D. Jones 23f9af35bf Add Hash derive to Profile 2022-07-24 09:43:22 +12:00
Luke D. Jones 526626b80c Minor tweaks on derives 2022-07-24 09:28:00 +12:00
Luke Jones 10eaaac54b Merge branch 'sova/G713IC-led-support' into 'main'
Add LED support for G713IC

See merge request asus-linux/asusctl!124
2022-07-21 12:39:43 +00:00
SoVa 901a3ddcc9 Add LED support for G713IC 2022-07-21 14:18:33 +02:00
Luke Jones e6ebf72a11 Merge branch 'main' into 'main'
Fix some typos.

See merge request asus-linux/asusctl!123
2022-07-21 07:59:16 +00:00
Luke D. Jones cd7e748c88 Prep new release 2022-07-21 19:36:17 +12:00
成超(Cheng Chao) a313359ef6 Fix some typos. 2022-07-21 12:46:44 +08:00
Luke Jones f222eef6b7 Update CHANGELOG.md 2022-07-21 03:40:04 +00:00
Luke Jones e6f3aeb851 Merge branch 'fluke/multi-led-power' into 'main'
Make LED power more universal

Closes #219

See merge request asus-linux/asusctl!122
2022-07-21 03:39:11 +00:00
Luke D. Jones 22605e57cc Properly set full defaults for LED power 2022-07-21 14:56:30 +12:00
Luke D. Jones 02fb7addf4 Make LED power more universal
Closes #219
2022-07-21 14:48:16 +12:00
Luke Jones bdbb403a0e Merge branch 'fluke/errors' into 'main'
Improve error handling in some cases

See merge request asus-linux/asusctl!121
2022-07-20 09:01:55 +00:00
Luke D. Jones 7a8bede92f Return error if a pixel-gif is larger than the anime-display dimensions 2022-07-20 20:52:03 +12:00
Luke D. Jones a71a40b509 Make rog-anime more tolerent of faults 2022-07-20 20:17:43 +12:00
Luke D. Jones 42fc5a5392 Minor doc comment change 2022-07-18 15:43:09 +12:00
58 changed files with 4368 additions and 499 deletions
+16
View File
@@ -5,6 +5,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased ]
### Added
- `rog-control-center` has now been moved in to the main workspace due to
the heavy dependencies on most of the rog crates
## [4.3.0] - 2022-07-21
### Added
- Clear command for anime `asusctl anime --clear` will clear the display
- Re-added support for LED power states on `0x1866` type keyboards
### Changed
- Make rog-anime more error tolerent. Remove various asserts and return errors instead
- Return error if a pixel-gif is larger than the anime-display dimensions
- Both Anime and Aura dbus interfaces are changed a little
- Aura power has changed, all power related settings are now in one method
- Anime methods will now return an error (if errored)
- /org/asuslinux/Led renamed to /org/asuslinux/Aura
## [4.2.1] - 2022-07-18
### Added
Generated
+1504 -76
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace]
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-supported", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles"]
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-supported", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles", "rog-control-center"]
[profile.release]
# thin = 57s, asusd = 9.0M
+2 -2
View File
@@ -90,7 +90,7 @@ where the number is a percentage.
Some options that you find in Armory Crate are available under this controller, so far there is:
- POST sound: this is the sound you here on bios boot post
- POST sound: this is the sound you hear on bios boot post
- G-Sync: this controls if the dGPU (Nvidia) is the *only* GPU, making it the main GPU and disabling the iGPU
These options are not written to the config file as they are stored in efivars. The only way to change these is to use the exposed safe dbus methods, or use the `asusctl` CLI tool.
@@ -293,7 +293,7 @@ A plain non-float integer.
## asusctl
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. I can be used in any place a terminal app can be used.
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. It can be used in any place a terminal app can be used.
This program will query `asusd` for the `Support` level of the laptop and show or hide options according to this support level.
+9
View File
@@ -11,6 +11,7 @@ datarootdir = $(prefix)/share
libdir = $(exec_prefix)/lib
zshcpl = $(datarootdir)/zsh/site-functions
BIN_ROG := rog-control-center
BIN_C := asusctl
BIN_D := asusd
BIN_U := asusd-user
@@ -39,6 +40,10 @@ distclean:
rm -rf .cargo vendor vendor.tar.xz
install:
$(INSTALL_PROGRAM) "./target/release/$(BIN_ROG)" "$(DESTDIR)$(bindir)/$(BIN_ROG)"
$(INSTALL_DATA) "./rog-control-center/data/$(BIN_ROG).desktop" "$(DESTDIR)$(datarootdir)/applications/$(BIN_ROG).desktop"
$(INSTALL_DATA) "./rog-control-center/data/$(BIN_ROG).png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/$(BIN_ROG).png"
$(INSTALL_PROGRAM) "./target/release/$(BIN_C)" "$(DESTDIR)$(bindir)/$(BIN_C)"
$(INSTALL_PROGRAM) "./target/release/$(BIN_D)" "$(DESTDIR)$(bindir)/$(BIN_D)"
$(INSTALL_PROGRAM) "./target/release/$(BIN_U)" "$(DESTDIR)$(bindir)/$(BIN_U)"
@@ -68,6 +73,10 @@ install:
cd rog-anime/data && find "./anime" -type f -exec install -Dm 755 "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
uninstall:
rm -f "$(DESTDIR)$(bindir)/$(BIN_ROG)"
rm -r "$(DESTDIR)$(datarootdir)/applications/$(BIN_ROG).desktop"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/$(BIN_ROG).png"
rm -f "$(DESTDIR)$(bindir)/$(BIN_C)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_D)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_N)"
+2
View File
@@ -5,6 +5,8 @@
`asusd` is a utility for Linux to control many aspects of various ASUS laptops
but can also be used with non-asus laptops with reduced features.
Now includes a GUI, `rog-control-center`.
## Kernel support
**The minimum supported kernel version is 5.15**
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "asusctl"
version = "4.2.1"
version = "4.3.0"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
+1 -1
View File
@@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn Error>> {
client
.proxies()
.anime()
.write(matrix.into_data_buffer(anime_type))
.write(matrix.into_data_buffer(anime_type)?)
.unwrap();
Ok(())
+1 -1
View File
@@ -29,7 +29,7 @@ fn main() {
client
.proxies()
.anime()
.write(matrix.into_data_buffer(anime_type))
.write(matrix.into_data_buffer(anime_type).unwrap())
.unwrap();
sleep(Duration::from_millis(300));
}
+2 -1
View File
@@ -1,5 +1,6 @@
use rog_anime::{usb::get_anime_type, AnimeDataBuffer, AnimeGrid};
use rog_dbus::RogDbusClientBlocking;
use std::convert::TryFrom;
// In usable data:
// Top row start at 1, ends at 32
@@ -39,7 +40,7 @@ fn main() {
}
}
let matrix = <AnimeDataBuffer>::from(matrix);
let matrix = <AnimeDataBuffer>::try_from(matrix).unwrap();
client.proxies().anime().write(matrix).unwrap();
}
+2 -1
View File
@@ -1,3 +1,4 @@
use std::convert::TryFrom;
use std::{env, error::Error, path::Path, process::exit};
use rog_anime::{
@@ -32,7 +33,7 @@ fn main() -> Result<(), Box<dyn Error>> {
client
.proxies()
.anime()
.write(<AnimeDataBuffer>::from(&matrix))
.write(<AnimeDataBuffer>::try_from(&matrix)?)
.unwrap();
Ok(())
+2 -1
View File
@@ -1,3 +1,4 @@
use std::convert::TryFrom;
use std::{
env, error::Error, f32::consts::PI, path::Path, process::exit, thread::sleep, time::Duration,
};
@@ -41,7 +42,7 @@ fn main() -> Result<(), Box<dyn Error>> {
client
.proxies()
.anime()
.write(<AnimeDataBuffer>::from(&matrix))
.write(<AnimeDataBuffer>::try_from(&matrix)?)
.unwrap();
sleep(Duration::from_micros(500));
}
+2
View File
@@ -16,6 +16,8 @@ pub struct AnimeCommand {
pub boot_enable: Option<bool>,
#[options(meta = "", help = "set global AniMe brightness value")]
pub brightness: Option<f32>,
#[options(help = "clear the display")]
pub clear: bool,
#[options(command)]
pub command: Option<AnimeActions>,
}
+20 -3
View File
@@ -3,7 +3,23 @@ use rog_aura::{error::Error, AuraEffect, AuraModeNum, AuraZone, Colour, Directio
use std::str::FromStr;
#[derive(Options)]
pub struct LedPowerCommand {
pub struct LedPowerCommand1 {
#[options(help = "print help message")]
pub help: bool,
#[options(meta = "", help = "Control if LEDs enabled while awake <true/false>")]
pub awake: Option<bool>,
#[options(meta = "", help = "Use with awake option <true/false>")]
pub keyboard: Option<bool>,
#[options(meta = "", help = "Use with awake option <true/false>")]
pub lightbar: Option<bool>,
#[options(meta = "", help = "Control boot animations <true/false>")]
pub boot: Option<bool>,
#[options(meta = "", help = "Control suspend animations <true/false>")]
pub sleep: Option<bool>,
}
#[derive(Options)]
pub struct LedPowerCommand2 {
#[options(help = "print help message")]
pub help: bool,
#[options(command)]
@@ -12,12 +28,13 @@ pub struct LedPowerCommand {
#[derive(Options)]
pub enum SetAuraEnabled {
/// Applies to both old and new models
#[options(help = "set <keyboard, logo, lightbar> to enabled while device is awake")]
Awake(AuraEnabled),
#[options(help = "set <keyboard, logo, lightbar> to enabled while the device is booting")]
Boot(AuraEnabled),
#[options(help = "set <keyboard, logo, lightbar> to animate while the device is suspended")]
Sleep(AuraEnabled),
#[options(help = "set <keyboard, logo, lightbar> to enabled while device is awake")]
Awake(AuraEnabled),
#[options(help = "set <keyboard, logo, lightbar> to animate while the device is shutdown")]
Shutdown(AuraEnabled),
}
+5 -3
View File
@@ -1,6 +1,6 @@
use crate::{
anime_cli::AnimeCommand,
aura_cli::{LedBrightness, LedPowerCommand, SetAuraBuiltin},
aura_cli::{LedBrightness, LedPowerCommand1, LedPowerCommand2, SetAuraBuiltin},
profiles_cli::{FanCurveCommand, ProfileCommand},
};
use gumdrop::Options;
@@ -29,8 +29,10 @@ pub struct CliStart {
pub enum CliCommand {
#[options(help = "Set the keyboard lighting from built-in modes")]
LedMode(LedModeCommand),
#[options(help = "Set the keyboard lighting from built-in modes")]
LedPower(LedPowerCommand),
#[options(help = "Set the LED power states")]
LedPow1(LedPowerCommand1),
#[options(help = "Set the LED power states")]
LedPow2(LedPowerCommand2),
#[options(help = "Set or select platform_profile")]
Profile(ProfileCommand),
#[options(help = "Set, select, or modify fan curves if supported")]
+148 -145
View File
@@ -1,15 +1,16 @@
use std::convert::TryFrom;
use std::process::Command;
use std::thread::sleep;
use std::{env::args, path::Path};
use aura_cli::LedPowerCommand;
use aura_cli::{LedPowerCommand1, LedPowerCommand2};
use gumdrop::{Opt, Options};
use anime_cli::{AnimeActions, AnimeCommand};
use profiles_cli::{FanCurveCommand, ProfileCommand};
use rog_anime::usb::get_anime_type;
use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, Vec2};
use rog_aura::usb::AuraControl;
use rog_aura::usb::{AuraDev1866, AuraDev19b6, AuraDevice, AuraPowerDev};
use rog_aura::{self, AuraEffect};
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::error::ProfileError;
@@ -27,8 +28,6 @@ mod aura_cli;
mod cli_opts;
mod profiles_cli;
const CONFIG_ADVICE: &str = "A config file need to be removed so a new one can be generated";
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = args().skip(1).collect();
@@ -82,15 +81,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
fn print_error_help(err: Box<dyn std::error::Error>, supported: Option<&SupportedFunctions>) {
if do_diagnose("asusd") {
println!("\nError: {}\n", err);
print_versions();
check_service("asusd");
println!("\nError: {}\n", err);
print_versions();
println!();
print_laptop_info();
if let Some(supported) = supported {
println!();
print_laptop_info();
if let Some(supported) = supported {
println!();
println!("Supported laptop functions:\n\n{}", supported);
}
println!("Supported laptop functions:\n\n{}", supported);
}
}
@@ -115,7 +113,7 @@ fn print_laptop_info() {
println!("Board name: {}", board_name.trim());
}
fn do_diagnose(name: &str) -> bool {
fn check_service(name: &str) -> bool {
if name != "asusd" && !check_systemd_unit_enabled(name) {
println!(
"\n\x1b[0;31m{} is not enabled, enable it with `systemctl enable {}\x1b[0m",
@@ -128,13 +126,6 @@ fn do_diagnose(name: &str) -> bool {
name, name
);
return true;
} else {
println!("\nSome error happened (sorry)");
println!(
"Please use `systemctl status {}` and `journalctl -b -u {}` for more information",
name, name
);
println!("{}", CONFIG_ADVICE);
}
false
}
@@ -146,7 +137,8 @@ fn do_parsed(
) -> Result<(), Box<dyn std::error::Error>> {
match &parsed.command {
Some(CliCommand::LedMode(mode)) => handle_led_mode(dbus, &supported.keyboard_led, mode)?,
Some(CliCommand::LedPower(pow)) => handle_led_power(dbus, &supported.keyboard_led, pow)?,
Some(CliCommand::LedPow1(pow)) => handle_led_power1(dbus, &supported.keyboard_led, pow)?,
Some(CliCommand::LedPow2(pow)) => handle_led_power2(dbus, &supported.keyboard_led, pow)?,
Some(CliCommand::Profile(cmd)) => handle_profile(dbus, &supported.platform_profile, cmd)?,
Some(CliCommand::FanCurve(cmd)) => {
handle_fan_curve(dbus, &supported.platform_profile, cmd)?
@@ -165,7 +157,24 @@ fn do_parsed(
println!("{}", CliStart::usage());
println!();
if let Some(cmdlist) = CliStart::command_list() {
println!("{}", cmdlist);
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_string()).collect();
for command in commands.iter().filter(|command| {
if !matches!(
supported.keyboard_led.prod_id,
AuraDevice::X1854 | AuraDevice::X1869 | AuraDevice::X1866
) && command.trim().starts_with("led-pow-1")
{
return false;
}
if supported.keyboard_led.prod_id != AuraDevice::X19B6
&& command.trim().starts_with("led-pow-2")
{
return false;
}
true
}) {
println!("{}", command);
}
}
println!("\nExtra help can be requested on any command or subcommand:");
@@ -239,6 +248,13 @@ fn handle_anime(
verify_brightness(bright);
dbus.proxies().anime().set_brightness(bright)?
}
if cmd.clear {
let anime_type = get_anime_type()?;
let data = vec![0u8; anime_type.data_length()];
let tmp = AnimeDataBuffer::from_vec(anime_type, data)?;
dbus.proxies().anime().write(tmp)?;
}
if let Some(action) = cmd.command.as_ref() {
let anime_type = get_anime_type()?;
match action {
@@ -263,7 +279,7 @@ fn handle_anime(
dbus.proxies()
.anime()
.write(<AnimeDataBuffer>::from(&matrix))?;
.write(<AnimeDataBuffer>::try_from(&matrix)?)?;
}
AnimeActions::PixelImage(image) => {
if image.help_requested() || image.path.is_empty() {
@@ -284,7 +300,7 @@ fn handle_anime(
dbus.proxies()
.anime()
.write(matrix.into_data_buffer(anime_type))?;
.write(matrix.into_data_buffer(anime_type)?)?;
}
AnimeActions::Gif(gif) => {
if gif.help_requested() || gif.path.is_empty() {
@@ -423,10 +439,70 @@ fn handle_led_mode(
Ok(())
}
fn handle_led_power(
fn handle_led_power1(
dbus: &RogDbusClientBlocking,
_supported: &LedSupportedFunctions,
power: &LedPowerCommand,
supported: &LedSupportedFunctions,
power: &LedPowerCommand1,
) -> Result<(), Box<dyn std::error::Error>> {
if power.awake.is_none()
&& power.sleep.is_none()
&& power.boot.is_none()
&& power.keyboard.is_none()
&& power.lightbar.is_none()
{
if !power.help {
println!("Missing arg or command\n");
}
println!("{}\n", power.self_usage());
return Ok(());
}
if !matches!(
supported.prod_id,
AuraDevice::X1854 | AuraDevice::X1869 | AuraDevice::X1866
) {
println!("These options are for keyboards of product ID 0x1866 only");
return Ok(());
}
let mut enabled: Vec<AuraDev1866> = Vec::new();
let mut disabled: Vec<AuraDev1866> = Vec::new();
let mut check = |e: Option<bool>, a: AuraDev1866| {
if let Some(arg) = e {
if arg {
enabled.push(a);
} else {
disabled.push(a);
}
}
};
check(power.awake, AuraDev1866::Awake);
check(power.boot, AuraDev1866::Boot);
check(power.sleep, AuraDev1866::Sleep);
check(power.keyboard, AuraDev1866::Keyboard);
check(power.lightbar, AuraDev1866::Lightbar);
let data = AuraPowerDev {
x1866: enabled,
x19b6: vec![],
};
dbus.proxies().led().set_leds_power(data, true)?;
let data = AuraPowerDev {
x1866: disabled,
x19b6: vec![],
};
dbus.proxies().led().set_leds_power(data, false)?;
Ok(())
}
fn handle_led_power2(
dbus: &RogDbusClientBlocking,
supported: &LedSupportedFunctions,
power: &LedPowerCommand2,
) -> Result<(), Box<dyn std::error::Error>> {
if power.command().is_none() {
if !power.help {
@@ -435,7 +511,7 @@ fn handle_led_power(
println!("{}\n", power.self_usage());
println!("Commands available");
if let Some(cmdlist) = LedPowerCommand::command_list() {
if let Some(cmdlist) = LedPowerCommand2::command_list() {
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_string()).collect();
for command in commands.iter() {
println!("{}", command);
@@ -452,133 +528,60 @@ fn handle_led_power(
return Ok(());
}
if supported.prod_id != AuraDevice::X19B6 {
println!("This option applies only to keyboards with product ID 0x19b6")
}
let mut enabled: Vec<AuraDev19b6> = Vec::new();
let mut disabled: Vec<AuraDev19b6> = Vec::new();
let mut check = |e: Option<bool>, a: AuraDev19b6| {
if let Some(arg) = e {
if arg {
enabled.push(a);
} else {
disabled.push(a);
}
}
};
match pow {
// TODO: make this a macro or something
aura_cli::SetAuraEnabled::Boot(arg) => {
let mut enabled: Vec<AuraControl> = Vec::new();
let mut disabled: Vec<AuraControl> = Vec::new();
arg.keyboard.map(|v| {
if v {
enabled.push(AuraControl::BootKeyb)
} else {
disabled.push(AuraControl::BootKeyb)
}
});
arg.logo.map(|v| {
if v {
enabled.push(AuraControl::BootLogo)
} else {
disabled.push(AuraControl::BootLogo)
}
});
arg.lightbar.map(|v| {
if v {
enabled.push(AuraControl::BootBar)
} else {
disabled.push(AuraControl::BootBar)
}
});
if !enabled.is_empty() {
dbus.proxies().led().set_leds_enabled(enabled)?;
}
if !disabled.is_empty() {
dbus.proxies().led().set_leds_disabled(disabled)?;
}
check(arg.keyboard, AuraDev19b6::BootKeyb);
check(arg.logo, AuraDev19b6::BootLogo);
check(arg.lightbar, AuraDev19b6::BootBar);
}
aura_cli::SetAuraEnabled::Sleep(arg) => {
let mut enabled: Vec<AuraControl> = Vec::new();
let mut disabled: Vec<AuraControl> = Vec::new();
arg.keyboard.map(|v| {
if v {
enabled.push(AuraControl::SleepKeyb)
} else {
disabled.push(AuraControl::SleepKeyb)
}
});
arg.logo.map(|v| {
if v {
enabled.push(AuraControl::SleepLogo)
} else {
disabled.push(AuraControl::SleepLogo)
}
});
arg.lightbar.map(|v| {
if v {
enabled.push(AuraControl::SleepBar)
} else {
disabled.push(AuraControl::SleepBar)
}
});
if !enabled.is_empty() {
dbus.proxies().led().set_leds_enabled(enabled)?;
}
if !disabled.is_empty() {
dbus.proxies().led().set_leds_disabled(disabled)?;
}
check(arg.keyboard, AuraDev19b6::SleepKeyb);
check(arg.logo, AuraDev19b6::SleepLogo);
check(arg.lightbar, AuraDev19b6::SleepBar);
}
aura_cli::SetAuraEnabled::Awake(arg) => {
let mut enabled: Vec<AuraControl> = Vec::new();
let mut disabled: Vec<AuraControl> = Vec::new();
arg.keyboard.map(|v| {
if v {
enabled.push(AuraControl::AwakeKeyb)
} else {
disabled.push(AuraControl::AwakeKeyb)
}
});
arg.logo.map(|v| {
if v {
enabled.push(AuraControl::AwakeLogo)
} else {
disabled.push(AuraControl::AwakeLogo)
}
});
arg.lightbar.map(|v| {
if v {
enabled.push(AuraControl::AwakeBar)
} else {
disabled.push(AuraControl::AwakeBar)
}
});
if !enabled.is_empty() {
dbus.proxies().led().set_leds_enabled(enabled)?;
}
if !disabled.is_empty() {
dbus.proxies().led().set_leds_disabled(disabled)?;
}
check(arg.keyboard, AuraDev19b6::AwakeKeyb);
check(arg.logo, AuraDev19b6::AwakeLogo);
check(arg.lightbar, AuraDev19b6::AwakeBar);
}
aura_cli::SetAuraEnabled::Shutdown(arg) => {
let mut enabled: Vec<AuraControl> = Vec::new();
let mut disabled: Vec<AuraControl> = Vec::new();
arg.keyboard.map(|v| {
if v {
enabled.push(AuraControl::ShutdownKeyb)
} else {
disabled.push(AuraControl::ShutdownKeyb)
}
});
arg.logo.map(|v| {
if v {
enabled.push(AuraControl::ShutdownLogo)
} else {
disabled.push(AuraControl::ShutdownLogo)
}
});
arg.lightbar.map(|v| {
if v {
enabled.push(AuraControl::ShutdownBar)
} else {
disabled.push(AuraControl::ShutdownBar)
}
});
if !enabled.is_empty() {
dbus.proxies().led().set_leds_enabled(enabled)?;
}
if !disabled.is_empty() {
dbus.proxies().led().set_leds_disabled(disabled)?;
}
check(arg.keyboard, AuraDev19b6::ShutdownKeyb);
check(arg.logo, AuraDev19b6::ShutdownLogo);
check(arg.lightbar, AuraDev19b6::ShutdownBar);
}
}
if !enabled.is_empty() {
let data = AuraPowerDev {
x1866: vec![],
x19b6: enabled,
};
dbus.proxies().led().set_leds_power(data, true)?;
}
if !disabled.is_empty() {
let data = AuraPowerDev {
x1866: vec![],
x19b6: disabled,
};
dbus.proxies().led().set_leds_power(data, false)?;
}
}
Ok(())
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "daemon"
version = "4.2.1"
version = "4.3.2"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
+24 -6
View File
@@ -18,6 +18,7 @@ use rusb::{Device, DeviceHandle};
use smol::{stream::StreamExt, Executor};
use std::{
cell::RefCell,
convert::TryFrom,
error::Error,
sync::{Arc, Mutex, MutexGuard},
thread::sleep,
@@ -167,7 +168,14 @@ impl CtrlAnime {
inner
.try_lock()
.map(|lock| {
lock.write_data_buffer(frame);
lock.write_data_buffer(frame)
.map_err(|err| {
warn!(
"rog_anime::run_animation:callback {}",
err
);
})
.ok();
false // Don't exit yet
})
.map_err(|err| {
@@ -183,6 +191,8 @@ impl CtrlAnime {
once = false;
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(image.as_ref().clone())
.map_err(|e| error!("{}", e))
.ok();
}
}
ActionData::Pause(duration) => sleep(*duration),
@@ -201,9 +211,16 @@ impl CtrlAnime {
}
// Clear the display on exit
if let Ok(lock) = inner.try_lock() {
let data =
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()]);
lock.write_data_buffer(data);
if let Ok(data) =
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()])
.map_err(|e| error!("{}", e))
{
lock.write_data_buffer(data)
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
}
}
// Loop ended, set the atmonics
thread_running.store(false, Ordering::SeqCst);
@@ -254,7 +271,7 @@ impl CtrlAnime {
/// Write only a data packet. This will modify the leds brightness using the
/// global brightness set in config.
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) {
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> {
for led in buffer.data_mut()[7..].iter_mut() {
let mut bright = *led as f32 * self.config.brightness;
if bright > 254.0 {
@@ -262,11 +279,12 @@ impl CtrlAnime {
}
*led = bright as u8;
}
let data = AnimePacketType::from(buffer);
let data = AnimePacketType::try_from(buffer)?;
for row in data.iter() {
self.write_bytes(row);
}
self.write_bytes(&pkt_for_flush());
Ok(())
}
fn do_initialization(&self) {
+7 -2
View File
@@ -1,6 +1,7 @@
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use log::warn;
use rog_anime::{
usb::{pkt_for_apply, pkt_for_set_boot, pkt_for_set_on},
AnimeDataBuffer, AnimePowerStates,
@@ -27,14 +28,18 @@ impl crate::ZbusAdd for CtrlAnimeZbus {
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnimeZbus {
/// Writes a data stream of length. Will force system thread to exit until it is restarted
fn write(&self, input: AnimeDataBuffer) {
fn write(&self, input: AnimeDataBuffer) -> zbus::fdo::Result<()> {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
lock.write_data_buffer(input);
lock.write_data_buffer(input).map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
err
})?;
break 'outer;
}
}
Ok(())
}
/// Set the global AniMe brightness
+98 -17
View File
@@ -1,14 +1,75 @@
use crate::laptops::LaptopLedData;
use crate::laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES};
use log::{error, warn};
use rog_aura::usb::AuraControl;
use rog_aura::usb::{AuraDev1866, AuraDev19b6, AuraPowerDev};
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Direction, LedBrightness, Speed, GRADIENT};
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashSet};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use super::controller::CtrlKbdLed;
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/aura.conf";
/// Enable/disable LED control in various states such as
/// when the device is awake, suspended, shutting down or
/// booting.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuraPowerConfig {
AuraDev1866(HashSet<AuraDev1866>),
AuraDev19b6(HashSet<AuraDev19b6>),
}
impl AuraPowerConfig {
pub fn to_bytes(control: &Self) -> [u8; 3] {
match control {
AuraPowerConfig::AuraDev1866(c) => {
let c: Vec<AuraDev1866> = c.iter().map(|v| *v).collect();
AuraDev1866::to_bytes(&c)
}
AuraPowerConfig::AuraDev19b6(c) => {
let c: Vec<AuraDev19b6> = c.iter().map(|v| *v).collect();
AuraDev19b6::to_bytes(&c)
}
}
}
pub fn set_0x1866(&mut self, power: AuraDev1866, on: bool) {
if let Self::AuraDev1866(p) = self {
if on {
p.insert(power);
} else {
p.remove(&power);
}
}
}
pub fn set_0x19b6(&mut self, power: AuraDev19b6, on: bool) {
if let Self::AuraDev19b6(p) = self {
if on {
p.insert(power);
} else {
p.remove(&power);
}
}
}
}
impl From<&AuraPowerConfig> for AuraPowerDev {
fn from(config: &AuraPowerConfig) -> Self {
match config {
AuraPowerConfig::AuraDev1866(d) => AuraPowerDev {
x1866: d.iter().map(|o| *o).collect(),
x19b6: vec![],
},
AuraPowerConfig::AuraDev19b6(d) => AuraPowerDev {
x1866: vec![],
x19b6: d.iter().map(|o| *o).collect(),
},
}
}
}
#[derive(Deserialize, Serialize)]
#[serde(default)]
pub struct AuraConfig {
@@ -17,31 +78,51 @@ pub struct AuraConfig {
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<BTreeMap<AuraModeNum, Vec<AuraEffect>>>,
pub multizone_on: bool,
pub enabled: HashSet<AuraControl>,
pub enabled: AuraPowerConfig,
}
impl Default for AuraConfig {
fn default() -> Self {
let mut prod_id = String::new();
for prod in ASUS_KEYBOARD_DEVICES.iter() {
if let Ok(_) = CtrlKbdLed::find_led_node(prod) {
prod_id = prod.to_string();
break;
}
}
let enabled = if prod_id == "19b6" {
AuraPowerConfig::AuraDev19b6(HashSet::from([
AuraDev19b6::BootLogo,
AuraDev19b6::BootKeyb,
AuraDev19b6::SleepLogo,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
AuraDev19b6::AwakeBar,
AuraDev19b6::BootBar,
AuraDev19b6::SleepBar,
AuraDev19b6::ShutdownBar,
]))
} else {
AuraPowerConfig::AuraDev1866(HashSet::from([
AuraDev1866::Awake,
AuraDev1866::Boot,
AuraDev1866::Sleep,
AuraDev1866::Keyboard,
AuraDev1866::Lightbar,
]))
};
AuraConfig {
brightness: LedBrightness::Med,
current_mode: AuraModeNum::Static,
builtins: BTreeMap::new(),
multizone: None,
multizone_on: false,
enabled: HashSet::from([
AuraControl::BootLogo,
AuraControl::BootKeyb,
AuraControl::SleepLogo,
AuraControl::SleepKeyb,
AuraControl::AwakeLogo,
AuraControl::AwakeKeyb,
AuraControl::ShutdownLogo,
AuraControl::ShutdownKeyb,
AuraControl::AwakeBar,
AuraControl::BootBar,
AuraControl::SleepBar,
AuraControl::ShutdownBar,
]),
enabled,
}
}
}
+27 -14
View File
@@ -9,11 +9,11 @@ use crate::{
use async_trait::async_trait;
use log::{error, info, warn};
use logind_zbus::manager::ManagerProxy;
use rog_aura::{usb::AuraControl, AuraZone, Direction, Speed, GRADIENT};
use rog_aura::{
usb::{LED_APPLY, LED_SET},
usb::{AuraDevice, LED_APPLY, LED_SET},
AuraEffect, LedBrightness, LED_MSG_LEN,
};
use rog_aura::{AuraZone, Direction, Speed, GRADIENT};
use rog_supported::LedSupportedFunctions;
use smol::{stream::StreamExt, Executor};
use std::path::Path;
@@ -28,7 +28,7 @@ use zbus::Connection;
use crate::GetSupported;
use super::config::AuraConfig;
use super::config::{AuraConfig, AuraPowerConfig};
impl GetSupported for CtrlKbdLed {
type A = LedSupportedFunctions;
@@ -40,7 +40,16 @@ impl GetSupported for CtrlKbdLed {
let multizone_led_mode = laptop.multizone;
let per_key_led_mode = laptop.per_key;
let mut prod_id = "";
for prod in ASUS_KEYBOARD_DEVICES.iter() {
if let Ok(_) = Self::find_led_node(prod) {
prod_id = *prod;
break;
}
}
LedSupportedFunctions {
prod_id: AuraDevice::from(prod_id),
brightness_set: CtrlKbdLed::get_kbd_bright_path().is_some(),
stock_led_modes,
multizone_led_mode,
@@ -50,6 +59,8 @@ impl GetSupported for CtrlKbdLed {
}
pub struct CtrlKbdLed {
// TODO: config stores the keyboard type as an AuraPower, use or update this
pub led_prod: Option<String>,
pub led_node: Option<String>,
pub bright_node: String,
pub supported_modes: LaptopLedData,
@@ -184,10 +195,12 @@ impl CtrlKbdLedZbus {
impl CtrlKbdLed {
pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> {
// TODO: return error if *all* nodes are None
let mut led_prod = None;
let mut led_node = None;
for prod in ASUS_KEYBOARD_DEVICES.iter() {
match Self::find_led_node(prod) {
Ok(node) => {
led_prod = Some(prod.to_string());
led_node = Some(node);
info!("Looked for keyboard controller 0x{prod}: Found");
break;
@@ -209,6 +222,7 @@ impl CtrlKbdLed {
}
let ctrl = CtrlKbdLed {
led_prod,
led_node,
bright_node: bright_node.unwrap(), // If was none then we already returned above
supported_modes,
@@ -282,12 +296,8 @@ impl CtrlKbdLed {
/// Set combination state for boot animation/sleep animation/all leds/keys leds/side leds LED active
pub(super) fn set_power_states(&self, config: &AuraConfig) -> Result<(), RogError> {
let set: Vec<AuraControl> = config.enabled.iter().map(|v| *v).collect();
let bytes = AuraControl::to_bytes(&set);
let message = [
0x5d, 0xbd, 0x01, bytes[0], bytes[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let bytes = AuraPowerConfig::to_bytes(&config.enabled);
let message = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2]];
self.write_bytes(&message)?;
self.write_bytes(&LED_SET)?;
@@ -296,7 +306,7 @@ impl CtrlKbdLed {
Ok(())
}
fn find_led_node(id_product: &str) -> Result<String, RogError> {
pub(crate) fn find_led_node(id_product: &str) -> Result<String, RogError> {
let mut enumerator = udev::Enumerator::new().map_err(|err| {
warn!("{}", err);
RogError::Udev("enumerator failed".into(), err)
@@ -407,10 +417,10 @@ impl CtrlKbdLed {
let next = self.supported_modes.standard[idx];
self.config.read();
if self.config.builtins.contains_key(&next) {
self.config.current_mode = next;
self.write_current_config_mode()?;
}
// if self.config.builtins.contains_key(&next) {
self.config.current_mode = next;
self.write_current_config_mode()?;
// }
self.config.write();
}
@@ -511,6 +521,7 @@ mod tests {
per_key: false,
};
let mut controller = CtrlKbdLed {
led_prod: None,
led_node: None,
bright_node: String::new(),
supported_modes,
@@ -572,6 +583,7 @@ mod tests {
per_key: false,
};
let mut controller = CtrlKbdLed {
led_prod: None,
led_node: None,
bright_node: String::new(),
supported_modes,
@@ -608,6 +620,7 @@ mod tests {
per_key: false,
};
let mut controller = CtrlKbdLed {
led_prod: None,
led_node: None,
bright_node: String::new(),
supported_modes,
+44 -61
View File
@@ -1,6 +1,8 @@
use std::collections::BTreeMap;
use async_trait::async_trait;
use log::warn;
use rog_aura::{usb::AuraControl, AuraEffect, LedBrightness};
use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, LedBrightness};
use zbus::{dbus_interface, Connection, SignalContext};
use super::controller::CtrlKbdLedZbus;
@@ -8,7 +10,7 @@ use super::controller::CtrlKbdLedZbus;
#[async_trait]
impl crate::ZbusAdd for CtrlKbdLedZbus {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Led", server).await;
Self::add_to_server_helper(self, "/org/asuslinux/Aura", server).await;
}
}
@@ -27,8 +29,21 @@ impl CtrlKbdLedZbus {
}
/// Set a variety of states, input is array of enum.
/// `enabled` sets if the sent array should be disabled or enabled
///
/// enum AuraControl {
/// ```text
/// pub struct AuraPowerDev {
/// pub x1866: Vec<AuraDev1866>,
/// pub x19b6: Vec<AuraDev19b6>,
/// }
/// pub enum AuraDev1866 {
/// Awake,
/// Keyboard,
/// Lightbar,
/// Boot,
/// Sleep,
/// }
/// enum AuraDev19b6 {
/// BootLogo,
/// BootKeyb,
/// AwakeLogo,
@@ -42,16 +57,22 @@ impl CtrlKbdLedZbus {
/// SleepBar,
/// ShutdownBar,
/// }
async fn set_leds_enabled(
/// ```
async fn set_leds_power(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
enabled: Vec<AuraControl>,
options: AuraPowerDev,
enabled: bool,
) -> zbus::fdo::Result<()> {
let mut states = None;
if let Ok(mut ctrl) = self.0.try_lock() {
for s in enabled {
ctrl.config.enabled.insert(s);
for p in options.x1866 {
ctrl.config.enabled.set_0x1866(p, enabled);
}
for p in options.x19b6 {
ctrl.config.enabled.set_0x19b6(p, enabled);
}
ctrl.config.write();
ctrl.set_power_states(&ctrl.config).map_err(|e| {
@@ -59,37 +80,7 @@ impl CtrlKbdLedZbus {
e
})?;
let set: Vec<AuraControl> = ctrl.config.enabled.iter().map(|v| *v).collect();
states = Some(set);
}
// Need to pull state out like this due to MutexGuard
if let Some(states) = states {
Self::notify_power_states(&ctxt, &states)
.await
.unwrap_or_else(|err| warn!("{}", err));
}
Ok(())
}
async fn set_leds_disabled(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
disabled: Vec<AuraControl>,
) -> zbus::fdo::Result<()> {
let mut states = None;
if let Ok(mut ctrl) = self.0.try_lock() {
for s in disabled {
ctrl.config.enabled.remove(&s);
}
ctrl.config.write();
ctrl.set_power_states(&ctrl.config).map_err(|e| {
warn!("{}", e);
e
})?;
let set: Vec<AuraControl> = ctrl.config.enabled.iter().map(|v| *v).collect();
states = Some(set);
states = Some(AuraPowerDev::from(&ctrl.config.enabled));
}
// Need to pull state out like this due to MutexGuard
if let Some(states) = states {
@@ -189,39 +180,31 @@ impl CtrlKbdLedZbus {
Ok(())
}
#[dbus_interface(property)]
async fn leds_enabled(&self) -> Vec<u8> {
if let Ok(ctrl) = self.0.try_lock() {
let set: Vec<AuraControl> = ctrl.config.enabled.iter().map(|v| *v).collect();
return AuraControl::to_bytes(&set).to_vec();
// As property doesn't work for AuraPowerDev (complexity of serialization?)
// #[dbus_interface(property)]
async fn leds_enabled(&self) -> AuraPowerDev {
loop {
if let Ok(ctrl) = self.0.try_lock() {
return AuraPowerDev::from(&ctrl.config.enabled);
}
}
vec![0, 0]
}
/// Return the current mode data
#[dbus_interface(property)]
async fn led_mode(&self) -> String {
async fn led_mode(&self) -> AuraModeNum {
if let Ok(ctrl) = self.0.try_lock() {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
return json;
}
}
return ctrl.config.current_mode;
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not deserialise".to_string()
AuraModeNum::Static
}
/// Return a list of available modes
#[dbus_interface(property)]
async fn led_modes(&self) -> String {
if let Ok(ctrl) = self.0.try_lock() {
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
return json;
async fn led_modes(&self) -> BTreeMap<AuraModeNum, AuraEffect> {
loop {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.builtins.clone();
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not serialise".to_string()
}
/// Return the current LED brightness
@@ -240,6 +223,6 @@ impl CtrlKbdLedZbus {
#[dbus_interface(signal)]
async fn notify_power_states(
signal_ctxt: &SignalContext<'_>,
data: &[AuraControl],
data: &AuraPowerDev,
) -> zbus::Result<()>;
}
+9
View File
@@ -1,3 +1,4 @@
use rog_anime::error::AnimeError;
use rog_profiles::error::ProfileError;
use std::convert::From;
use std::fmt;
@@ -26,6 +27,7 @@ pub enum RogError {
AuraEffectNotSupported,
NoAuraKeyboard,
NoAuraNode,
Anime(AnimeError),
}
impl fmt::Display for RogError {
@@ -54,6 +56,7 @@ impl fmt::Display for RogError {
RogError::AuraEffectNotSupported => write!(f, "Aura effect not supported"),
RogError::NoAuraKeyboard => write!(f, "No supported Aura keyboard"),
RogError::NoAuraNode => write!(f, "No Aura keyboard node found"),
RogError::Anime(deets) => write!(f, "AniMe Matrix error: {}", deets),
}
}
}
@@ -66,6 +69,12 @@ impl From<ProfileError> for RogError {
}
}
impl From<AnimeError> for RogError {
fn from(err: AnimeError) -> Self {
RogError::Anime(err)
}
}
impl From<zbus::Error> for RogError {
fn from(err: zbus::Error) -> Self {
RogError::Zbus(err)
+1 -1
View File
@@ -50,7 +50,7 @@ per_key = false
[[led_data]]
prod_family = "ROG Strix"
board_names = ["G531GW", "G533QR", "G533QS", "G733QS", "G733QR", "G513QR", "G713QR", "G513QM"]
board_names = ["G531GW", "G533QR", "G533QS", "G733QS", "G733QR", "G513QR", "G713QR", "G513QM", "G713IC"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = []
per_key = true
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "rog_anime"
version = "1.3.4"
version = "1.3.5"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -14,7 +14,7 @@ exclude = ["data"]
[features]
default = ["dbus", "detect"]
dbus = ["zvariant"]
dbus = ["zvariant", "zbus"]
detect = ["udev", "sysfs-class"]
[dependencies]
@@ -29,6 +29,7 @@ serde_derive = "^1.0"
glam = { version = "0.20.5", features = ["serde"] }
zvariant = { version = "^3.0", optional = true }
zbus = { version = "^2.2", optional = true }
udev = { version = "^0.6", optional = true }
sysfs-class = { version = "^0.1", optional = true }
+20 -11
View File
@@ -1,4 +1,5 @@
use std::{
convert::TryFrom,
thread::sleep,
time::{Duration, Instant},
};
@@ -8,7 +9,10 @@ use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "dbus")]
use zvariant::Type;
use crate::{error::AnimeError, AnimTime, AnimeGif};
use crate::{
error::{AnimeError, Result},
AnimTime, AnimeGif,
};
/// The first 7 bytes of a USB packet are accounted for by `USB_PREFIX1` and `USB_PREFIX2`
const BLOCK_START: usize = 7;
@@ -102,20 +106,25 @@ impl AnimeDataBuffer {
/// # Panics
/// Will panic if the vector length is not `ANIME_DATA_LEN`
#[inline]
pub fn from_vec(anime: AnimeType, data: Vec<u8>) -> Self {
assert_eq!(data.len(), anime.data_length());
pub fn from_vec(anime: AnimeType, data: Vec<u8>) -> Result<Self> {
if data.len() != anime.data_length() {
return Err(AnimeError::DataBufferLength);
}
Self { data, anime }
Ok(Self { data, anime })
}
}
/// The two packets to be written to USB
pub type AnimePacketType = Vec<[u8; 640]>;
impl From<AnimeDataBuffer> for AnimePacketType {
#[inline]
fn from(anime: AnimeDataBuffer) -> Self {
assert_eq!(anime.data.len(), anime.anime.data_length());
impl TryFrom<AnimeDataBuffer> for AnimePacketType {
type Error = AnimeError;
fn try_from(anime: AnimeDataBuffer) -> std::result::Result<Self, Self::Error> {
if anime.data.len() != anime.anime.data_length() {
return Err(AnimeError::DataBufferLength);
}
let mut buffers = match anime.anime {
AnimeType::GA401 => vec![[0; 640]; 2],
@@ -131,7 +140,7 @@ impl From<AnimeDataBuffer> for AnimePacketType {
if matches!(anime.anime, AnimeType::GA402) {
buffers[2][..7].copy_from_slice(&USB_PREFIX3);
}
buffers
Ok(buffers)
}
}
@@ -140,8 +149,8 @@ impl From<AnimeDataBuffer> for AnimePacketType {
/// If `callback` is `Ok(true)` then `run_animation` will exit the animation loop early.
pub fn run_animation(
frames: &AnimeGif,
callback: &dyn Fn(AnimeDataBuffer) -> Result<bool, AnimeError>,
) -> Result<(), AnimeError> {
callback: &dyn Fn(AnimeDataBuffer) -> Result<bool>,
) -> Result<()> {
let mut count = 0;
let start = Instant::now();
+16 -12
View File
@@ -1,6 +1,10 @@
use std::{path::Path, time::Duration};
use crate::{data::AnimeDataBuffer, error::AnimeError, AnimeType};
use crate::{
data::AnimeDataBuffer,
error::{AnimeError, Result},
AnimeType,
};
/// Mostly intended to be used with ASUS gifs, but can be used for other purposes (like images)
#[derive(Debug, Clone)]
@@ -40,7 +44,7 @@ impl AnimeDiagonal {
duration: Option<Duration>,
bright: f32,
anime_type: AnimeType,
) -> Result<Self, AnimeError> {
) -> Result<Self> {
let data = std::fs::read(path)?;
let data = std::io::Cursor::new(data);
let decoder = png_pong::Decoder::new(data)?.into_steps();
@@ -125,7 +129,7 @@ impl AnimeDiagonal {
/// Convert to a data buffer that can be sent over dbus
#[inline]
pub fn into_data_buffer(&self, anime_type: AnimeType) -> AnimeDataBuffer {
pub fn into_data_buffer(&self, anime_type: AnimeType) -> Result<AnimeDataBuffer> {
match anime_type {
AnimeType::GA401 => self.into_ga401_packets(),
AnimeType::GA402 => self.into_ga402_packets(),
@@ -134,7 +138,7 @@ impl AnimeDiagonal {
/// Do conversion from the nested Vec in AnimeMatrix to the two required
/// packets suitable for sending over USB
fn into_ga401_packets(&self) -> AnimeDataBuffer {
fn into_ga401_packets(&self) -> Result<AnimeDataBuffer> {
let mut buf = vec![0u8; AnimeType::GA401.data_length()];
buf[1..=32].copy_from_slice(&self.get_row(0, 3, 32));
@@ -196,7 +200,7 @@ impl AnimeDiagonal {
AnimeDataBuffer::from_vec(crate::AnimeType::GA401, buf)
}
fn into_ga402_packets(&self) -> AnimeDataBuffer {
fn into_ga402_packets(&self) -> Result<AnimeDataBuffer> {
let mut buf = vec![0u8; AnimeType::GA402.data_length()];
let mut start_index: usize = 0;
@@ -282,7 +286,7 @@ impl AnimeDiagonal {
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::{convert::TryFrom, path::PathBuf};
use crate::{AnimeDiagonal, AnimePacketType, AnimeType};
@@ -374,8 +378,8 @@ mod tests {
path.push("test/ga401-diagonal.png");
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA401).unwrap();
let data = matrix.into_data_buffer(crate::AnimeType::GA401);
let pkt = AnimePacketType::from(data);
let data = matrix.into_data_buffer(crate::AnimeType::GA401).unwrap();
let pkt = AnimePacketType::try_from(data).unwrap();
assert_eq!(pkt[0], pkt0_check);
assert_eq!(pkt[1], pkt1_check);
@@ -509,8 +513,8 @@ mod tests {
path.push("test/ga402-diagonal.png");
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA402).unwrap();
let data = matrix.into_data_buffer(crate::AnimeType::GA402);
let pkt = AnimePacketType::from(data);
let data = matrix.into_data_buffer(crate::AnimeType::GA402).unwrap();
let pkt = AnimePacketType::try_from(data).unwrap();
assert_eq!(pkt[0], pkt0_check);
assert_eq!(pkt[1], pkt1_check);
@@ -654,8 +658,8 @@ mod tests {
path.push("test/ga402-diagonal-fullbright.png");
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA402).unwrap();
let data = matrix.into_data_buffer(crate::AnimeType::GA402);
let pkt = AnimePacketType::from(data);
let data = matrix.into_data_buffer(crate::AnimeType::GA402).unwrap();
let pkt = AnimePacketType::try_from(data).unwrap();
assert_eq!(pkt[0], pkt0_check);
assert_eq!(pkt[1], pkt1_check);
+23
View File
@@ -3,6 +3,8 @@ use png_pong::decode::Error as PngError;
use std::error::Error;
use std::fmt;
pub type Result<T> = std::result::Result<T, AnimeError>;
#[derive(Debug)]
pub enum AnimeError {
NoFrames,
@@ -17,6 +19,9 @@ pub enum AnimeError {
NoDevice,
UnsupportedDevice,
InvalidBrightness(f32),
DataBufferLength,
PixelGifWidth(usize),
PixelGifHeight(usize),
}
impl fmt::Display for AnimeError {
@@ -36,12 +41,23 @@ impl fmt::Display for AnimeError {
AnimeError::Dbus(detail) => write!(f, "{}", detail),
AnimeError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
AnimeError::NoDevice => write!(f, "No AniMe Matrix device found"),
AnimeError::DataBufferLength => write!(
f,
"The data buffer was incorrect length for generating USB packets"
),
AnimeError::UnsupportedDevice => write!(f, "Unsupported AniMe Matrix device found"),
AnimeError::InvalidBrightness(bright) => write!(
f,
"Image brightness must be between 0.0 and 1.0 (inclusive), was {}",
bright
),
AnimeError::PixelGifWidth(n) => {
write!(f, "The gif used for pixel-perfect gif is is wider than {n}")
}
AnimeError::PixelGifHeight(n) => write!(
f,
"The gif used for pixel-perfect gif is is taller than {n}"
),
}
}
}
@@ -68,3 +84,10 @@ impl From<DecodingError> for AnimeError {
AnimeError::Gif(err)
}
}
impl From<AnimeError> for zbus::fdo::Error {
#[inline]
fn from(err: AnimeError) -> Self {
zbus::fdo::Error::Failed(format!("{}", err))
}
}
+22 -11
View File
@@ -1,8 +1,10 @@
use glam::Vec2;
use serde_derive::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::{fs::File, path::Path, time::Duration};
use crate::{error::AnimeError, AnimeDataBuffer, AnimeDiagonal, AnimeImage, AnimeType, Pixel};
use crate::error::AnimeError;
use crate::{error::Result, AnimeDataBuffer, AnimeDiagonal, AnimeImage, AnimeType, Pixel};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AnimeFrame {
@@ -93,7 +95,7 @@ impl AnimeGif {
duration: AnimTime,
brightness: f32,
anime_type: AnimeType,
) -> Result<Self, AnimeError> {
) -> Result<Self> {
let mut matrix = AnimeDiagonal::new(anime_type, None);
let mut decoder = gif::DecodeOptions::new();
@@ -116,13 +118,22 @@ impl AnimeGif {
// should be t but not in some gifs? What, ASUS, what?
continue;
}
matrix.get_mut()[y + frame.top as usize][x + frame.left as usize] =
(px[0] as f32 * brightness) as u8;
let tmp = matrix.get_mut();
let y = y + frame.top as usize;
if y >= tmp.len() {
return Err(AnimeError::PixelGifHeight(tmp.len()));
}
let x = x + frame.left as usize;
if x >= tmp[y].len() {
return Err(AnimeError::PixelGifWidth(tmp[y].len()));
}
matrix.get_mut()[y][x] = (px[0] as f32 * brightness) as u8;
}
}
frames.push(AnimeFrame {
data: matrix.into_data_buffer(anime_type),
data: matrix.into_data_buffer(anime_type)?,
delay: Duration::from_millis(wait as u64),
});
}
@@ -136,7 +147,7 @@ impl AnimeGif {
anime_type: AnimeType,
duration: AnimTime,
brightness: f32,
) -> Result<Self, AnimeError> {
) -> Result<Self> {
let image = AnimeDiagonal::from_png(file_name, None, brightness, anime_type)?;
let mut total = Duration::from_millis(1000);
@@ -150,7 +161,7 @@ impl AnimeGif {
let frame_count = total.as_millis() / 30;
let single = AnimeFrame {
data: image.into_data_buffer(anime_type),
data: image.into_data_buffer(anime_type)?,
delay: Duration::from_millis(30),
};
let frames = vec![single; frame_count as usize];
@@ -169,7 +180,7 @@ impl AnimeGif {
duration: AnimTime,
brightness: f32,
anime_type: AnimeType,
) -> Result<Self, AnimeError> {
) -> Result<Self> {
let mut frames = Vec::new();
let mut decoder = gif::DecodeOptions::new();
@@ -225,7 +236,7 @@ impl AnimeGif {
image.update();
frames.push(AnimeFrame {
data: <AnimeDataBuffer>::from(&image),
data: <AnimeDataBuffer>::try_from(&image)?,
delay: Duration::from_millis(wait as u64),
});
}
@@ -244,7 +255,7 @@ impl AnimeGif {
duration: AnimTime,
brightness: f32,
anime_type: AnimeType,
) -> Result<Self, AnimeError> {
) -> Result<Self> {
let image =
AnimeImage::from_png(file_name, scale, angle, translation, brightness, anime_type)?;
@@ -259,7 +270,7 @@ impl AnimeGif {
let frame_count = total.as_millis() / 30;
let single = AnimeFrame {
data: <AnimeDataBuffer>::from(&image),
data: <AnimeDataBuffer>::try_from(&image)?,
delay: Duration::from_millis(30),
};
let frames = vec![single; frame_count as usize];
+8 -4
View File
@@ -1,4 +1,7 @@
use std::convert::TryFrom;
use crate::data::AnimeDataBuffer;
use crate::error::{AnimeError, Result};
use crate::{AnimeImage, AnimeType};
// TODO: Adjust these sizes as WIDTH_GA401 WIDTH_GA402
@@ -87,11 +90,12 @@ impl AnimeGrid {
// }
}
impl From<AnimeGrid> for AnimeDataBuffer {
impl TryFrom<AnimeGrid> for AnimeDataBuffer {
type Error = AnimeError;
/// Do conversion from the nested Vec in AniMeMatrix to the two required
/// packets suitable for sending over USB
#[inline]
fn from(anime: AnimeGrid) -> Self {
fn try_from(anime: AnimeGrid) -> Result<Self> {
let mut buf = vec![0u8; anime.anime_type.data_length()];
for (idx, pos) in AnimeImage::generate_image_positioning(anime.anime_type)
@@ -123,7 +127,7 @@ mod tests {
}
}
let matrix = <AnimeDataBuffer>::from(matrix);
let matrix = <AnimeDataBuffer>::try_from(matrix).unwrap();
let data_cmp = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+16 -11
View File
@@ -1,9 +1,13 @@
use std::path::Path;
use std::{convert::TryFrom, path::Path};
pub use glam::Vec2;
use glam::{Mat3, Vec3};
use crate::{data::AnimeDataBuffer, error::AnimeError, AnimeType};
use crate::{
data::AnimeDataBuffer,
error::{AnimeError, Result},
AnimeType,
};
/// A single greyscale + alpha pixel in the image
#[derive(Copy, Clone, Debug)]
@@ -83,7 +87,7 @@ impl AnimeImage {
pixels: Vec<Pixel>,
width: u32,
anime_type: AnimeType,
) -> Result<Self, AnimeError> {
) -> Result<Self> {
if bright < 0.0 || bright > 1.0 {
return Err(AnimeError::InvalidBrightness(bright));
}
@@ -388,7 +392,7 @@ impl AnimeImage {
translation: Vec2,
bright: f32,
anime_type: AnimeType,
) -> Result<Self, AnimeError> {
) -> Result<Self> {
let data = std::fs::read(path)?;
let data = std::io::Cursor::new(data);
let decoder = png_pong::Decoder::new(data)?.into_steps();
@@ -484,11 +488,12 @@ impl AnimeImage {
}
}
impl From<&AnimeImage> for AnimeDataBuffer {
impl TryFrom<&AnimeImage> for AnimeDataBuffer {
type Error = AnimeError;
/// Do conversion from the nested Vec in AnimeDataBuffer to the two required
/// packets suitable for sending over USB
#[inline]
fn from(leds: &AnimeImage) -> Self {
fn try_from(leds: &AnimeImage) -> Result<Self> {
let mut l: Vec<u8> = leds
.led_pos
.iter()
@@ -506,7 +511,7 @@ impl From<&AnimeImage> for AnimeDataBuffer {
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::{convert::TryFrom, path::PathBuf};
use crate::{image::*, AnimTime, AnimeGif, AnimePacketType};
@@ -734,8 +739,8 @@ mod tests {
)
.unwrap();
matrix._edge_outline();
let data = AnimeDataBuffer::from(&matrix);
let pkt = AnimePacketType::from(data);
let data = AnimeDataBuffer::try_from(&matrix).unwrap();
let pkt = AnimePacketType::try_from(data).unwrap();
assert_eq!(pkt[0], pkt0_check);
assert_eq!(pkt[1], pkt1_check);
@@ -759,6 +764,6 @@ mod tests {
)
.unwrap();
matrix.frames()[0].frame();
let _pkt = AnimePacketType::from(matrix.frames()[0].frame().clone());
let _pkt = AnimePacketType::try_from(matrix.frames()[0].frame().clone()).unwrap();
}
}
+7 -10
View File
@@ -1,10 +1,10 @@
use std::{path::PathBuf, time::Duration};
use glam::Vec2;
use serde_derive::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::{path::PathBuf, time::Duration};
use crate::{
error::AnimeError, AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType,
error::Result, AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType,
};
/// All the possible AniMe actions that can be used. This enum is intended to be
@@ -65,10 +65,7 @@ pub enum ActionData {
}
impl ActionData {
pub fn from_anime_action(
anime_type: AnimeType,
action: &ActionLoader,
) -> Result<ActionData, AnimeError> {
pub fn from_anime_action(anime_type: AnimeType, action: &ActionLoader) -> Result<ActionData> {
let a = match action {
ActionLoader::AsusAnimation {
file,
@@ -87,7 +84,7 @@ impl ActionData {
} => match time {
AnimTime::Infinite => {
let image = AnimeDiagonal::from_png(file, None, *brightness, anime_type)?;
let data = image.into_data_buffer(anime_type);
let data = image.into_data_buffer(anime_type)?;
ActionData::Image(Box::new(data))
}
_ => ActionData::Animation(AnimeGif::from_diagonal_png(
@@ -147,7 +144,7 @@ impl ActionData {
*brightness,
anime_type,
)?;
let data = <AnimeDataBuffer>::from(&image);
let data = <AnimeDataBuffer>::try_from(&image)?;
ActionData::Image(Box::new(data))
}
_ => ActionData::Animation(AnimeGif::from_png(
@@ -180,7 +177,7 @@ impl Sequences {
/// Use a base `AnimeAction` to generate the precomputed data and insert in to
/// the run buffer
#[inline]
pub fn insert(&mut self, index: usize, action: &ActionLoader) -> Result<(), AnimeError> {
pub fn insert(&mut self, index: usize, action: &ActionLoader) -> Result<()> {
self.0
.insert(index, ActionData::from_anime_action(self.1, action)?);
Ok(())
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rog_aura"
version = "1.1.3"
version = "1.2.0"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
+16
View File
@@ -63,6 +63,22 @@ impl FromStr for Colour {
}
}
impl From<&[f32; 3]> for Colour {
fn from(c: &[f32; 3]) -> Self {
Self(
(255.0 * c[0]) as u8,
(255.0 * c[1]) as u8,
(255.0 * c[2]) as u8,
)
}
}
impl From<Colour> for [f32; 3] {
fn from(c: Colour) -> Self {
[c.0 as f32 / 255.0, c.1 as f32 / 255.0, c.2 as f32 / 255.0]
}
}
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum Speed {
+203 -77
View File
@@ -20,16 +20,111 @@ pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
]
}
/// Enable/disable LED control in various states such as
/// when the device is awake, suspended, shutting down or
/// booting.
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum AuraDevice {
X1854,
X1869,
X1866,
X19B6,
Unknown,
}
impl From<&str> for AuraDevice {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"1866" => AuraDevice::X1866,
"1869" => AuraDevice::X1869,
"1854" => AuraDevice::X1854,
"19b6" => AuraDevice::X19B6,
"0x1866" => AuraDevice::X1866,
"0x1869" => AuraDevice::X1869,
"0x1854" => AuraDevice::X1854,
"0x19b6" => AuraDevice::X19B6,
_ => AuraDevice::Unknown,
}
}
}
/// This struct is intended as a helper to pass args to generic dbus interface
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuraPowerDev {
pub x1866: Vec<AuraDev1866>,
pub x19b6: Vec<AuraDev19b6>,
}
/// # Bits for older 0x1866 keyboard model
///
/// Keybord and Lightbar require Awake, Boot and Sleep apply to both
/// Keybord and Lightbar regardless of if either are enabled (or Awake is enabled)
///
/// | Byte 1 | Byte 2 | Byte 3 | function | hex |
/// |------------|------------|------------|----------|----------|
/// | 0000, 0000 | 0000, 0000 | 0000, 0010 | Awake | 00,00,02 |
/// | 0000, 1000 | 0000, 0000 | 0000, 0000 | Keyboard | 08,00,00 |
/// | 0000, 0100 | 0000, 0101 | 0000, 0000 | Lightbar | 04,05,00 |
/// | 1100, 0011 | 0001, 0010 | 0000, 1001 | Boot/Sht | c3,12,09 |
/// | 0011, 0000 | 0000, 1000 | 0000, 0100 | Sleep | 30,08,04 |
/// | 1111, 1111 | 0001, 1111 | 0000, 1111 | all on | |
///
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum AuraDev1866 {
Awake = 0x000002,
Keyboard = 0x080000,
Lightbar = 0x040500,
Boot = 0xc31209,
Sleep = 0x300804,
}
impl From<AuraDev1866> for u32 {
fn from(a: AuraDev1866) -> Self {
a as u32
}
}
impl AuraDev1866 {
pub fn to_bytes(control: &[Self]) -> [u8; 3] {
let mut a: u32 = 0;
control.iter().for_each(|n| {
a |= *n as u32;
});
[
((a & 0xff0000) >> 16) as u8,
((a & 0xff00) >> 8) as u8,
(a & 0xff) as u8,
]
}
pub const fn dev_id() -> &'static str {
"0x1866"
}
}
impl BitOr<AuraDev1866> for AuraDev1866 {
type Output = u32;
fn bitor(self, rhs: AuraDev1866) -> Self::Output {
return self as u32 | rhs as u32;
}
}
impl BitAnd<AuraDev1866> for AuraDev1866 {
type Output = u32;
fn bitand(self, rhs: AuraDev1866) -> Self::Output {
return self as u32 & rhs as u32;
}
}
/// # Bits for 0x19b6 keyboard model
///
/// ```text
/// byte 4 in the USB packet is for keyboard + logo power states
/// default is on, `ff`
/// Keyboard and logo use the full range of bits (almost)
///
/// | n1 | n2 | hex | action | bit |
/// |------|------|-----|-----------------------|-------|
/// | 0000 | 0000 | 00 | all off | |
@@ -43,6 +138,7 @@ pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
/// | 1000 | 0000 | 80 | keyboard shutdown off | bit 8 |
///
/// byte 5 = lightbar
///
/// | 1 | 2 | hex | action | bit |
/// |------|------|-----|----------------------|-------|
/// | 0000 | 0010 | 02 | lightbar off boot | bit 2 |
@@ -50,21 +146,10 @@ pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
/// | 0000 | 1000 | 08 | lightbar off sleep | bit 4 |
/// | 0001 | 0000 | 10 | lightbar shtdn off | bit 5 |
///
/// # Bits for older 0x1866 keyboard model
///
/// Keybord and Light zone require Awake
/// | byte 1 | byte 2 | byte 3 | | |
/// | 1 | 2 | 3 | 4 | 5 | 6 | function | hex |
/// |------|------|------|------|------|------|----------|----------|
/// | 0000 | 0000 | 0000 | 0000 | 0000 | 0010 | Awake | 00,00,02 |
/// | 1000 | 0000 | 0000 | 0000 | 0000 | 0010 | Keyboard | 80,00,02 |
/// | 0000 | 0100 | 0000 | 0101 | 0000 | 0010 | Lightbar | 04,05,02 |
/// | 1100 | 0011 | 0001 | 0010 | 0000 | 1001 | Boot/Sht | c3,12,09 |
/// | 0011 | 0000 | 0000 | 1000 | 0000 | 0100 | Sleep | 30,08,04 |
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[repr(u16)]
pub enum AuraControl {
pub enum AuraDev19b6 {
BootLogo = 1,
BootKeyb = 1 << 1,
AwakeLogo = 1 << 2,
@@ -79,133 +164,174 @@ pub enum AuraControl {
ShutdownBar = 1 << 7 + 5,
}
impl From<AuraControl> for u16 {
fn from(a: AuraControl) -> Self {
impl From<AuraDev19b6> for u16 {
fn from(a: AuraDev19b6) -> Self {
a as u16
}
}
impl AuraControl {
pub fn to_bytes(control: &[Self]) -> [u8; 2] {
impl AuraDev19b6 {
pub fn to_bytes(control: &[Self]) -> [u8; 3] {
let mut a: u16 = 0;
control.iter().for_each(|n| {
a |= *n as u16;
});
[(a & 0xff) as u8, ((a & 0xff00) >> 8) as u8]
[(a & 0xff) as u8, ((a & 0xff00) >> 8) as u8, 0x00]
}
pub const fn dev_id() -> &'static str {
"0x196b"
}
}
impl BitOr<AuraControl> for AuraControl {
impl BitOr<AuraDev19b6> for AuraDev19b6 {
type Output = u16;
fn bitor(self, rhs: AuraControl) -> Self::Output {
fn bitor(self, rhs: AuraDev19b6) -> Self::Output {
return self as u16 | rhs as u16;
}
}
impl BitAnd<AuraControl> for AuraControl {
impl BitAnd<AuraDev19b6> for AuraDev19b6 {
type Output = u16;
fn bitand(self, rhs: AuraControl) -> Self::Output {
fn bitand(self, rhs: AuraDev19b6) -> Self::Output {
return self as u16 & rhs as u16;
}
}
#[cfg(test)]
mod tests {
use crate::usb::AuraControl;
use crate::usb::AuraDev19b6;
use super::AuraDev1866;
#[test]
fn check_led_control_bytes() {
fn check_0x1866_control_bytes() {
let bytes = [AuraDev1866::Keyboard, AuraDev1866::Awake];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0x08, 0x00, 0x02]);
let bytes = [AuraDev1866::Lightbar, AuraDev1866::Awake];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0x04, 0x05, 0x02]);
let bytes = [AuraDev1866::Sleep];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0x30, 0x08, 0x04]);
let bytes = [AuraDev1866::Boot];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0xc3, 0x12, 0x09]);
let bytes = [
AuraDev1866::Keyboard,
AuraDev1866::Lightbar,
AuraDev1866::Awake,
AuraDev1866::Sleep,
AuraDev1866::Boot,
];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0xff, 0x1f, 0x000f]);
}
#[test]
fn check_0x19b6_control_bytes() {
// All on
let byte1 = [
AuraControl::BootLogo,
AuraControl::BootKeyb,
AuraControl::SleepLogo,
AuraControl::SleepKeyb,
AuraControl::AwakeLogo,
AuraControl::AwakeKeyb,
AuraControl::ShutdownLogo,
AuraControl::ShutdownKeyb,
AuraDev19b6::BootLogo,
AuraDev19b6::BootKeyb,
AuraDev19b6::SleepLogo,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraControl::to_bytes(&byte1);
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xff);
//
let byte1 = [
// AuraControl::BootLogo,
AuraControl::BootKeyb,
AuraControl::SleepLogo,
AuraControl::SleepKeyb,
AuraControl::AwakeLogo,
AuraControl::AwakeKeyb,
AuraControl::ShutdownLogo,
AuraControl::ShutdownKeyb,
AuraDev19b6::BootKeyb,
AuraDev19b6::SleepLogo,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraControl::to_bytes(&byte1);
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xfe);
let byte1 = [
AuraControl::BootLogo,
AuraDev19b6::BootLogo,
// AuraControl::BootKeyb,
AuraControl::SleepLogo,
AuraControl::SleepKeyb,
AuraControl::AwakeLogo,
AuraControl::AwakeKeyb,
AuraControl::ShutdownLogo,
AuraControl::ShutdownKeyb,
AuraDev19b6::SleepLogo,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraControl::to_bytes(&byte1);
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xfd);
let byte1 = [
AuraControl::BootLogo,
AuraControl::BootKeyb,
AuraDev19b6::BootLogo,
AuraDev19b6::BootKeyb,
// AuraControl::SleepLogo,
AuraControl::SleepKeyb,
AuraControl::AwakeLogo,
AuraControl::AwakeKeyb,
AuraControl::ShutdownLogo,
AuraControl::ShutdownKeyb,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraControl::to_bytes(&byte1);
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xef);
let byte1 = [
AuraControl::BootLogo,
AuraControl::BootKeyb,
AuraControl::SleepLogo,
AuraDev19b6::BootLogo,
AuraDev19b6::BootKeyb,
AuraDev19b6::SleepLogo,
// AuraControl::SleepKeyb,
AuraControl::AwakeLogo,
AuraControl::AwakeKeyb,
AuraControl::ShutdownLogo,
AuraControl::ShutdownKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraControl::to_bytes(&byte1);
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xdf);
let byte2 = [
AuraControl::AwakeBar,
AuraControl::BootBar,
AuraControl::SleepBar,
AuraControl::ShutdownBar,
AuraDev19b6::AwakeBar,
AuraDev19b6::BootBar,
AuraDev19b6::SleepBar,
AuraDev19b6::ShutdownBar,
];
let bytes = AuraControl::to_bytes(&byte2);
let bytes = AuraDev19b6::to_bytes(&byte2);
println!("{:08b}", bytes[1]);
assert_eq!(bytes[1], 0x1e);
let byte2 = [
AuraControl::AwakeBar,
AuraControl::BootBar,
AuraDev19b6::AwakeBar,
AuraDev19b6::BootBar,
// AuraControl::SleepBar,
AuraControl::ShutdownBar,
AuraDev19b6::ShutdownBar,
];
let bytes = AuraControl::to_bytes(&byte2);
let bytes = AuraDev19b6::to_bytes(&byte2);
println!("{:08b}", bytes[1]);
assert_eq!(bytes[1], 0x16);
}
+45
View File
@@ -0,0 +1,45 @@
[package]
name = "rog-control-center"
version = "0.1.4"
authors = ["Luke D. Jones <luke@ljones.dev>"]
edition = "2021"
[features]
mocking = []
[dependencies]
egui = { git = "https://github.com/emilk/egui" }
eframe= { git = "https://github.com/emilk/egui" }
#eframe= { git = "https://github.com/emilk/egui", default-features = false, features = ["dark-light", "default_fonts", "wgpu"] }
rog_dbus = { path = "../rog-dbus" }
rog_aura = { path = "../rog-aura" }
rog_profiles = { path = "../rog-profiles" }
rog_supported = { path = "../rog-supported" }
# supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git" }
smol = "^1.2"
serde = "^1.0"
toml = "^0.5"
serde_json = "^1.0"
serde_derive = "^1.0"
zbus = "^2.3"
nix = "^0.20.0"
tempfile = "3.2.0"
dirs = "3.0.1"
[dependencies.notify-rust]
version = "^4.3"
default-features = false
features = ["z"]
[profile.release]
lto = true
debug = false
opt-level = 3
panic = "abort"
[profile.bench]
debug = false
opt-level = 3
+22
View File
@@ -0,0 +1,22 @@
# App template
This is a trial app cut down to bare essentials to show how to create an app that can run in the background
with some user-config options.
egui based. Keep in mind that this is very much a bit of a mess due to experimenting.
## Running
Use `WINIT_UNIX_BACKEND=x11 rog-control-center`. `WINIT_UNIX_BACKEND` is required due to window decorations not updating and the window not really being set as visible/invisible on wayland.
## Build features
For testing some features that are typically not available on all laptops:
```rust
cargo run --features mocking
```
## TODO
- Add notification watch for certain UI elements to enforce an update (for example when a user changes Aura via a hot key).
@@ -0,0 +1,11 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=ROG Control Center
Comment=Make your ASUS ROG Laptop go Brrrrr!
Categories=Settings
Icon=rog-control-center
Exec=env WINIT_UNIX_BACKEND=x11 rog-control-center
Terminal=false
Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

+127
View File
@@ -0,0 +1,127 @@
use std::{
io::Write,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use egui::{Button, RichText};
use rog_dbus::RogDbusClientBlocking;
use rog_supported::SupportedFunctions;
use crate::{config::Config, get_ipc_file, page_states::PageDataStates, Page, SHOWING_GUI};
pub struct RogApp<'a> {
pub page: Page,
pub states: PageDataStates,
pub supported: SupportedFunctions,
/// Should the app begin showing the GUI
pub begin_show_gui: Arc<AtomicBool>,
/// Is the app GUI closed (and running in bg)
pub running_in_bg: bool,
// TODO: can probably just open and read whenever
pub config: Config,
pub asus_dbus: RogDbusClientBlocking<'a>,
}
impl<'a> RogApp<'a> {
/// Called once before the first frame.
pub fn new(
start_closed: bool,
config: Config,
show_gui: Arc<AtomicBool>,
states: PageDataStates,
_cc: &eframe::CreationContext<'_>,
) -> Self {
let (dbus, _) = RogDbusClientBlocking::new().unwrap();
let supported = dbus.proxies().supported().supported_functions().unwrap();
Self {
supported,
states,
page: Page::System,
begin_show_gui: show_gui,
running_in_bg: start_closed,
config,
asus_dbus: dbus,
}
}
}
impl<'a> eframe::App for RogApp<'a> {
fn on_exit_event(&mut self) -> bool {
if self.config.run_in_background {
self.running_in_bg = true;
get_ipc_file().unwrap().write_all(&[0]).unwrap();
return false;
}
true
}
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
let Self {
begin_show_gui: should_show_gui,
supported,
asus_dbus: dbus,
states,
..
} = self;
if states.refresh_if_notfied(supported, dbus) {
ctx.request_repaint();
}
let page = self.page;
if should_show_gui.load(Ordering::SeqCst) {
let mut ipc_file = get_ipc_file().unwrap();
ipc_file.write_all(&[SHOWING_GUI]).unwrap();
should_show_gui.store(false, Ordering::SeqCst);
frame.set_visible(true);
self.running_in_bg = false;
}
if self.running_in_bg {
// Request to draw nothing at all
ctx.request_repaint_after(Duration::from_millis(500));
frame.set_visible(false);
return;
}
// Do all GUI display after this point
self.top_bar(ctx, frame);
self.side_panel(ctx);
if let Some(err) = self.states.error.clone() {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading(RichText::new("Error!").size(28.0));
ui.centered_and_justified(|ui| {
ui.label(RichText::new(format!("The error was: {:?}", err)).size(22.0));
});
});
egui::TopBottomPanel::bottom("error_bar")
.default_height(26.0)
.show(ctx, |ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
if ui
.add(Button::new(RichText::new("Okay").size(20.0)))
.clicked()
{
self.states.error = None;
}
});
});
} else if page == Page::System {
self.system_page(ctx);
} else if page == Page::AuraEffects {
self.aura_page(ctx);
} else if page == Page::AnimeMatrix {
self.anime_page(ctx);
} else if page == Page::FanCurves {
self.fan_curve_page(ctx);
}
}
}
+92
View File
@@ -0,0 +1,92 @@
use std::{
fs::{create_dir, OpenOptions},
io::{Read, Write},
};
use serde_derive::{Deserialize, Serialize};
//use log::{error, info, warn};
use crate::error::Error;
const CFG_DIR: &str = "rog";
const CFG_FILE_NAME: &str = "app-template.cfg";
#[derive(Debug, Deserialize, Serialize)]
#[serde(default)]
pub struct Config {
pub run_in_background: bool,
pub startup_in_background: bool,
pub enable_notifications: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
run_in_background: true,
startup_in_background: false,
enable_notifications: true,
}
}
}
impl Config {
pub fn load() -> Result<Config, Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push(CFG_DIR);
if !path.exists() {
create_dir(path.clone())?;
}
path.push(CFG_FILE_NAME);
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)?;
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
let default = Config::default();
let t = toml::to_string_pretty(&default).unwrap();
file.write_all(t.as_bytes())?;
return Ok(default);
} else if let Ok(data) = toml::from_str::<Config>(&buf) {
return Ok(data);
}
}
Err(Error::ConfigLoadFail)
}
pub fn save(&self) -> Result<(), Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push(CFG_DIR);
if !path.exists() {
create_dir(path.clone())?;
}
path.push(CFG_FILE_NAME);
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let t = toml::to_string_pretty(&self).unwrap();
file.write_all(t.as_bytes())?;
Ok(())
}
}
+43
View File
@@ -0,0 +1,43 @@
use std::fmt;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Nix(nix::Error),
ConfigLoadFail,
ConfigLockFail,
XdgVars,
}
impl fmt::Display for Error {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Io(err) => write!(f, "Failed to open: {}", err),
Error::Nix(err) => write!(f, "Error: {}", err),
Error::ConfigLoadFail => write!(f, "Failed to load user config"),
Error::ConfigLockFail => write!(f, "Failed to lock user config"),
Error::XdgVars => write!(f, "XDG environment vars appear unset"),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
}
}
impl From<Error> for zbus::fdo::Error {
fn from(err: Error) -> Self {
zbus::fdo::Error::Failed(format!("Anime zbus error: {}", err))
}
}
impl From<nix::Error> for Error {
fn from(err: nix::Error) -> Self {
Error::Nix(err)
}
}
+77
View File
@@ -0,0 +1,77 @@
pub mod app;
use std::{
fs::{remove_dir_all, File, OpenOptions},
io::{Read, Write},
process::exit,
thread::sleep,
time::Duration,
};
pub use app::RogApp;
pub mod config;
pub mod error;
pub mod notify;
pub mod page_states;
pub mod widgets;
use nix::{sys::stat, unistd};
use tempfile::TempDir;
//use log::{error, info, warn};
pub const SHOWING_GUI: u8 = 1;
pub const SHOW_GUI: u8 = 2;
#[derive(PartialEq, Clone, Copy)]
pub enum Page {
System,
AuraEffects,
AnimeMatrix,
FanCurves,
}
/// Either exit the process, or return with a refreshed tmp-dir
pub fn on_tmp_dir_exists() -> Result<TempDir, std::io::Error> {
let mut buf = [0u8; 4];
let path = std::env::temp_dir().join("rog-gui");
let mut ipc_file = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(path.join("ipc.pipe"))?;
// If the app is running this ends up stacked on top of SHOWING_GUI
ipc_file.write_all(&[SHOW_GUI])?;
// tiny sleep to give the app a chance to respond
sleep(Duration::from_millis(10));
ipc_file.read(&mut buf).ok();
// First entry is the actual state
if buf[0] == SHOWING_GUI {
ipc_file.write_all(&[SHOWING_GUI])?; // Store state again as we drained the fifo
exit(0);
} else if buf[0] == SHOW_GUI {
remove_dir_all(&path)?;
return tempfile::Builder::new()
.prefix("rog-gui")
.rand_bytes(0)
.tempdir();
}
exit(-1);
}
pub fn get_ipc_file() -> Result<File, crate::error::Error> {
let tmp_dir = std::env::temp_dir().join("rog-gui");
let fifo_path = tmp_dir.join("ipc.pipe");
if let Err(e) = unistd::mkfifo(&fifo_path, stat::Mode::S_IRWXU) {
if !matches!(e, nix::Error::Sys(nix::errno::Errno::EEXIST)) {
return Err(e)?;
}
}
Ok(OpenOptions::new()
.read(true)
.write(true)
.truncate(true)
.open(&fifo_path)?)
}
+97
View File
@@ -0,0 +1,97 @@
use rog_control_center::{
config::Config, get_ipc_file, notify::start_notifications, on_tmp_dir_exists,
page_states::PageDataStates, RogApp, SHOW_GUI,
};
use rog_dbus::RogDbusClientBlocking;
use std::{
io::Read,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::spawn,
time::Duration,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Startup
let mut config = Config::load()?;
let start_closed = config.startup_in_background;
if config.startup_in_background {
config.run_in_background = true;
config.save()?;
}
let (dbus, _) = RogDbusClientBlocking::new().unwrap();
let supported = dbus.proxies().supported().supported_functions().unwrap();
// Cheap method to alert to notifications rather than spinning a thread for each
// This is quite different when done in a retained mode app
let charge_notified = Arc::new(AtomicBool::new(false));
let bios_notified = Arc::new(AtomicBool::new(false));
let aura_notified = Arc::new(AtomicBool::new(false));
let anime_notified = Arc::new(AtomicBool::new(false));
let profiles_notified = Arc::new(AtomicBool::new(false));
let fans_notified = Arc::new(AtomicBool::new(false));
let notifs_enabled = Arc::new(AtomicBool::new(config.enable_notifications));
// TODO: change this to an error instead of the nested unwraps, then use to
// display a bare box app with error message.
let states = PageDataStates::new(
notifs_enabled.clone(),
charge_notified.clone(),
bios_notified.clone(),
aura_notified.clone(),
anime_notified.clone(),
profiles_notified.clone(),
fans_notified.clone(),
&supported,
&dbus,
);
if config.enable_notifications {
start_notifications(
charge_notified,
bios_notified,
aura_notified,
anime_notified,
profiles_notified,
fans_notified,
notifs_enabled,
)?;
}
// tmp-dir must live to the end of program life
let _tmp_dir = match tempfile::Builder::new()
.prefix("rog-gui")
.rand_bytes(0)
.tempdir()
{
Ok(tmp) => tmp,
Err(_) => on_tmp_dir_exists().unwrap(),
};
let native_options = eframe::NativeOptions {
decorated: true,
..Default::default()
};
let should_show_gui = Arc::new(AtomicBool::new(!start_closed));
let should = should_show_gui.clone();
spawn(move || {
// Loop is blocked here until a single byte is read
loop {
let mut buf = [0u8; 4];
if get_ipc_file().unwrap().read(&mut buf).is_ok() && buf[0] == SHOW_GUI {
should_show_gui.store(true, Ordering::SeqCst);
// Give the starting app a change to read or we'll race it
std::thread::sleep(Duration::from_millis(10));
}
}
});
eframe::run_native(
"ROG Control Center",
native_options,
Box::new(move |cc| Box::new(RogApp::new(start_closed, config, should, states, cc))),
);
}
+256
View File
@@ -0,0 +1,256 @@
//TODO: a lot of app state refresh depends on this so there needs
// to be an extra AtomicBool for checking if notifications are enabled
use notify_rust::{Hint, Notification, NotificationHandle};
use rog_aura::AuraEffect;
use rog_dbus::{
zbus_anime::AnimeProxy, zbus_charge::ChargeProxy, zbus_led::LedProxy,
zbus_profile::ProfileProxy, zbus_rogbios::RogBiosProxy,
};
use rog_profiles::Profile;
use smol::{future, Executor};
use std::{
error::Error,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
thread::spawn,
};
use zbus::export::futures_util::StreamExt;
const NOTIF_HEADER: &str = "ROG Control";
macro_rules! notify {
($notifier:ident, $last_notif:ident, $data:expr) => {
if let Some(notif) = $last_notif.take() {
notif.close();
}
if let Ok(x) = $notifier($data) {
$last_notif.replace(x);
}
};
}
macro_rules! base_notification {
($body:expr) => {
Notification::new()
.summary(NOTIF_HEADER)
.body($body)
.timeout(2000)
.show()
};
}
type SharedHandle = Arc<Mutex<Option<NotificationHandle>>>;
pub fn start_notifications(
charge_notified: Arc<AtomicBool>,
bios_notified: Arc<AtomicBool>,
aura_notified: Arc<AtomicBool>,
anime_notified: Arc<AtomicBool>,
profiles_notified: Arc<AtomicBool>,
_fans_notified: Arc<AtomicBool>,
notifs_enabled: Arc<AtomicBool>,
) -> Result<(), Box<dyn std::error::Error>> {
let last_notification: SharedHandle = Arc::new(Mutex::new(None));
let executor = Executor::new();
// BIOS notif
let last_notif = last_notification.clone();
let notifs_enabled1 = notifs_enabled.clone();
let bios_notified1 = bios_notified.clone();
// TODO: make a macro or generic function or something...
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = RogBiosProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_post_boot_sound().await {
p.for_each(|e| {
if let Ok(out) = e.args() {
if notifs_enabled1.load(Ordering::SeqCst) {
if let Ok(ref mut lock) = last_notif.try_lock() {
notify!(do_post_sound_notif, lock, &out.sound());
}
}
bios_notified1.store(true, Ordering::SeqCst);
}
future::ready(())
})
.await;
};
})
.detach();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = RogBiosProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_panel_overdrive().await {
p.for_each(|_| {
bios_notified.store(true, Ordering::SeqCst);
future::ready(())
})
.await;
};
})
.detach();
// Charge notif
let last_notif = last_notification.clone();
let notifs_enabled1 = notifs_enabled.clone();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = ChargeProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_charge().await {
p.for_each(|e| {
if let Ok(out) = e.args() {
if notifs_enabled1.load(Ordering::SeqCst) {
if let Ok(ref mut lock) = last_notif.try_lock() {
notify!(do_charge_notif, lock, &out.limit);
}
}
charge_notified.store(true, Ordering::SeqCst);
}
future::ready(())
})
.await;
};
})
.detach();
// Profile notif
let last_notif = last_notification.clone();
let notifs_enabled1 = notifs_enabled.clone();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = ProfileProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_profile().await {
p.for_each(|e| {
if let Ok(out) = e.args() {
if notifs_enabled1.load(Ordering::SeqCst) {
if let Ok(ref mut lock) = last_notif.try_lock() {
notify!(do_thermal_notif, lock, &out.profile);
}
}
profiles_notified.store(true, Ordering::SeqCst);
}
future::ready(())
})
.await;
};
})
.detach();
// LED notif
let last_notif = last_notification.clone();
let aura_notif = aura_notified.clone();
let notifs_enabled1 = notifs_enabled.clone();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = LedProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_led().await {
p.for_each(|e| {
if let Ok(out) = e.args() {
if notifs_enabled1.load(Ordering::SeqCst) {
if let Ok(ref mut lock) = last_notif.try_lock() {
notify!(do_led_notif, lock, &out.data);
}
}
aura_notif.store(true, Ordering::SeqCst);
}
future::ready(())
})
.await;
};
})
.detach();
let aura_notif = aura_notified.clone();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = LedProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_led().await {
p.for_each(|_| {
aura_notif.store(true, Ordering::SeqCst);
future::ready(())
})
.await;
};
})
.detach();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = LedProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_all_signals().await {
p.for_each(|_| {
aura_notified.store(true, Ordering::SeqCst);
future::ready(())
})
.await;
};
})
.detach();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = AnimeProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_power_states().await {
p.for_each(|_| {
anime_notified.store(true, Ordering::SeqCst);
future::ready(())
})
.await;
};
})
.detach();
spawn(move || loop {
smol::block_on(executor.tick());
});
Ok(())
}
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
let icon = match profile {
Profile::Balanced => "asus_notif_yellow",
Profile::Performance => "asus_notif_red",
Profile::Quiet => "asus_notif_green",
};
let profile: &str = (*profile).into();
let x = Notification::new()
.summary("ASUS ROG")
.body(&format!(
"Thermal profile changed to {}",
profile.to_uppercase(),
))
.hint(Hint::Resident(true))
.timeout(2000)
.hint(Hint::Category("device".into()))
//.hint(Hint::Transient(true))
.icon(icon)
.show()?;
Ok(x)
}
fn do_led_notif(ledmode: &AuraEffect) -> Result<NotificationHandle, notify_rust::error::Error> {
base_notification!(&format!(
"Keyboard LED mode changed to {}",
ledmode.mode_name()
))
}
fn do_charge_notif(limit: &u8) -> Result<NotificationHandle, notify_rust::error::Error> {
base_notification!(&format!("Battery charge limit changed to {}", limit))
}
fn do_post_sound_notif(on: &bool) -> Result<NotificationHandle, notify_rust::error::Error> {
base_notification!(&format!("BIOS Post sound {}", on))
}
+304
View File
@@ -0,0 +1,304 @@
use std::{
collections::{BTreeMap, HashMap, HashSet},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use egui::Vec2;
use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum};
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::{fan_curve_set::FanCurveSet, FanCurvePU, Profile};
use rog_supported::SupportedFunctions;
#[derive(Clone, Debug)]
pub struct BiosState {
/// To be shared to a thread that checks notifications.
/// It's a bit general in that it won't provide *what* was
/// updated, so the full state needs refresh
pub was_notified: Arc<AtomicBool>,
pub post_sound: bool,
pub dedicated_gfx: bool,
pub panel_overdrive: bool,
pub dgpu_disable: bool,
pub egpu_enable: bool,
}
impl BiosState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
was_notified,
post_sound: if supported.rog_bios_ctrl.post_sound {
dbus.proxies().rog_bios().post_boot_sound().unwrap() != 0
} else {
false
},
dedicated_gfx: if supported.rog_bios_ctrl.dedicated_gfx {
dbus.proxies().rog_bios().dedicated_graphic_mode().unwrap() != 0
} else {
false
},
panel_overdrive: if supported.rog_bios_ctrl.panel_overdrive {
dbus.proxies().rog_bios().panel_overdrive().unwrap() != 0
} else {
false
},
// TODO: needs supergfx
dgpu_disable: supported.rog_bios_ctrl.dgpu_disable,
egpu_enable: supported.rog_bios_ctrl.egpu_enable,
}
}
}
#[derive(Clone, Debug)]
pub struct ProfilesState {
pub was_notified: Arc<AtomicBool>,
pub list: Vec<Profile>,
pub current: Profile,
}
impl ProfilesState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
was_notified,
list: if supported.platform_profile.platform_profile {
dbus.proxies().profile().profiles().unwrap()
} else {
vec![]
},
current: if supported.platform_profile.platform_profile {
dbus.proxies().profile().active_profile().unwrap()
} else {
Profile::Balanced
},
}
}
}
#[derive(Clone, Debug)]
pub struct FanCurvesState {
pub was_notified: Arc<AtomicBool>,
pub show_curve: Profile,
pub show_graph: FanCurvePU,
pub enabled: HashSet<Profile>,
pub curves: HashMap<Profile, FanCurveSet>,
pub drag_delta: Vec2,
}
impl FanCurvesState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
let profiles = if supported.platform_profile.platform_profile {
dbus.proxies().profile().profiles().unwrap()
} else {
vec![Profile::Balanced, Profile::Quiet, Profile::Performance]
};
let enabled = if supported.platform_profile.fan_curves {
HashSet::from_iter(
dbus.proxies()
.profile()
.enabled_fan_profiles()
.unwrap()
.iter()
.cloned(),
)
} else {
HashSet::from([Profile::Balanced, Profile::Quiet, Profile::Performance])
};
let mut curves: HashMap<Profile, FanCurveSet> = HashMap::new();
profiles.iter().for_each(|p| {
if supported.platform_profile.fan_curves {
let curve = dbus.proxies().profile().fan_curve_data(*p).unwrap();
curves.insert(*p, curve);
} else {
let mut curve = FanCurveSet::default();
curve.cpu.pwm = [30, 40, 60, 100, 140, 180, 200, 250];
curve.cpu.temp = [20, 30, 40, 50, 70, 80, 90, 100];
curve.gpu.pwm = [40, 80, 100, 140, 170, 200, 230, 250];
curve.gpu.temp = [20, 30, 40, 50, 70, 80, 90, 100];
curves.insert(*p, curve);
}
});
let show_curve = if supported.platform_profile.fan_curves {
dbus.proxies().profile().active_profile().unwrap()
} else {
Profile::Balanced
};
Self {
was_notified,
show_curve,
show_graph: FanCurvePU::CPU,
enabled,
curves,
drag_delta: Vec2::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct AuraState {
pub was_notified: Arc<AtomicBool>,
pub current_mode: AuraModeNum,
pub modes: BTreeMap<AuraModeNum, AuraEffect>,
pub enabled: AuraPowerDev,
/// Brightness from 0-3
pub bright: i16,
}
impl AuraState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
was_notified,
current_mode: if !supported.keyboard_led.stock_led_modes.is_empty() {
dbus.proxies().led().led_mode().unwrap()
} else {
AuraModeNum::Static
},
modes: if !supported.keyboard_led.stock_led_modes.is_empty() {
dbus.proxies().led().led_modes().unwrap()
} else {
BTreeMap::new()
},
enabled: dbus.proxies().led().leds_enabled().unwrap(),
bright: if !supported.keyboard_led.brightness_set {
dbus.proxies().led().led_brightness().unwrap()
} else {
2
},
}
}
}
#[derive(Clone, Debug)]
pub struct AnimeState {
pub was_notified: Arc<AtomicBool>,
pub bright: u8,
pub boot: bool,
pub awake: bool,
pub sleep: bool,
}
impl AnimeState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
was_notified,
boot: if supported.anime_ctrl.0 {
dbus.proxies().anime().boot_enabled().unwrap()
} else {
false
},
awake: if supported.anime_ctrl.0 {
dbus.proxies().anime().awake_enabled().unwrap()
} else {
false
},
// TODO:
sleep: false,
bright: 200,
}
}
}
#[derive(Debug)]
pub struct PageDataStates {
pub notifs_enabled: Arc<AtomicBool>,
pub was_notified: Arc<AtomicBool>,
/// Because much of the app state here is the same as `RogBiosSupportedFunctions`
/// we can re-use that structure.
pub bios: BiosState,
pub aura: AuraState,
pub anime: AnimeState,
pub profiles: ProfilesState,
pub fan_curves: FanCurvesState,
pub charge_limit: i16,
pub error: Option<String>,
}
impl PageDataStates {
pub fn new(
notifs_enabled: Arc<AtomicBool>,
charge_notified: Arc<AtomicBool>,
bios_notified: Arc<AtomicBool>,
aura_notified: Arc<AtomicBool>,
anime_notified: Arc<AtomicBool>,
profiles_notified: Arc<AtomicBool>,
fans_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
notifs_enabled,
was_notified: charge_notified,
charge_limit: dbus.proxies().charge().limit().unwrap(),
bios: BiosState::new(bios_notified, supported, dbus),
aura: AuraState::new(aura_notified, supported, dbus),
anime: AnimeState::new(anime_notified, supported, dbus),
profiles: ProfilesState::new(profiles_notified, supported, dbus),
fan_curves: FanCurvesState::new(fans_notified, supported, dbus),
error: None,
}
}
pub fn refresh_if_notfied(
&mut self,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> bool {
let mut notified = false;
if self.was_notified.load(Ordering::SeqCst) {
self.charge_limit = dbus.proxies().charge().limit().unwrap();
self.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
if self.aura.was_notified.load(Ordering::SeqCst) {
self.aura = AuraState::new(self.aura.was_notified.clone(), supported, dbus);
self.aura.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
if self.bios.was_notified.load(Ordering::SeqCst) {
self.bios = BiosState::new(self.bios.was_notified.clone(), supported, dbus);
self.bios.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
if self.profiles.was_notified.load(Ordering::SeqCst) {
self.profiles = ProfilesState::new(self.profiles.was_notified.clone(), supported, dbus);
self.profiles.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
if self.fan_curves.was_notified.load(Ordering::SeqCst) {
self.fan_curves =
FanCurvesState::new(self.fan_curves.was_notified.clone(), supported, dbus);
self.fan_curves.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
notified
}
}
@@ -0,0 +1,77 @@
use egui::RichText;
use crate::RogApp;
impl<'a> RogApp<'a> {
pub fn anime_page(&mut self, ctx: &egui::Context) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("AniMe Matrix Settings");
ui.label("Options are incomplete. Awake + Boot should work");
let Self {
states,
asus_dbus: dbus,
..
} = self;
let mut changed = false;
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
let h = 16.0;
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Brightness").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Boot").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Awake").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Sleep").size(h));
});
});
ui.vertical(|ui| {
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
if ui
.add(egui::Slider::new(&mut states.anime.bright, 0..=254))
.changed()
{
changed = true;
}
});
ui.horizontal_wrapped(|ui| {
if ui.checkbox(&mut states.anime.boot, "Enable").changed() {
dbus.proxies()
.anime()
.set_boot_on_off(states.anime.boot)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
});
ui.horizontal_wrapped(|ui| {
if ui.checkbox(&mut states.anime.awake, "Enable").changed() {
dbus.proxies()
.anime()
.set_on_off(states.anime.awake)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
});
ui.horizontal_wrapped(|ui| {
if ui.checkbox(&mut states.anime.sleep, "Enable").changed() {
changed = true;
}
});
});
});
});
}
}
+480
View File
@@ -0,0 +1,480 @@
use egui::{RichText, Ui};
use rog_aura::{
usb::{AuraDev1866, AuraDev19b6, AuraDevice, AuraPowerDev},
AuraModeNum, AuraZone, Colour, Speed,
};
use rog_dbus::RogDbusClientBlocking;
use rog_supported::SupportedFunctions;
use crate::{
page_states::{AuraState, PageDataStates},
RogApp,
};
impl<'a> RogApp<'a> {
pub fn aura_page(&mut self, ctx: &egui::Context) {
let Self {
supported,
states,
asus_dbus: dbus,
..
} = self;
egui::CentralPanel::default().show(ctx, |ui| {
Self::aura_power(supported, states, dbus, ui);
ui.separator();
Self::aura_modes(supported, states, dbus, ui);
});
}
fn aura_power(
supported: &SupportedFunctions,
states: &mut PageDataStates,
dbus: &mut RogDbusClientBlocking,
ui: &mut Ui,
) {
match supported.keyboard_led.prod_id {
AuraDevice::X1854 | AuraDevice::X1869 | AuraDevice::X1866 => {
Self::aura_power1(supported, states, dbus, ui)
}
AuraDevice::X19B6 => Self::aura_power2(supported, states, dbus, ui),
AuraDevice::Unknown => {}
}
}
fn aura_power1(
supported: &SupportedFunctions,
states: &mut PageDataStates,
dbus: &mut RogDbusClientBlocking,
ui: &mut Ui,
) {
let enabled_states = &mut states.aura.enabled;
ui.heading("Aura go brrrrr! (incomplete)");
ui.separator();
let boot = &mut enabled_states.x1866.contains(&AuraDev1866::Boot);
let sleep = &mut enabled_states.x1866.contains(&AuraDev1866::Sleep);
let keyboard = &mut enabled_states.x1866.contains(&AuraDev1866::Keyboard);
let lightbar = &mut enabled_states.x1866.contains(&AuraDev1866::Lightbar);
let mut changed = false;
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
let h = 16.0;
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Boot").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Awake").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Sleep").size(h));
});
// if supported.keyboard_led.brightness_set {
// ui.horizontal_wrapped(|ui| {
// ui.label(RichText::new("Brightness").size(h));
// });
// }
});
ui.vertical(|ui| {
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
if ui.checkbox(boot, "Enable").changed() {
changed = true;
}
});
ui.horizontal_wrapped(|ui| {
if ui.toggle_value(keyboard, "Keyboard").changed() {
changed = true;
}
if !supported.keyboard_led.multizone_led_mode.is_empty() {
if ui.toggle_value(lightbar, "Lightbar").changed() {
changed = true;
}
}
});
ui.horizontal_wrapped(|ui| {
if ui.checkbox(sleep, "Enable").changed() {
changed = true;
}
});
// We currently don't have a watch for system changes here
// if supported.keyboard_led.brightness_set {
// if ui
// .add(egui::Slider::new(
// &mut states.aura.bright,
// 0..=3,
// ))
// .changed()
// {
// let bright = LedBrightness::from(states.aura.bright as u32);
// dbus.proxies()
// .led()
// .set_brightness(bright)
// .map_err(|err| {
// states.error = Some(err.to_string());
// })
// .ok();
// }
// }
});
});
if changed {
let mut enabled = Vec::new();
let mut disabled = Vec::new();
let mut modify = |b: bool, a: AuraDev1866| {
if b {
enabled.push(a);
if !enabled_states.x1866.contains(&a) {
enabled_states.x1866.push(a);
}
} else {
disabled.push(a);
// This would be so much better as a hashset
if enabled_states.x1866.contains(&a) {
let mut idx = 0;
for (i, n) in enabled_states.x1866.iter().enumerate() {
if *n == a {
idx = i;
break;
}
}
enabled_states.x1866.remove(idx);
}
}
};
modify(*boot, AuraDev1866::Boot);
modify(*sleep, AuraDev1866::Sleep);
modify(*keyboard, AuraDev1866::Keyboard);
if !supported.keyboard_led.multizone_led_mode.is_empty() {
modify(*lightbar, AuraDev1866::Lightbar);
}
let mut send = |enable: bool, data: Vec<AuraDev1866>| {
let options = AuraPowerDev {
x1866: data,
x19b6: vec![],
};
// build data to send
dbus.proxies()
.led()
.set_leds_power(options, enable)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
};
send(true, enabled);
send(false, disabled);
}
}
fn aura_power2(
supported: &SupportedFunctions,
states: &mut PageDataStates,
dbus: &mut RogDbusClientBlocking,
ui: &mut Ui,
) {
let enabled_states = &mut states.aura.enabled;
ui.heading("Lights go brrrrr! (incomplete)");
ui.separator();
let has_logo = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::Logo);
let has_lightbar = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarLeft)
|| supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarRight);
let boot_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootBar);
let boot_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootLogo);
let boot_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootKeyb);
let awake_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeBar);
let awake_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeLogo);
let awake_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeKeyb);
let sleep_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepBar);
let sleep_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepLogo);
let sleep_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepKeyb);
let mut changed = false;
let mut item = |keyboard: &mut bool, logo: &mut bool, lightbar: &mut bool, ui: &mut Ui| {
ui.horizontal_wrapped(|ui| {
if ui.checkbox(keyboard, "Keyboard").changed() {
changed = true;
}
if has_logo && ui.checkbox(logo, "Logo").changed() {
changed = true;
}
if has_lightbar && ui.checkbox(lightbar, "Lightbar").changed() {
changed = true;
}
});
};
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
let h = 16.0;
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Boot").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Awake").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Sleep").size(h));
});
});
ui.vertical(|ui| {
ui.set_row_height(22.0);
item(boot_keyb, boot_logo, boot_bar, ui);
item(awake_keyb, awake_logo, awake_bar, ui);
item(sleep_keyb, sleep_logo, sleep_bar, ui);
});
});
if changed {
let mut enabled = Vec::new();
let mut disabled = Vec::new();
let mut modify = |b: bool, a: AuraDev19b6| {
if b {
enabled.push(a);
if !enabled_states.x19b6.contains(&a) {
enabled_states.x19b6.push(a);
}
} else {
disabled.push(a);
// This would be so much better as a hashset
if enabled_states.x19b6.contains(&a) {
let mut idx = 0;
for (i, n) in enabled_states.x19b6.iter().enumerate() {
if *n == a {
idx = i;
break;
}
}
enabled_states.x1866.remove(idx);
}
}
};
modify(*boot_keyb, AuraDev19b6::BootKeyb);
modify(*sleep_keyb, AuraDev19b6::SleepKeyb);
modify(*awake_keyb, AuraDev19b6::AwakeKeyb);
if supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::Logo)
{
modify(*boot_logo, AuraDev19b6::BootLogo);
modify(*sleep_logo, AuraDev19b6::SleepLogo);
modify(*awake_logo, AuraDev19b6::AwakeLogo);
}
if supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarLeft)
{
modify(*boot_bar, AuraDev19b6::BootBar);
modify(*sleep_bar, AuraDev19b6::SleepBar);
modify(*awake_bar, AuraDev19b6::AwakeBar);
}
let mut send = |enable: bool, data: Vec<AuraDev19b6>| {
let options = AuraPowerDev {
x1866: vec![],
x19b6: data,
};
// build data to send
dbus.proxies()
.led()
.set_leds_power(options, enable)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
};
send(true, enabled);
send(false, disabled);
}
}
fn aura_modes(
supported: &SupportedFunctions,
states: &mut PageDataStates,
dbus: &mut RogDbusClientBlocking,
ui: &mut Ui,
) {
let mut changed = false;
let mut selected = states.aura.current_mode;
let has_keyzones = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::Key2);
let has_logo = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::Logo);
let has_lightbar = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarLeft)
|| supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarRight);
ui.heading("Aura modes");
let mut item = |a: AuraModeNum, ui: &mut Ui| {
if ui
.selectable_value(&mut selected, a, format!("{:?}", a))
.clicked()
{
changed = true;
}
};
ui.horizontal_wrapped(|ui| {
for a in states.aura.modes.keys() {
item(*a, ui);
}
});
// TODO: Need some sort of mapping to enable options only if
// they actually work.
if let Some(effect) = states.aura.modes.get_mut(&selected) {
let mut zone_button = |a: AuraZone, ui: &mut Ui| {
ui.selectable_value(&mut effect.zone, a, format!("{:?}", a));
};
let mut speed_button = |a: Speed, ui: &mut Ui| {
ui.selectable_value(&mut effect.speed, a, format!("{:?}", a));
};
let mut dir_button = |a: rog_aura::Direction, ui: &mut Ui| {
ui.selectable_value(&mut effect.direction, a, format!("{:?}", a));
};
let mut c1: [f32; 3] = effect.colour1.into();
let mut c2: [f32; 3] = effect.colour2.into();
ui.separator();
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
let h = 16.0;
ui.set_row_height(22.0);
if has_keyzones || has_lightbar || has_logo {
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Zone").size(h));
});
}
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Colour 1").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Colour 2").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Speed").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Direction").size(h));
});
});
ui.vertical(|ui| {
ui.set_row_height(22.0);
if has_keyzones || has_lightbar || has_logo {
ui.horizontal_wrapped(|ui| {
zone_button(AuraZone::None, ui);
if has_keyzones {
zone_button(AuraZone::Key1, ui);
zone_button(AuraZone::Key2, ui);
zone_button(AuraZone::Key3, ui);
zone_button(AuraZone::Key4, ui);
}
if has_logo {
zone_button(AuraZone::Logo, ui);
}
if has_lightbar {
zone_button(AuraZone::BarLeft, ui);
zone_button(AuraZone::BarRight, ui);
}
});
}
egui::color_picker::color_edit_button_rgb(ui, &mut c1);
egui::color_picker::color_edit_button_rgb(ui, &mut c2);
ui.horizontal_wrapped(|ui| {
speed_button(Speed::Low, ui);
speed_button(Speed::Med, ui);
speed_button(Speed::High, ui);
});
ui.horizontal_wrapped(|ui| {
dir_button(rog_aura::Direction::Left, ui);
dir_button(rog_aura::Direction::Down, ui);
dir_button(rog_aura::Direction::Right, ui);
dir_button(rog_aura::Direction::Up, ui);
});
});
});
effect.colour1 = Colour::from(&c1);
effect.colour2 = Colour::from(&c2);
}
ui.separator();
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
if ui.add(egui::Button::new("Cancel")).clicked() {
let notif = states.aura.was_notified.clone();
states.aura.modes = AuraState::new(notif, supported, dbus).modes;
}
if ui.add(egui::Button::new("Apply")).clicked() {
changed = true;
}
});
// egui::TopBottomPanel::bottom("error_bar")
// .default_height(26.0)
// .show(ctx, |ui| {
// ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
// if ui.add(egui::Button::new("Cancel")).clicked() {
// let notif = states.aura.was_notified.clone();
// states.aura.modes = AuraState::new(notif, supported, dbus).modes;
// }
// if ui.add(egui::Button::new("Apply")).clicked() {
// changed = true;
// }
// });
// });
if changed {
states.aura.current_mode = selected;
dbus.proxies()
.led()
.set_led_mode(states.aura.modes.get(&selected).unwrap())
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
}
}
@@ -0,0 +1,199 @@
use crate::{
page_states::{FanCurvesState, ProfilesState},
RogApp,
};
use egui::{plot::Points, Ui};
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::{FanCurvePU, Profile};
use rog_supported::SupportedFunctions;
impl<'a> RogApp<'a> {
pub fn fan_curve_page(&mut self, ctx: &egui::Context) {
let Self {
supported,
states,
asus_dbus: dbus,
..
} = self;
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Custom fan curves");
ui.label("A fan curve is only active when the related profile is active and the curve is enabled");
Self::fan_curve(
supported,
&mut states.profiles,
&mut states.fan_curves,
dbus, &mut states.error,
ui,
);
Self::fan_graphs(&mut states.profiles, &mut states.fan_curves, dbus, &mut states.error, ui);
});
}
fn fan_curve(
supported: &SupportedFunctions,
profiles: &mut ProfilesState,
curves: &mut FanCurvesState,
dbus: &RogDbusClientBlocking,
do_error: &mut Option<String>,
ui: &mut Ui,
) {
ui.separator();
ui.label("Enabled fan-curves");
let mut changed = false;
ui.horizontal(|ui| {
let mut item = |p: Profile, _curves: &mut FanCurvesState, mut checked: bool| {
if ui
.add(egui::Checkbox::new(&mut checked, format!("{:?}", p)))
.changed()
{
dbus.proxies()
.profile()
.set_fan_curve_enabled(p, checked)
.map_err(|err| {
*do_error = Some(err.to_string());
})
.ok();
#[cfg(feature = "mocking")]
if !checked {
_curves.enabled.remove(&p);
} else {
_curves.enabled.insert(p);
}
changed = true;
}
};
for f in profiles.list.iter() {
item(*f, curves, curves.enabled.contains(f));
}
});
if changed {
// Need to update app data if change made
#[cfg(not(feature = "mocking"))]
{
let notif = curves.was_notified.clone();
*curves = FanCurvesState::new(notif, supported, dbus);
}
}
}
fn fan_graphs(
profiles: &mut ProfilesState,
curves: &mut FanCurvesState,
dbus: &RogDbusClientBlocking,
do_error: &mut Option<String>,
ui: &mut Ui,
) {
ui.separator();
let mut item = |p: Profile, ui: &mut Ui| {
ui.selectable_value(&mut curves.show_curve, p, format!("{p:?}"));
};
ui.horizontal_wrapped(|ui| {
for a in curves.curves.iter() {
item(*a.0, ui);
}
ui.selectable_value(
&mut curves.show_graph,
FanCurvePU::CPU,
format!("{:?}", FanCurvePU::CPU),
);
ui.selectable_value(
&mut curves.show_graph,
FanCurvePU::GPU,
format!("{:?}", FanCurvePU::GPU),
);
});
let curve = curves.curves.get_mut(&curves.show_curve).unwrap();
use egui::plot::{Line, Plot, PlotPoints};
let data = if curves.show_graph == FanCurvePU::CPU {
&mut curve.cpu
} else {
&mut curve.gpu
};
let points = data.temp.iter().enumerate().map(|(idx, x)| {
let x = *x as f64;
let y = ((data.pwm[idx] as u32) * 100 / 255) as f64;
[x, y]
});
let line = Line::new(PlotPoints::from_iter(points.clone())).width(2.0);
let points = Points::new(PlotPoints::from_iter(points)).radius(3.0);
Plot::new("my_plot")
.view_aspect(2.0)
// .center_x_axis(true)
// .center_y_axis(true)
.include_x(0.0)
.include_x(110.0)
.include_y(0.0)
.include_y(110.0)
.allow_scroll(false)
.allow_drag(false)
.allow_boxed_zoom(false)
.x_axis_formatter(|d, _r| format!("{}", d))
.y_axis_formatter(|d, _r| format!("{:.*}%", 1, d))
.label_formatter(|name, value| {
if !name.is_empty() {
format!("{}: {:.*}%", name, 1, value.y)
} else {
format!("Temp {}c\nFan {:.*}%", value.x as u8, 1, value.y)
}
})
.show(ui, |plot_ui| {
if plot_ui.plot_hovered() {
let mut idx = 0;
if let Some(point) = plot_ui.pointer_coordinate() {
let mut x: i32 = 255;
for (i, n) in data.temp.iter().enumerate() {
let tmp = x.min((point.x as i32 - *n as i32).abs());
if tmp < x {
x = tmp;
idx = i;
}
}
if plot_ui.plot_clicked() {
data.temp[idx] = point.x as u8;
data.pwm[idx] = (point.y * 255.0 / 100.0) as u8;
} else {
let drag = plot_ui.pointer_coordinate_drag_delta();
if drag.length_sq() != 0.0 {
data.temp[idx] = (point.x as f32 + drag.x) as u8;
data.pwm[idx] = ((point.y as f32 + drag.y) * 255.0 / 100.0) as u8;
}
}
}
}
plot_ui.line(line);
plot_ui.points(points)
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
if ui.add(egui::Button::new("Apply Fan-curve")).clicked() {
#[cfg(not(feature = "mocking"))]
dbus.proxies()
.profile()
.set_fan_curve(profiles.current, data.clone())
.map_err(|err| {
*do_error = Some(err.to_string());
})
.ok();
#[cfg(feature = "mocking")]
dbg!("Applied");
}
});
}
}
+13
View File
@@ -0,0 +1,13 @@
mod anime_page;
mod aura_page;
mod fan_curve_page;
mod side_panel;
mod system_page;
mod top_bar;
pub use anime_page::*;
pub use aura_page::*;
pub use fan_curve_page::*;
pub use side_panel::*;
pub use system_page::*;
pub use top_bar::*;
@@ -0,0 +1,62 @@
use crate::{Page, RogApp};
impl<'a> RogApp<'a> {
pub fn side_panel(&mut self, ctx: &egui::Context) {
egui::SidePanel::left("side_panel")
.resizable(false)
.default_width(60.0) // TODO: set size to match icon buttons when done
.show(ctx, |ui| {
let Self { page, .. } = self;
ui.heading("Functions");
ui.separator();
if ui
.selectable_value(page, Page::System, "System Settings")
.clicked()
{
*page = Page::System;
}
if self.supported.platform_profile.fan_curves || cfg!(feature = "mocking") {
ui.separator();
if ui
.selectable_value(page, Page::FanCurves, "Fan Curves")
.clicked()
{
*page = Page::FanCurves;
}
}
if !self.supported.keyboard_led.stock_led_modes.is_empty()
|| cfg!(feature = "mocking")
{
ui.separator();
if ui
.selectable_value(page, Page::AuraEffects, "Keyboard Aura")
.clicked()
{
*page = Page::AuraEffects;
}
}
if self.supported.anime_ctrl.0 || cfg!(feature = "mocking") {
ui.separator();
if ui
.selectable_value(page, Page::AnimeMatrix, "AniMe Matrix")
.clicked()
{
*page = Page::AnimeMatrix;
}
}
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("Source code ");
ui.hyperlink_to("rog-gui.", "https://gitlab.com/asus-linux/rog-gui");
});
});
});
}
}
@@ -0,0 +1,135 @@
use crate::{page_states::PageDataStates, RogApp};
use egui::Ui;
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::Profile;
impl<'a> RogApp<'a> {
pub fn system_page(&mut self, ctx: &egui::Context) {
let Self {
supported,
states,
asus_dbus: dbus,
..
} = self;
egui::CentralPanel::default().show(ctx, |ui| {
// The central panel the region left after adding TopPanel's and SidePanel's
ui.heading("Experimental application for asusd");
ui.horizontal(|ui| {
egui::global_dark_light_mode_buttons(ui);
egui::warn_if_debug_build(ui);
});
ui.separator();
egui::ScrollArea::vertical().show(ui, |ui| {
ui.heading("Charge control");
let slider = egui::Slider::new(&mut states.charge_limit, 20..=100)
.text("Limit")
.step_by(1.0);
if ui.add(slider).drag_released() {
dbus.proxies()
.charge()
.set_limit(states.charge_limit as u8)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
ui.separator();
ui.heading("Bios options");
if supported.rog_bios_ctrl.post_sound {
if ui
.add(egui::Checkbox::new(
&mut states.bios.post_sound,
"POST sound",
))
.changed()
{
dbus.proxies()
.rog_bios()
.set_post_boot_sound(states.bios.post_sound)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
}
if supported.rog_bios_ctrl.post_sound {
if ui
.add(egui::Checkbox::new(
&mut states.bios.panel_overdrive,
"Panel overdrive",
))
.changed()
{
dbus.proxies()
.rog_bios()
.set_panel_overdrive(states.bios.panel_overdrive)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
}
if supported.rog_bios_ctrl.dedicated_gfx {
if ui
.add(egui::Checkbox::new(
&mut states.bios.dedicated_gfx,
"G-Sync Dedicated GPU mode",
))
.changed()
{
dbus.proxies()
.rog_bios()
.set_dedicated_graphic_mode(states.bios.dedicated_gfx)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
}
if supported.platform_profile.platform_profile {
Self::platform_profile(states, dbus, ui);
}
});
});
}
fn platform_profile(states: &mut PageDataStates, dbus: &RogDbusClientBlocking, ui: &mut Ui) {
ui.separator();
ui.heading("Platform profile");
let mut changed = false;
let mut item = |p: Profile, ui: &mut Ui| {
if ui
.selectable_value(&mut states.profiles.current, p, format!("{p:?}"))
.clicked()
{
changed = true;
}
};
ui.horizontal_wrapped(|ui| {
for a in states.profiles.list.iter() {
item(*a, ui);
}
});
if changed {
dbus.proxies()
.profile()
.set_active_profile(states.profiles.current)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
};
}
}
+42
View File
@@ -0,0 +1,42 @@
use std::sync::atomic::Ordering;
use crate::RogApp;
impl<'a> RogApp<'a> {
pub fn top_bar(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
let Self { states, config, .. } = self;
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Quit").clicked() {
frame.quit();
}
});
ui.menu_button("Settings", |ui| {
if ui
.checkbox(&mut config.run_in_background, "Run in Background")
.clicked()
|| ui
.checkbox(&mut config.startup_in_background, "Startup Hidden")
.clicked()
|| ui
.checkbox(&mut config.enable_notifications, "Enable Notifications")
.clicked()
{
states
.notifs_enabled
.store(config.enable_notifications, Ordering::SeqCst);
config
.save()
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
});
});
});
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rog_dbus"
version = "4.2.0"
version = "4.2.1"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
+12 -13
View File
@@ -1,7 +1,7 @@
//! # DBus interface proxy for: `org.asuslinux.Daemon`
//!
//! This code was generated by `zbus-xmlgen` `1.0.0` from DBus introspection data.
//! Source: `Interface '/org/asuslinux/Led' from service 'org.asuslinux.Daemon' on system bus`.
//! Source: `Interface '/org/asuslinux/Aura' from service 'org.asuslinux.Daemon' on system bus`.
//!
//! You may prefer to adapt it, instead of using it verbatim.
//!
@@ -19,16 +19,18 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::collections::BTreeMap;
use zbus::{blocking::Connection, Result};
use zbus_macros::dbus_proxy;
use rog_aura::{usb::AuraControl, AuraEffect, KeyColourArray, LedBrightness};
use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, KeyColourArray, LedBrightness};
const BLOCKING_TIME: u64 = 40; // 100ms = 10 FPS, max 50ms = 20 FPS, 40ms = 25 FPS
#[dbus_proxy(
interface = "org.asuslinux.Daemon",
default_path = "/org/asuslinux/Led"
default_path = "/org/asuslinux/Aura"
)]
trait Led {
/// NextLedMode method
@@ -49,31 +51,28 @@ trait Led {
/// SetLedMode method
fn set_led_mode(&self, effect: &AuraEffect) -> zbus::Result<()>;
fn set_leds_enabled(&self, enabled: Vec<AuraControl>) -> zbus::Result<()>;
fn set_leds_disabled(&self, disabled: Vec<AuraControl>) -> zbus::Result<()>;
fn set_leds_power(&self, options: AuraPowerDev, enabled: bool) -> zbus::Result<()>;
/// NotifyLed signal
#[dbus_proxy(signal)]
fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>;
#[dbus_proxy(signal)]
fn notify_power_states(&self, data: Vec<AuraControl>) -> zbus::Result<()>;
fn notify_power_states(&self, data: AuraPowerDev) -> zbus::Result<()>;
/// LedBrightness property
#[dbus_proxy(property)]
fn led_brightness(&self) -> zbus::Result<i16>;
/// LedMode property
#[dbus_proxy(property)]
fn led_mode(&self) -> zbus::Result<String>;
fn led_mode(&self) -> zbus::Result<AuraModeNum>;
/// LedModes property
#[dbus_proxy(property)]
fn led_modes(&self) -> zbus::Result<String>;
fn led_modes(&self) -> zbus::Result<BTreeMap<AuraModeNum, AuraEffect>>;
#[dbus_proxy(property)]
fn leds_enabled(&self) -> zbus::Result<Vec<u8>>;
// As property doesn't work for AuraPowerDev (complexity of serialization?)
// #[dbus_proxy(property)]
fn leds_enabled(&self) -> zbus::Result<AuraPowerDev>;
}
pub struct LedProxyPerkey<'a>(LedProxyBlocking<'a>);
+6 -6
View File
@@ -29,9 +29,9 @@ pub(crate) fn temp_str(fan: char, index: usize) -> String {
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
pub struct CurveData {
pub(crate) fan: FanCurvePU,
pub(crate) pwm: [u8; 8],
pub(crate) temp: [u8; 8],
pub fan: FanCurvePU,
pub pwm: [u8; 8],
pub temp: [u8; 8],
}
impl std::str::FromStr for CurveData {
@@ -166,9 +166,9 @@ impl CurveData {
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FanCurveSet {
pub(crate) enabled: bool,
pub(crate) cpu: CurveData,
pub(crate) gpu: CurveData,
pub enabled: bool,
pub cpu: CurveData,
pub gpu: CurveData,
}
impl Default for FanCurveSet {
+1 -1
View File
@@ -38,7 +38,7 @@ pub fn find_fan_curve_node() -> Result<Option<Device>, ProfileError> {
}
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy)]
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum Profile {
Balanced,
Performance,
+2 -1
View File
@@ -1,6 +1,6 @@
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
use rog_aura::{AuraModeNum, AuraZone};
use rog_aura::{usb::AuraDevice, AuraModeNum, AuraZone};
use serde_derive::{Deserialize, Serialize};
use std::fmt;
use zvariant_derive::Type;
@@ -30,6 +30,7 @@ pub struct PlatformProfileFunctions {
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct LedSupportedFunctions {
pub prod_id: AuraDevice,
pub brightness_set: bool,
pub stock_led_modes: Vec<AuraModeNum>,
pub multizone_led_mode: Vec<AuraZone>,