Compare commits

...

10 Commits

Author SHA1 Message Date
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
11 changed files with 150 additions and 280 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

@@ -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: "",

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,76 +215,77 @@ async fn main() -> Result<()> {
} }
}) })
.ok(); .ok();
} else {
// save as a var, don't hold the lock the entire time or deadlocks happen continue;
if let Ok(app_state) = app_state.lock() { }
state = *app_state;
// save as a var, don't hold the lock the entire time or deadlocks happen
if let Ok(app_state) = app_state.lock() {
state = *app_state;
}
// This sleep is required to give the event loop time to react
sleep(Duration::from_millis(300));
if state == AppState::MainWindowShouldOpen {
if let Ok(mut app_state) = app_state.lock() {
*app_state = AppState::MainWindowOpen;
} }
// This sleep is required to give the event loop time to react let config_copy = config.clone();
sleep(Duration::from_millis(300)); let app_state_copy = app_state.clone();
if state == AppState::MainWindowShouldOpen { slint::invoke_from_event_loop(move || {
if let Ok(mut app_state) = app_state.lock() { UI.with(|ui| {
*app_state = AppState::MainWindowOpen; let app_state_copy = app_state_copy.clone();
} let mut ui = ui.borrow_mut();
if let Some(ui) = ui.as_mut() {
ui.window().show().unwrap();
ui.window().on_close_requested(move || {
if let Ok(mut app_state) = app_state_copy.lock() {
*app_state = AppState::MainWindowClosed;
}
slint::CloseRequestResponse::HideWindow
});
} else {
let config_copy_2 = config_copy.clone();
let newui = setup_window(config_copy);
newui.window().on_close_requested(move || {
if let Ok(mut app_state) = app_state_copy.lock() {
*app_state = AppState::MainWindowClosed;
}
slint::CloseRequestResponse::HideWindow
});
let config_copy = config.clone(); let ui_copy = newui.as_weak();
let app_state_copy = app_state.clone(); newui
slint::invoke_from_event_loop(move || { .window()
UI.with(|ui| { .set_rendering_notifier(move |s, _| {
let app_state_copy = app_state_copy.clone(); if let slint::RenderingState::RenderingSetup = s {
let mut ui = ui.borrow_mut(); let config = config_copy_2.clone();
if let Some(ui) = ui.as_mut() { ui_copy
ui.window().show().unwrap(); .upgrade_in_event_loop(move |w| {
ui.window().on_close_requested(move || { let fullscreen =
if let Ok(mut app_state) = app_state_copy.lock() { config.lock().is_ok_and(|c| c.start_fullscreen);
*app_state = AppState::MainWindowClosed; if fullscreen && !w.window().is_fullscreen() {
w.window().set_fullscreen(fullscreen);
}
})
.ok();
} }
slint::CloseRequestResponse::HideWindow })
}); .ok();
} else { ui.replace(newui);
let config_copy_2 = config_copy.clone();
let newui = setup_window(config_copy);
newui.window().on_close_requested(move || {
if let Ok(mut app_state) = app_state_copy.lock() {
*app_state = AppState::MainWindowClosed;
}
slint::CloseRequestResponse::HideWindow
});
let ui_copy = newui.as_weak();
newui
.window()
.set_rendering_notifier(move |s, _| {
if let slint::RenderingState::RenderingSetup = s {
let config = config_copy_2.clone();
ui_copy
.upgrade_in_event_loop(move |w| {
let fullscreen = config
.lock()
.is_ok_and(|c| c.start_fullscreen);
if fullscreen && !w.window().is_fullscreen() {
w.window().set_fullscreen(fullscreen);
}
})
.ok();
}
})
.ok();
ui.replace(newui);
}
});
})
.unwrap();
} else if state == AppState::QuitApp {
slint::quit_event_loop().unwrap();
exit(0);
} else if state != AppState::MainWindowOpen {
if let Ok(config) = config.lock() {
if !config.run_in_background {
slint::quit_event_loop().unwrap();
exit(0);
} }
});
})
.unwrap();
} else if state == AppState::QuitApp {
slint::quit_event_loop().unwrap();
exit(0);
} else if state != AppState::MainWindowOpen {
if let Ok(config) = config.lock() {
if !config.run_in_background {
slint::quit_event_loop().unwrap();
exit(0);
} }
} }
} }

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(())
}