Compare commits

...

16 Commits

Author SHA1 Message Date
rforced
5d52626e32 Merge branch 'fix-z13-2025-1' into 'main'
Draft: Fixes for Asus z13 2025

See merge request asus-linux/asusctl!245
2026-01-16 16:37:37 -05:00
Josh Dariano
f471f340d4 Fix fan controls on z13 2026-01-16 16:37:15 -05:00
Josh Dariano
8095ac34ed Fix formatting 2026-01-16 15:48:02 -05:00
Josh Dariano
9d629b62ca Fix aura backlight for z13 2026-01-16 15:33:30 -05:00
Josh Dariano
5282c56f59 Merge remote-tracking branch 'rforced/fix-z13-2025' 2026-01-16 14:13:45 -05:00
Josh Dariano
0d2cd4eb10 Fix keyboard light 2026-01-16 13:57:35 -05:00
Denis Benato
b20ecf5378 chore: README.md edits 2026-01-16 19:53:58 +01:00
Denis Benato
ade839e981 chore: Update README.md 2026-01-16 19:18:15 +01:00
Denis Benato
d625c279a6 fix: G835LW doesn't have pulse available in windows 2026-01-16 19:13:00 +01:00
Denis Benato
5ea14be3fa chore: add extra/index.html: docs landing redirect to asusctl docs 2026-01-14 14:25:13 +01:00
Denis Benato
64c2e55db4 chore: prepare for a new version 2026-01-14 02:27:39 +01:00
Denis Benato
5303bfc1ad Release: 6.3.0 2026-01-14 02:16:22 +01:00
Denis Benato
635f1dc9b9 Feat: add support for G835L 2026-01-14 02:13:30 +01:00
Denis Benato
33a4dba8fe fix: rogcc not starting in rog ally 2026-01-14 02:10:03 +01:00
Denis Benato
e4680c9543 Merge branch 'remove_supergfxctl' into devel 2026-01-14 02:02:26 +01:00
Denis Benato
e9c5315bda Feat: Remove supergfxctl notification daemon 2026-01-13 22:05:34 +01:00
16 changed files with 294 additions and 305 deletions

View File

@@ -90,7 +90,7 @@ pages:
- rm -rf public - rm -rf public
- mkdir public - mkdir public
- cp -R ci-target/doc/* public - cp -R ci-target/doc/* public
- cp extra/index.html public - if [ -f extra/index.html ]; then cp extra/index.html public; else echo "no extra/index.html to copy"; fi
artifacts: artifacts:
paths: paths:
- public - public

View File

@@ -1,12 +1,19 @@
# Changelog # Changelog
## [Unreleased] ## [6.3.1]
### Changes
- Removed a lighting mode that is unavailable in windows to G835L: thanks to @shevchenko0013 again!
## [6.3.0]
### Changed ### Changed
- Added support for TUF keyboard powerstate control - Added support for TUF keyboard powerstate control
- Improved AniMe Matrix support thanks to @Seom1177 ! - Improved AniMe Matrix support thanks to @Seom1177 !
- Fixed a bug with one-shot battery change, thanks @bitr8 ! - Fixed a bug with one-shot battery change, thanks @bitr8 !
- Changed the CLI interface of asusctl to be less confusing - Changed the CLI interface of asusctl to be less confusing
- Added support for G835L, thanks to @shevchenko0013 !
## [6.2.0] ## [6.2.0]

28
Cargo.lock generated
View File

@@ -212,7 +212,7 @@ dependencies = [
[[package]] [[package]]
name = "asusctl" name = "asusctl"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"argh", "argh",
"dmi_id", "dmi_id",
@@ -232,7 +232,7 @@ dependencies = [
[[package]] [[package]]
name = "asusd" name = "asusd"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"cargo-husky", "cargo-husky",
"concat-idents", "concat-idents",
@@ -259,7 +259,7 @@ dependencies = [
[[package]] [[package]]
name = "asusd-user" name = "asusd-user"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"config-traits", "config-traits",
"dirs", "dirs",
@@ -938,7 +938,7 @@ dependencies = [
[[package]] [[package]]
name = "config-traits" name = "config-traits"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"log", "log",
"ron", "ron",
@@ -1274,7 +1274,7 @@ dependencies = [
[[package]] [[package]]
name = "dmi_id" name = "dmi_id"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"log", "log",
"udev 0.8.0", "udev 0.8.0",
@@ -4482,7 +4482,7 @@ dependencies = [
[[package]] [[package]]
name = "rog-control-center" name = "rog-control-center"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"asusd", "asusd",
"concat-idents", "concat-idents",
@@ -4513,7 +4513,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_anime" name = "rog_anime"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"dmi_id", "dmi_id",
"gif 0.12.0", "gif 0.12.0",
@@ -4527,7 +4527,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_aura" name = "rog_aura"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"dmi_id", "dmi_id",
"log", "log",
@@ -4538,7 +4538,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_dbus" name = "rog_dbus"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"asusd", "asusd",
"rog_anime", "rog_anime",
@@ -4552,7 +4552,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_platform" name = "rog_platform"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"concat-idents", "concat-idents",
"inotify", "inotify",
@@ -4565,7 +4565,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_profiles" name = "rog_profiles"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"log", "log",
"rog_platform", "rog_platform",
@@ -4576,7 +4576,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_scsi" name = "rog_scsi"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"ron", "ron",
"serde", "serde",
@@ -4586,7 +4586,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_simulators" name = "rog_simulators"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"log", "log",
"rog_anime", "rog_anime",
@@ -4596,7 +4596,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_slash" name = "rog_slash"
version = "6.2.0" version = "6.3.0"
dependencies = [ dependencies = [
"dmi_id", "dmi_id",
"serde", "serde",

View File

@@ -1,9 +1,12 @@
[workspace.package] [workspace.package]
version = "6.2.0" version = "6.3.0"
rust-version = "1.82" rust-version = "1.82"
license = "MPL-2.0" license = "MPL-2.0"
readme = "README.md" readme = "README.md"
authors = ["Luke <luke@ljones.dev>"] authors = [
"Luke <luke@ljones.dev>",
"Denis Benato <benato.denis96@gmail.com>"
]
repository = "https://gitlab.com/asus-linux/asusctl" repository = "https://gitlab.com/asus-linux/asusctl"
homepage = "https://gitlab.com/asus-linux/asusctl" homepage = "https://gitlab.com/asus-linux/asusctl"
description = "Laptop feature control for ASUS ROG laptops and others" description = "Laptop feature control for ASUS ROG laptops and others"

View File

@@ -13,9 +13,7 @@ Now includes a GUI, `rog-control-center`.
Due to on-going driver work the minimum suggested kernel version is always **the latest*, as improvements and fixes are continuous. Due to on-going driver work the minimum suggested kernel version is always **the latest*, as improvements and fixes are continuous.
Support for some new features is not avilable unless you run a patched kernel with the work I am doing [in this github repo](https://github.com/flukejones/linux/tree/wip/ally-6.13). Use the linked branch, or `wip/ally-6.12`. Everything that is done here is upstreamed eventually (a long process). Support for TDP is tied to the new asus-armoury driver: available mainline since linux 6.19: everything older is not supported.
Z13 devices will need [these](https://lore.kernel.org/linux-input/20240416090402.31057-1-luke@ljones.dev/T/#t)
## X11 support ## X11 support
@@ -180,3 +178,7 @@ Reference to any ASUS products, services, processes, or other information and/or
The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops. The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops.
--- ---
## AI Disaclaimer
Portions of this code have been written by various AI tools and reviewed by the maintainer exaclty as with every other contribution.

View File

@@ -25,6 +25,30 @@ pub struct Aura {
impl Aura { impl Aura {
/// Initialise the device if required. /// Initialise the device if required.
pub async fn do_initialization(&self) -> Result<(), RogError> { pub async fn do_initialization(&self) -> Result<(), RogError> {
if let Some(hid) = &self.hid {
let hid = hid.lock().await;
let init_1: [u8; 2] = [
0x5d, 0xb9,
];
let init_2 = b"]ASUS Tech.Inc.";
let init_3: [u8; 6] = [
0x5d, 0x05, 0x20, 0x31, 0, 0x1a,
];
hid.write_bytes(&init_1)?;
hid.write_bytes(init_2)?;
hid.write_bytes(&init_3)?;
let config = self.config.lock().await;
if config.support_data.device_name.contains("GZ30")
|| config.support_data.device_name.contains("Z13")
{
let z13_init: [u8; 4] = [
0x5d, 0xc0, 0x03, 0x01,
];
hid.write_bytes(&z13_init)?;
}
}
Ok(()) Ok(())
} }
@@ -152,9 +176,54 @@ impl Aura {
} }
} }
let bytes = config.enabled.to_bytes(config.led_type); let mut enabled = config.enabled.clone();
if config.support_data.device_name.contains("GZ30")
|| config.support_data.device_name.contains("Z13")
{
let logo_state = enabled
.states
.iter()
.find(|s| s.zone == PowerZones::Logo)
.cloned();
if let Some(logo) = logo_state {
let mut lid_found = false;
let mut bar_found = false;
for s in enabled.states.iter_mut() {
if s.zone == PowerZones::Lid {
s.boot = logo.boot;
s.awake = logo.awake;
s.sleep = logo.sleep;
s.shutdown = logo.shutdown;
lid_found = true;
}
if s.zone == PowerZones::Lightbar {
s.boot = logo.boot;
s.awake = logo.awake;
s.sleep = logo.sleep;
s.shutdown = logo.shutdown;
bar_found = true;
}
}
if !lid_found {
let mut new_state = logo;
new_state.zone = PowerZones::Lid;
enabled.states.push(new_state.clone());
new_state.zone = PowerZones::Lightbar;
enabled.states.push(new_state);
} else if !bar_found {
// Lid found but not bar?
let mut new_state = logo;
new_state.zone = PowerZones::Lightbar;
enabled.states.push(new_state);
}
}
}
let bytes = enabled.to_bytes(config.led_type);
let msg = [ let msg = [
0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3], 0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3], 0xff,
]; ];
hid_raw.write_bytes(&msg)?; hid_raw.write_bytes(&msg)?;
} }

View File

@@ -20,7 +20,7 @@
%global debug_package %{nil} %global debug_package %{nil}
%endif %endif
%define version 6.2.0 %define version 6.3.0
%define specrelease %{?dist} %define specrelease %{?dist}
%define pkg_release 1%{specrelease} %define pkg_release 1%{specrelease}

23
extra/index.html Normal file
View File

@@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>asusctl docs</title>
<!-- Redirect to the generated crate docs -->
<meta http-equiv="refresh" content="0;url=asusctl/index.html">
<link rel="canonical" href="asusctl/index.html">
<style>
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; color:#222; display:flex; align-items:center; justify-content:center; height:100vh; margin:0 }
.box { text-align:center }
a { color: #0366d6 }
</style>
</head>
<body>
<div class="box">
<h1>asusctl documentation</h1>
<p>Redirecting to the generated docs — if your browser doesn't redirect automatically, <a href="asusctl/index.html">click here</a>.</p>
<p>If you expected a different landing page, update <code>extra/index.html</code> accordingly.</p>
</div>
</body>
</html>

View File

@@ -566,6 +566,15 @@
advanced_type: PerKey, advanced_type: PerKey,
power_zones: [Keyboard, Lightbar, Logo, RearGlow], power_zones: [Keyboard, Lightbar, Logo, RearGlow],
), ),
(
device_name: "G835L",
product_id: "",
layout_name: "g814ji-per-key",
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Star, Rain, Highlight, Laser, Ripple, Comet, Flash],
basic_zones: [],
advanced_type: PerKey,
power_zones: [Keyboard, Lightbar, Logo],
),
( (
device_name: "GA401I", device_name: "GA401I",
product_id: "", product_id: "",
@@ -962,6 +971,24 @@
advanced_type: r#None, advanced_type: r#None,
power_zones: [Keyboard], power_zones: [Keyboard],
), ),
(
device_name: "GZ302",
product_id: "18c6",
layout_name: "",
basic_modes: [Static, Breathe, Pulse],
basic_zones: [],
advanced_type: r#None,
power_zones: [Logo],
),
(
device_name: "GZ302",
product_id: "1a30",
layout_name: "ga401q",
basic_modes: [Static, Breathe, Pulse],
basic_zones: [],
advanced_type: r#None,
power_zones: [Keyboard],
),
( (
device_name: "RC71L", device_name: "RC71L",
product_id: "", product_id: "",

View File

@@ -123,7 +123,8 @@ impl AuraPowerState {
| ((self.shutdown as u32) << 7) | ((self.shutdown as u32) << 7)
} }
PowerZones::Lightbar => { PowerZones::Lightbar => {
((self.boot as u32) << (7 + 2)) ((self.awake as u32) << (7 + 1))
| ((self.boot as u32) << (7 + 2))
| ((self.awake as u32) << (7 + 3)) | ((self.awake as u32) << (7 + 3))
| ((self.sleep as u32) << (7 + 4)) | ((self.sleep as u32) << (7 + 4))
| ((self.shutdown as u32) << (7 + 5)) | ((self.shutdown as u32) << (7 + 5))
@@ -133,12 +134,20 @@ impl AuraPowerState {
| ((self.awake as u32) << (15 + 2)) | ((self.awake as u32) << (15 + 2))
| ((self.sleep as u32) << (15 + 3)) | ((self.sleep as u32) << (15 + 3))
| ((self.shutdown as u32) << (15 + 4)) | ((self.shutdown as u32) << (15 + 4))
| ((self.boot as u32) << (15 + 5))
| ((self.awake as u32) << (15 + 6))
| ((self.sleep as u32) << (15 + 7))
| ((self.shutdown as u32) << (15 + 8))
} }
PowerZones::RearGlow => { PowerZones::RearGlow => {
((self.boot as u32) << (23 + 1)) ((self.boot as u32) << (23 + 1))
| ((self.awake as u32) << (23 + 2)) | ((self.awake as u32) << (23 + 2))
| ((self.sleep as u32) << (23 + 3)) | ((self.sleep as u32) << (23 + 3))
| ((self.shutdown as u32) << (23 + 4)) | ((self.shutdown as u32) << (23 + 4))
| ((self.boot as u32) << (23 + 5))
| ((self.awake as u32) << (23 + 6))
| ((self.sleep as u32) << (23 + 7))
| ((self.shutdown as u32) << (23 + 8))
} }
PowerZones::None | PowerZones::KeyboardAndLightbar => 0, PowerZones::None | PowerZones::KeyboardAndLightbar => 0,
} }
@@ -618,19 +627,19 @@ mod test {
assert_eq!(shut_keyb_, "10000000, 00000000, 00000000, 00000000"); assert_eq!(shut_keyb_, "10000000, 00000000, 00000000, 00000000");
// //
assert_eq!(boot_bar__, "00000000, 00000010, 00000000, 00000000"); assert_eq!(boot_bar__, "00000000, 00000010, 00000000, 00000000");
assert_eq!(awake_bar_, "00000000, 00000100, 00000000, 00000000"); assert_eq!(awake_bar_, "00000000, 00000101, 00000000, 00000000");
assert_eq!(sleep_bar_, "00000000, 00001000, 00000000, 00000000"); assert_eq!(sleep_bar_, "00000000, 00001000, 00000000, 00000000");
assert_eq!(shut_bar__, "00000000, 00010000, 00000000, 00000000"); assert_eq!(shut_bar__, "00000000, 00010000, 00000000, 00000000");
// //
assert_eq!(boot_lid__, "00000000, 00000000, 00000001, 00000000"); assert_eq!(boot_lid__, "00000000, 00000000, 00010001, 00000000");
assert_eq!(awake_lid_, "00000000, 00000000, 00000010, 00000000"); assert_eq!(awake_lid_, "00000000, 00000000, 00100010, 00000000");
assert_eq!(sleep_lid_, "00000000, 00000000, 00000100, 00000000"); assert_eq!(sleep_lid_, "00000000, 00000000, 01000100, 00000000");
assert_eq!(shut_lid__, "00000000, 00000000, 00001000, 00000000"); assert_eq!(shut_lid__, "00000000, 00000000, 10001000, 00000000");
// //
assert_eq!(boot_rear_, "00000000, 00000000, 00000000, 00000001"); assert_eq!(boot_rear_, "00000000, 00000000, 00000000, 00010001");
assert_eq!(awake_rear, "00000000, 00000000, 00000000, 00000010"); assert_eq!(awake_rear, "00000000, 00000000, 00000000, 00100010");
assert_eq!(sleep_rear, "00000000, 00000000, 00000000, 00000100"); assert_eq!(sleep_rear, "00000000, 00000000, 00000000, 01000100");
assert_eq!(shut_rear_, "00000000, 00000000, 00000000, 00001000"); assert_eq!(shut_rear_, "00000000, 00000000, 00000000, 10001000");
// All on // All on
let byte1 = to_binary_string_post2021(&LaptopAuraPower { let byte1 = to_binary_string_post2021(&LaptopAuraPower {
@@ -657,6 +666,6 @@ mod test {
}, },
], ],
}); });
assert_eq!(byte1, "11111111, 00011110, 00001111, 00001111"); assert_eq!(byte1, "11111111, 00011111, 11111111, 11111111");
} }
} }

View File

@@ -106,10 +106,10 @@ impl From<&str> for AuraDeviceType {
match s.to_lowercase().trim_start_matches("0x") { match s.to_lowercase().trim_start_matches("0x") {
"tuf" => AuraDeviceType::LaptopKeyboardTuf, "tuf" => AuraDeviceType::LaptopKeyboardTuf,
"1932" => AuraDeviceType::ScsiExtDisk, "1932" => AuraDeviceType::ScsiExtDisk,
"1866" | "18c6" | "1869" | "1854" => Self::LaptopKeyboardPre2021, "1866" | "1869" | "1854" => Self::LaptopKeyboardPre2021,
"1abe" | "1b4c" => Self::Ally, "1abe" | "1b4c" => Self::Ally,
"19b3" | "193b" => Self::AnimeOrSlash, "19b3" | "193b" => Self::AnimeOrSlash,
"19b6" => Self::LaptopKeyboard2021, "19b6" | "1a30" | "18c6" => Self::LaptopKeyboard2021,
_ => Self::Unknown, _ => Self::Unknown,
} }
} }

View File

@@ -14,6 +14,7 @@ mocking = []
x11 = ["slint/backend-winit-x11"] x11 = ["slint/backend-winit-x11"]
# Optional tokio debug feature does not require nightly; remove RUSTFLAGS note. # Optional tokio debug feature does not require nightly; remove RUSTFLAGS note.
tokio-debug = ["console-subscriber"] tokio-debug = ["console-subscriber"]
rog_ally = []
[dependencies] [dependencies]
console-subscriber = { version = "^0.4", optional = true } console-subscriber = { version = "^0.4", optional = true }

View File

@@ -107,7 +107,6 @@ async fn main() -> Result<()> {
let board_name = dmi.board_name; let board_name = dmi.board_name;
let prod_family = dmi.product_family; let prod_family = dmi.product_family;
info!("Running on {board_name}, product: {prod_family}"); info!("Running on {board_name}, product: {prod_family}");
let is_rog_ally = board_name == "RC71L" || board_name == "RC72L" || prod_family == "ROG Ally";
let args: Vec<String> = args().skip(1).collect(); let args: Vec<String> = args().skip(1).collect();
@@ -138,6 +137,18 @@ async fn main() -> Result<()> {
config.start_fullscreen = false; config.start_fullscreen = false;
} }
let is_rog_ally = {
#[cfg(feature = "rog_ally")]
{
board_name == "RC71L" || board_name == "RC72L" || prod_family == "ROG Ally"
}
#[cfg(not(feature = "rog_ally"))]
{
false
}
};
#[cfg(feature = "rog_ally")]
if is_rog_ally { if is_rog_ally {
config.notifications.enabled = false; config.notifications.enabled = false;
config.enable_tray_icon = false; config.enable_tray_icon = false;
@@ -145,6 +156,7 @@ async fn main() -> Result<()> {
config.startup_in_background = false; config.startup_in_background = false;
config.start_fullscreen = true; config.start_fullscreen = true;
} }
config.write(); config.write();
let enable_tray_icon = config.enable_tray_icon; let enable_tray_icon = config.enable_tray_icon;
@@ -203,7 +215,10 @@ async fn main() -> Result<()> {
} }
}) })
.ok(); .ok();
} else {
continue;
}
// save as a var, don't hold the lock the entire time or deadlocks happen // save as a var, don't hold the lock the entire time or deadlocks happen
if let Ok(app_state) = app_state.lock() { if let Ok(app_state) = app_state.lock() {
state = *app_state; state = *app_state;
@@ -248,9 +263,8 @@ async fn main() -> Result<()> {
let config = config_copy_2.clone(); let config = config_copy_2.clone();
ui_copy ui_copy
.upgrade_in_event_loop(move |w| { .upgrade_in_event_loop(move |w| {
let fullscreen = config let fullscreen =
.lock() config.lock().is_ok_and(|c| c.start_fullscreen);
.is_ok_and(|c| c.start_fullscreen);
if fullscreen && !w.window().is_fullscreen() { if fullscreen && !w.window().is_fullscreen() {
w.window().set_fullscreen(fullscreen); w.window().set_fullscreen(fullscreen);
} }
@@ -276,7 +290,6 @@ async fn main() -> Result<()> {
} }
} }
} }
}
}); });
slint::run_event_loop_until_quit().unwrap(); slint::run_event_loop_until_quit().unwrap();

View File

@@ -9,15 +9,11 @@ use std::process::Command;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use futures_util::StreamExt;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use notify_rust::{Hint, Notification, Timeout, Urgency}; use notify_rust::{Hint, Notification, Timeout};
use rog_platform::platform::GpuMode;
use rog_platform::power::AsusPower; use rog_platform::power::AsusPower;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use supergfxctl::actions::UserActionRequired as GfxUserAction; use supergfxctl::pci_device::GfxPower;
use supergfxctl::pci_device::{GfxMode, GfxPower};
use supergfxctl::zbus_proxy::DaemonProxy as SuperProxy;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@@ -145,12 +141,8 @@ pub fn start_notifications(
} }
}); });
let enabled_notifications_copy = config.clone(); info!("Attempting to start plain dgpu status monitor");
let no_supergfx = move |e: &zbus::Error| { start_dpu_status_mon(config.clone());
error!("zbus signal: receive_notify_gfx_status: {e}");
warn!("Attempting to start plain dgpu status monitor");
start_dpu_status_mon(enabled_notifications_copy.clone());
};
// GPU MUX Mode notif // GPU MUX Mode notif
// TODO: need to get armoury attrs and iter to find // TODO: need to get armoury attrs and iter to find
@@ -189,95 +181,9 @@ pub fn start_notifications(
// Ok::<(), zbus::Error>(()) // Ok::<(), zbus::Error>(())
// }); // });
let enabled_notifications_copy = config.clone();
// GPU Mode change/action notif
tokio::spawn(async move {
let conn = zbus::Connection::system().await.inspect_err(|e| {
no_supergfx(e);
})?;
let proxy = SuperProxy::builder(&conn).build().await.inspect_err(|e| {
no_supergfx(e);
})?;
let _ = proxy.mode().await.inspect_err(|e| {
no_supergfx(e);
})?;
let proxy_copy = proxy.clone();
let enabled_notifications_copy_action = enabled_notifications_copy.clone();
let mut p = proxy.receive_notify_action().await?;
tokio::spawn(async move {
info!("Started zbus signal thread: receive_notify_action");
while let Some(e) = p.next().await {
if let Ok(out) = e.args() {
// Respect user notification settings for gpu actions
if let Ok(cfg) = enabled_notifications_copy_action.lock() {
if !cfg.notifications.enabled || !cfg.notifications.receive_notify_gfx {
continue;
}
}
let action = out.action();
let mode = convert_gfx_mode(proxy.mode().await.unwrap_or_default());
match action {
supergfxctl::actions::UserActionRequired::Reboot => {
do_mux_notification("Graphics mode change requires reboot", &mode)
}
_ => do_gfx_action_notif(<&str>::from(action), *action, mode),
}
.map_err(|e| {
error!("zbus signal: do_gfx_action_notif: {e}");
e
})
.ok();
}
}
});
let mut p = proxy_copy.receive_notify_gfx_status().await?;
tokio::spawn(async move {
info!("Started zbus signal thread: receive_notify_gfx_status");
let mut last_status = GfxPower::Unknown;
while let Some(e) = p.next().await {
if let Ok(out) = e.args() {
let status = out.status;
if status != GfxPower::Unknown && status != last_status {
if let Ok(config) = enabled_notifications_copy.lock() {
if !config.notifications.receive_notify_gfx_status
|| !config.notifications.enabled
{
continue;
}
}
// Required check because status cycles through
// active/unknown/suspended
do_gpu_status_notif("dGPU status changed:", &status)
.show_async()
.await
.unwrap()
.on_close(|_| ());
}
last_status = status;
}
}
});
Ok::<(), zbus::Error>(())
});
Ok(vec![blocking]) Ok(vec![blocking])
} }
fn convert_gfx_mode(gfx: GfxMode) -> GpuMode {
match gfx {
GfxMode::Hybrid => GpuMode::Optimus,
GfxMode::Integrated => GpuMode::Integrated,
GfxMode::NvidiaNoModeset => GpuMode::Optimus,
GfxMode::Vfio => GpuMode::Vfio,
GfxMode::AsusEgpu => GpuMode::Egpu,
GfxMode::AsusMuxDgpu => GpuMode::Ultimate,
GfxMode::None => GpuMode::Error,
}
}
fn base_notification<T>(message: &str, data: &T) -> Notification fn base_notification<T>(message: &str, data: &T) -> Notification
where where
T: Display, T: Display,
@@ -303,97 +209,3 @@ fn do_gpu_status_notif(message: &str, data: &GfxPower) -> Notification {
notif.icon(icon); notif.icon(icon);
notif notif
} }
fn do_gfx_action_notif(message: &str, action: GfxUserAction, mode: GpuMode) -> Result<()> {
if matches!(action, GfxUserAction::Reboot) {
do_mux_notification("Graphics mode change requires reboot", &mode).ok();
return Ok(());
}
let mut notif = Notification::new();
notif
.appname(NOTIF_HEADER)
.summary(&format!("Changing to {mode}. {message}"))
//.hint(Hint::Resident(true))
.hint(Hint::Category("device".into()))
.urgency(Urgency::Critical)
// For user-action notifications keep them visible if they require interaction
// but for non-interactive actions we prefer they auto-hide like other notifs.
.timeout(Timeout::Milliseconds(6000))
.icon("dialog-warning")
.hint(Hint::Transient(true));
if matches!(action, GfxUserAction::Logout) {
notif.action("gfx-mode-session-action", "Logout");
let handle = notif.show()?;
if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") {
if desktop.to_lowercase() == "gnome" {
handle.wait_for_action(|id| {
if id == "gfx-mode-session-action" {
let mut cmd = Command::new("gnome-session-quit");
cmd.spawn().ok();
} else if id == "__closed" {
// TODO: cancel the switching
}
});
} else if desktop.to_lowercase() == "kde" {
handle.wait_for_action(|id| {
if id == "gfx-mode-session-action" {
let mut cmd = Command::new("qdbus");
cmd.args([
"org.kde.ksmserver", "/KSMServer", "logout", "1", "0", "0",
]);
cmd.spawn().ok();
} else if id == "__closed" {
// TODO: cancel the switching
}
});
} else {
// todo: handle alternatives
}
}
} else {
notif.show()?;
}
Ok(())
}
/// Actual `GpuMode` unused as data is never correct until switched by reboot
fn do_mux_notification(message: &str, m: &GpuMode) -> Result<()> {
let mut notif = base_notification(message, &m.to_string());
notif
.action("gfx-mode-session-action", "Reboot")
.urgency(Urgency::Critical)
.icon("system-reboot-symbolic")
.hint(Hint::Transient(true));
let handle = notif.show()?;
std::thread::spawn(|| {
if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") {
if desktop.to_lowercase() == "gnome" {
handle.wait_for_action(|id| {
if id == "gfx-mode-session-action" {
let mut cmd = Command::new("gnome-session-quit");
cmd.arg("--reboot");
cmd.spawn().ok();
} else if id == "__closed" {
// TODO: cancel the switching
}
});
} else if desktop.to_lowercase() == "kde" {
handle.wait_for_action(|id| {
if id == "gfx-mode-session-action" {
let mut cmd = Command::new("qdbus");
cmd.args([
"org.kde.ksmserver", "/KSMServer", "logout", "1", "1", "0",
]);
cmd.spawn().ok();
} else if id == "__closed" {
// TODO: cancel the switching
}
});
}
}
});
Ok(())
}

View File

@@ -162,7 +162,6 @@ impl CurveData {
/// Write this curve to the device fan specified by `self.fan` /// Write this curve to the device fan specified by `self.fan`
pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> { pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> {
let pwm_num: char = self.fan.into(); let pwm_num: char = self.fan.into();
let enable = if self.enabled { '1' } else { '2' };
for (index, out) in self.pwm.iter().enumerate() { for (index, out) in self.pwm.iter().enumerate() {
let pwm = pwm_str(pwm_num, index); let pwm = pwm_str(pwm_num, index);
@@ -176,10 +175,20 @@ impl CurveData {
device.set_attribute_value(&temp, out.to_string())?; device.set_attribute_value(&temp, out.to_string())?;
} }
// Enable must be done *after* all points are written pwm3_enable // Note: pwm_enable is set by write_profile_curve_to_platform after all
// curves are written, because on some devices (e.g., ASUS Z13 2025)
// setting any pwm_enable to 2 resets ALL fan enables.
Ok(())
}
/// Set the enable state for this fan curve
pub fn set_enable(&self, device: &mut Device) -> std::io::Result<()> {
let pwm_num: char = self.fan.into();
let enable = if self.enabled { "1" } else { "2" };
let enable_attr = format!("pwm{pwm_num}_enable");
device device
.set_attribute_value(format!("pwm{pwm_num}_enable"), enable.to_string()) .set_attribute_value(&enable_attr, enable.to_string())
.map_err(|e| error!("Failed to set pwm{pwm_num}_enable to {enable}: {e:?}")) .map_err(|e| error!("Failed to set {enable_attr} to {enable}: {e:?}"))
.ok(); .ok();
Ok(()) Ok(())
} }

View File

@@ -181,15 +181,29 @@ impl FanCurveProfiles {
PlatformProfile::Quiet | PlatformProfile::LowPower => &mut self.quiet, PlatformProfile::Quiet | PlatformProfile::LowPower => &mut self.quiet,
PlatformProfile::Custom => &mut self.custom, PlatformProfile::Custom => &mut self.custom,
}; };
for fan in fans.iter().filter(|f| !f.enabled) {
debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}"); // First write all curve data (pwm/temp values) for all fans
for fan in fans.iter() {
debug!("write_profile_curve_to_platform: writing curve data for profile:{profile}, {fan:?}");
fan.write_to_device(device)?; fan.write_to_device(device)?;
} }
// Write enabled fans last because the kernel currently resets *all* if one is
// disabled // Then set enables: disabled fans first, then enabled fans last.
// This order is important because on some devices (e.g., ASUS Z13 2025)
// setting any pwm_enable to 2 (disabled) resets ALL fan enables.
for fan in fans.iter().filter(|f| !f.enabled) {
debug!(
"write_profile_curve_to_platform: disabling fan for profile:{profile}, {:?}",
fan.fan
);
fan.set_enable(device)?;
}
for fan in fans.iter().filter(|f| f.enabled) { for fan in fans.iter().filter(|f| f.enabled) {
debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}"); debug!(
fan.write_to_device(device)?; "write_profile_curve_to_platform: enabling fan for profile:{profile}, {:?}",
fan.fan
);
fan.set_enable(device)?;
} }
Ok(()) Ok(())
} }