Skip to content
Open
10 changes: 5 additions & 5 deletions asus-shutdown/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,23 +152,23 @@ async fn acquire_shutdown_inhibitor(manager: &ManagerProxy<'_>) -> Result<OwnedF
}

async fn print_dry_run_actions() {
println!("asus-shutdown dry-run mode (manual start)");
println!("Planned shutdown actions from asusd queue:");
info!("asus-shutdown dry-run mode (manual start)");
info!("Planned shutdown actions from asusd queue:");

match fetch_pending_actions().await {
Ok(actions) if actions.is_empty() => {
println!("- none");
info!("- none");
}
Ok(actions) => {
for action in actions {
println!(
info!(
"- {} => {} (path: {})",
action.name, action.value, action.path
);
}
}
Err(err) => {
println!("- could not query asusd queue: {err}");
warn!("- could not query asusd queue: {err}");
}
}
}
Expand Down
235 changes: 232 additions & 3 deletions asusctl/src/anime_cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use argh::FromArgs;
use rog_anime::usb::{AnimAwake, AnimBooting, AnimShutdown, AnimSleeping};
use log::warn;
use rog_anime::usb::{get_anime_type, AnimAwake, AnimBooting, AnimShutdown, AnimSleeping};
use rog_anime::AnimeType;
use rog_dbus::find_iface_blocking;

#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "anime", description = "anime commands")]
Expand Down Expand Up @@ -30,8 +32,6 @@ pub struct AnimeCommand {
pub off_when_suspended: Option<bool>,
#[argh(option, description = "turn the anime off when the lid is closed")]
pub off_when_lid_closed: Option<bool>,
#[argh(option, description = "off with his head!!!")]
pub off_with_his_head: Option<bool>,
#[argh(subcommand)]
pub command: Option<AnimeActions>,
}
Expand Down Expand Up @@ -149,3 +149,232 @@ pub struct AnimeGifDiagonal {
)]
pub loops: u32,
}

pub fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
if cmd.command.is_none()
&& cmd.enable_display.is_none()
&& cmd.enable_powersave_anim.is_none()
&& cmd.brightness.is_none()
&& cmd.off_when_lid_closed.is_none()
&& cmd.off_when_suspended.is_none()
&& cmd.off_when_unplugged.is_none()
&& !cmd.clear
{
warn!("Missing arg or command; run 'asusctl anime --help' for usage");
return Ok(());
}

if let Some(action) = cmd.command.as_ref() {
match action {
AnimeActions::Image(image) if image.path.is_empty() => {
warn!("Missing arg or command; run 'asusctl anime image --help' for usage");
return Ok(());
}
AnimeActions::PixelImage(image) if image.path.is_empty() => {
warn!("Missing arg or command; run 'asusctl anime pixel-image --help' for usage");
return Ok(());
}
AnimeActions::Gif(gif) if gif.path.is_empty() => {
warn!("Missing arg or command; run 'asusctl anime gif --help' for usage");
return Ok(());
}
AnimeActions::PixelGif(gif) if gif.path.is_empty() => {
warn!("Missing arg or command; run 'asusctl anime pixel-gif --help' for usage");
return Ok(());
}
AnimeActions::SetBuiltins(builtins) if builtins.set.is_none() => {
warn!("Missing arg; run 'asusctl anime set-builtins --help' for usage");
return Ok(());
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
_ => {}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

let animes =
find_iface_blocking::<rog_dbus::zbus_anime::AnimeProxyBlocking>("xyz.ljones.Anime")?;

let mut anime_type = get_anime_type();
if let Some(model) = cmd.override_type {
anime_type = model;
} else if let AnimeType::Unsupported = anime_type {
warn!("Anime display type is Unsupported; consider specifying --override-type");
}

for proxy in animes {
if let Some(enable) = cmd.enable_display {
proxy.set_enable_display(enable)?;
}
if let Some(enable) = cmd.enable_powersave_anim {
proxy.set_builtins_enabled(enable)?;
}
if let Some(bright) = cmd.brightness {
proxy.set_brightness(bright)?;
}
if let Some(enable) = cmd.off_when_lid_closed {
proxy.set_off_when_lid_closed(enable)?;
}
if let Some(enable) = cmd.off_when_suspended {
proxy.set_off_when_suspended(enable)?;
}
if let Some(enable) = cmd.off_when_unplugged {
proxy.set_off_when_unplugged(enable)?;
}

if cmd.clear {
let data = vec![255u8; anime_type.data_length()];
let tmp = rog_anime::AnimeDataBuffer::from_vec(anime_type, data)?;
proxy.write(tmp)?;
}

if let Some(action) = cmd.command.as_ref() {
match action {
AnimeActions::Image(image) => {
verify_brightness(image.bright)?;

let matrix = rog_anime::AnimeImage::from_png(
std::path::Path::new(&image.path),
image.scale,
image.angle,
rog_anime::Vec2::new(image.x_pos, image.y_pos),
image.bright,
anime_type,
)?;

proxy.write(<rog_anime::AnimeDataBuffer>::try_from(&matrix)?)?;
}
AnimeActions::PixelImage(image) => {
verify_brightness(image.bright)?;

let matrix = rog_anime::AnimeDiagonal::from_png(
std::path::Path::new(&image.path),
None,
image.bright,
anime_type,
)?;

proxy.write(matrix.into_data_buffer(anime_type)?)?;
}
AnimeActions::Gif(gif) => {
verify_brightness(gif.bright)?;

let matrix = rog_anime::AnimeGif::from_gif(
std::path::Path::new(&gif.path),
gif.scale,
gif.angle,
rog_anime::Vec2::new(gif.x_pos, gif.y_pos),
rog_anime::AnimTime::Count(1),
gif.bright,
anime_type,
)?;

play_gif_animation(&proxy, &matrix, gif.loops)?;
}
AnimeActions::PixelGif(gif) => {
verify_brightness(gif.bright)?;

let matrix = rog_anime::AnimeGif::from_diagonal_gif(
std::path::Path::new(&gif.path),
rog_anime::AnimTime::Count(1),
gif.bright,
anime_type,
)?;

play_gif_animation(&proxy, &matrix, gif.loops)?;
}
AnimeActions::SetBuiltins(builtins) => {
if builtins.set == Some(true) {
proxy.set_builtin_animations(rog_anime::Animations {
boot: builtins.boot,
awake: builtins.awake,
sleep: builtins.sleep,
shutdown: builtins.shutdown,
})?;
}
}
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Ok(())
}

/// Helper to determine the playback iteration strategy.
/// Returns `None` for infinite loops (`loops == 0`), or `Some(count)` for finite playback.
fn compute_loop_plan(loops: u32) -> Option<u32> {
if loops == 0 {
None
} else {
Some(loops)
}
}

/// Play GIF animation frames. `loops == 0` means infinite playback until interrupted.
fn play_gif_animation(
proxy: &rog_dbus::zbus_anime::AnimeProxyBlocking,
matrix: &rog_anime::AnimeGif,
loops: u32,
) -> Result<(), Box<dyn std::error::Error>> {
let mut remaining = compute_loop_plan(loops);

loop {
for frame in matrix.frames() {
proxy.write(frame.frame().clone())?;
std::thread::sleep(frame.delay());
}
match remaining {
None => continue,
Some(1) => break,
Some(ref mut count) => *count -= 1,
}
}
Ok(())
}

fn verify_brightness(brightness: f32) -> Result<(), Box<dyn std::error::Error>> {
if !(0.0..=1.0).contains(&brightness) {
return Err(format!(
"Brightness must be between 0.0 and 1.0 (inclusive), was {brightness}"
)
.into());
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_verify_brightness_valid() {
assert!(verify_brightness(0.0).is_ok());
assert!(verify_brightness(0.5).is_ok());
assert!(verify_brightness(1.0).is_ok());
}

#[test]
fn test_verify_brightness_invalid() {
assert!(verify_brightness(-0.1).is_err());
assert!(verify_brightness(1.1).is_err());
assert!(verify_brightness(f32::NAN).is_err());
assert!(verify_brightness(f32::INFINITY).is_err());
assert!(verify_brightness(f32::NEG_INFINITY).is_err());
}

#[test]
fn test_loop_iteration_count() {
assert_eq!(
compute_loop_plan(0),
None,
"0 loops must plan for infinite playback"
);
assert_eq!(
compute_loop_plan(1),
Some(1),
"1 loop must plan for 1 iteration"
);
assert_eq!(
compute_loop_plan(5),
Some(5),
"5 loops must plan for 5 iterations"
);
}
}
75 changes: 75 additions & 0 deletions asusctl/src/fan_curve_cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use argh::FromArgs;
use log::warn;
use rog_platform::platform::PlatformProfile;
use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::FanCurvePU;
Expand Down Expand Up @@ -42,3 +43,77 @@ pub struct FanCurveCommand {
)]
pub data: Option<CurveData>,
}

const REQ_MOD_PROFILE_MSG: &str =
"--enable-fan-curves, --enable-fan-curve, --fan, and --data options require --mod-profile";

pub fn handle_fan_curve(
conn: &zbus::blocking::Connection,
cmd: &FanCurveCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if !cmd.get_enabled && !cmd.default && cmd.mod_profile.is_none() {
warn!("Missing arg or command; run 'asusctl fan-curve --help' for usage");
return Ok(());
}

if (cmd.enable_fan_curves.is_some()
|| cmd.enable_fan_curve.is_some()
|| cmd.fan.is_some()
|| cmd.data.is_some())
&& cmd.mod_profile.is_none()
{
warn!("{REQ_MOD_PROFILE_MSG}");
return Ok(());
}

let fan_proxy = rog_dbus::zbus_fan_curves::FanCurvesProxyBlocking::new(conn).map_err(|e| {
warn!("Fan curves unavailable: {e}");
rog_profiles::error::ProfileError::NotSupported
})?;

let plat_proxy = rog_dbus::zbus_platform::PlatformProxyBlocking::new(conn)?;
if cmd.get_enabled {
let profile = plat_proxy.platform_profile()?;
let curves = fan_proxy.fan_curve_data(profile)?;
for curve in curves.iter() {
log::info!("{}", String::from(curve));
}
}

if cmd.default {
let active = plat_proxy.platform_profile()?;
fan_proxy.set_curves_to_defaults(active)?;
}

if let Some(profile) = cmd.mod_profile {
if cmd.enable_fan_curves.is_none() && cmd.enable_fan_curve.is_none() && cmd.data.is_none() {
let data = fan_proxy.fan_curve_data(profile)?;
let ron =
ron::ser::to_string_pretty(&data, ron::ser::PrettyConfig::new().depth_limit(4))?;
log::info!("\nFan curves for {:?}\n\n{}", profile, ron);
}

if let Some(enabled) = cmd.enable_fan_curves {
fan_proxy.set_fan_curves_enabled(profile, enabled)?;
}

if let Some(enabled) = cmd.enable_fan_curve {
if let Some(fan) = cmd.fan {
fan_proxy.set_profile_fan_curve_enabled(profile, fan, enabled)?;
} else {
warn!("--enable-fan-curve requires --fan <cpu/gpu/mid>");
}
}

if let Some(mut curve) = cmd.data.clone() {
if let Some(fan) = cmd.fan {
curve.set_fan(fan);
fan_proxy.set_fan_curve(profile, curve)?;
} else {
warn!("--data requires --fan <cpu/gpu/mid>");
}
}
}

Ok(())
}
Loading