Compare commits

...

88 Commits

Author SHA1 Message Date
mihai2mn
9cbc2272b5 Merge branch 'main' into 'devel'
feat(rog-control-center): Major UI/UX improvements and new features

See merge request asus-linux/asusctl!243
2026-01-16 19:46:41 +00:00
mihai2mn
f5f997e057 fix(ci): Resolve yaml invalid error by adding explicit stages 2026-01-16 20:43:46 +01: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
mihai2mn
3d0caa39e1 feat(rog-control-center): Major UI/UX improvements and new features
- Add software RGB animations for static-only keyboards (rainbow, color cycle)
- Add custom fan curve control via direct sysfs for unsupported laptops
- Add real-time system status bar (CPU/GPU temps, fan speeds, power draw)
- Add tray icon tooltip with live system stats
- Add power profile change notifications (Fn+F5)
- Add dGPU status notifications
- Add ROG theme with dark palette and accent colors
- Add Screenpad, Slash, and SuperGFX page stubs
- Improve fan curve graph UI
- Various UI refinements and fixes

Co-Authored-By: Gemini <noreply@google.com>
2026-01-15 20:09:40 +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
60c4818381 fix: implement Display instead of ToString 2026-01-14 02:00:59 +01:00
Denis Benato
9cd48dc101 chore: edit CHANGELOG.md 2026-01-14 01:59:03 +01:00
Denis Benato
8d954c16fa fix: cargo clippy --fix 2026-01-14 01:57:48 +01:00
Denis Benato
3665d9cc8e feat: improve the cli interface 2026-01-14 01:56:58 +01:00
Denis Benato
d7ddee246a feat: refactor the cli battery interface 2026-01-14 01:45:56 +01:00
Denis Benato
e85938b34d feat: refactor cli options for leds 2026-01-14 01:32:10 +01:00
Denis Benato
b7d480dd39 fix: help strings 2026-01-14 01:14:53 +01:00
Denis Benato
77640d1637 feat: improve profile cli usage 2026-01-14 01:05:35 +01:00
Denis Benato
32da2a2da0 feat: make easier to use armoury subcommand 2026-01-14 00:31:13 +01:00
Denis Benato
7981a85ff5 feat: improve cmdline 2026-01-14 00:06:34 +01:00
Denis Benato
e3035adf98 chore: change the text in about page 2026-01-13 23:55:52 +01:00
Denis Benato
be17e0b388 feat: remove references to graphic switching 2026-01-13 23:53:23 +01:00
Denis Benato
20cbddb6fa feat: refactor cli interface 2026-01-13 23:50:31 +01:00
Denis Benato
e9c5315bda Feat: Remove supergfxctl notification daemon 2026-01-13 22:05:34 +01:00
Denis Benato
b521a9ffc1 Fix: avoid using deprecated functions 2026-01-13 21:49:14 +01:00
Denis Benato
5e48923db1 Feat: update dependencies 2026-01-13 21:29:58 +01:00
Denis Benato
392436808d Fix text of armoury usage 2026-01-13 20:54:53 +01:00
Denis Benato
3d9a08d7e0 Silence the server 2026-01-13 20:18:49 +01:00
Denis Benato
ff103f98af Chore: change text of an error 2026-01-13 20:14:45 +01:00
Denis Benato
d05182ae64 chore: make the settings save code prettier 2026-01-13 19:29:37 +01:00
Denis Benato
a4957a6eeb Modify changelog and cargo fmt 2026-01-10 13:58:09 +01:00
Denis Benato
dda750cf33 Merge branch 'Support_additional_ga403_2025' into 'devel'
Add slash support for additional ga403 2025 models

See merge request asus-linux/asusctl!238
2026-01-10 12:54:01 +00:00
James Lademann
0b5e04393a Add slash support for additional ga403 2025 models 2026-01-10 12:54:01 +00:00
Denis Benato
c9c9a022a4 Merge branch 'fix/one-shot-charge-persistence' into 'devel'
fix: one-shot charging loses original charge limit after restart

See merge request asus-linux/asusctl!242
2026-01-10 12:51:30 +00:00
bitr8
aa063c20fd fix: persist base_charge_control_end_threshold for one-shot charging
base_charge_control_end_threshold was marked #[serde(skip)] so it only
lived in memory. triggering one-shot charge then restarting asusd would
permanently lose the original charge limit.

- remove #[serde(skip)] so the field gets persisted
- add serde default of 0 for backwards compat (skips restore for
  upgraded configs missing the field)
- add comments explaining Default vs serde default asymmetry
2026-01-05 17:23:59 +11:00
Denis Benato
8551908452 Merge branch 'devel' into 'devel'
slashctl: Add "static" LED profile

See merge request asus-linux/asusctl!240
2025-12-24 00:09:19 +00:00
Denis Benato
5ecb174b8f Merge branch 'main' into 'devel'
feat (rog-aura): Add support to FA617NT and FA617XT and update layout_name on FA617NS and FA617XS models

See merge request asus-linux/asusctl!241
2025-12-23 23:48:32 +00:00
Denis Benato
fa266bff5b Merge branch 'main' into 'devel'
Add G614JU support

See merge request asus-linux/asusctl!239
2025-12-23 23:43:41 +00:00
Seom1177
f53f1f360f Feat: update layout_name for FA617NS and FA617XS models in aura_support.ron 2025-12-22 09:06:06 -05:00
Seom1177
da19216b78 Feat: add support for FA617NT and FA617XT models to aura_support.ron 2025-12-22 09:02:25 -05:00
Some Otters
46efd4190f slashctl: Add "static" LED profile
See https://gitlab.com/asus-linux/reverse-engineering/-/blob/master/slash-bar/packets.txt?ref_type=heads . Tested working on a GU605CR
2025-12-22 10:59:17 +00:00
Hamidreza Khorsand
ed3022e25e Add G614JU support 2025-12-19 01:21:16 +03:30
Denis Benato
7c10d6c6d9 Fix: fans control for LowPower and Quiet 2025-12-18 21:53:42 +01:00
Denis Benato
af5f3a5c71 Fix: memorize last applied brightness settings 2025-12-18 15:07:14 +01:00
Denis Benato
7d5ec5f2c7 Update CHANGELOG.md 2025-12-18 03:40:03 +01:00
Denis Benato
1c8acf6de3 Feat: implement keyboard control for TUFs via asus-armoury 2025-12-18 02:10:45 +01:00
Denis Benato
7b644e7ad6 Merge branch 'main' into 'devel'
fix: add systemd scriptlets to restart asusd on package upgrade

See merge request asus-linux/asusctl!237
2025-12-15 18:12:29 +00:00
Denis Benato
0f02fe868c Fix: re-add G835L 2025-12-15 19:11:09 +01:00
Ali Abdelaal
abd3100e30 Merge upstream main, keep systemd scriptlets 2025-12-15 15:47:18 +00:00
Ali Abdelaal
6f651c2b85 Add systemd scriptlets to restart asusd on upgrade 2025-12-15 15:36:45 +00:00
Denis Benato
93ec5d1bce Feat: improve anime matrix detection 2025-12-14 15:05:10 +01:00
Denis Benato
5aea7f51c0 Chore: fix CHANGELOG.md styling 2025-12-14 15:00:51 +01:00
Denis Benato
d03d8ce67f Release: v6.2.0 2025-12-13 14:41:12 +01:00
Denis Benato
ae3693e0d9 Feat: add aura support for G614F models 2025-12-13 14:17:49 +01:00
Denis Benato
c8b9248eda Chore: update CHANGELOG.md 2025-12-13 13:56:46 +01:00
Denis Benato
22098794fe Fix: ensure upper/lower case doesn't fail the match 2025-12-13 13:48:33 +01:00
Denis Benato
10d49f4fc8 Fix: rever silly change in anime type detection 2025-12-13 13:46:29 +01:00
Denis Benato
5aba2854b0 Feat: add support for GU605C* models 2025-12-13 13:45:55 +01:00
Denis Benato
ea32ad6e0a Feat: add support for FX607V 2025-12-13 13:36:46 +01:00
Denis Benato
ee0e612c04 Feat: add AniMe support for G835LW 2025-12-13 13:35:56 +01:00
Denis Benato
e565ce748a Feat: GA403WR -> GA403W 2025-12-13 13:21:58 +01:00
Denis Benato
7024941663 Chore: fix changelog styling 2025-12-13 13:14:37 +01:00
Denis Benato
817a0165b5 Merge branch 'ga403wr' into 'devel'
add support for ga403wr, zephyrus g14 2025

See merge request asus-linux/asusctl!235
2025-12-13 12:14:19 +00:00
Denis Benato
efcd038f40 Merge branch 'main' into 'devel'
Fix: restore spec file for fedora copr builds

See merge request asus-linux/asusctl!236
2025-12-13 12:06:36 +00:00
Ali
375d99b8fc Fix: restore spec file for fedora copr builds 2025-12-13 12:06:36 +00:00
Ali Abdelaal
3a206eb76f Add asusctl as dependency for rog-gui subpackage 2025-12-11 23:49:24 +00:00
Ali Abdelaal
4449838282 Fix: remove cargo_prep which deletes Cargo.lock 2025-12-11 22:55:49 +00:00
Ali Abdelaal
58d740f77a Fix: use direct cargo build instead of cargo_build macro 2025-12-11 22:52:46 +00:00
Ali Abdelaal
f0488d9750 Build with --locked for reproducible builds 2025-12-11 22:47:00 +00:00
Ali Abdelaal
f1b9ae6f71 Fix: rewrite install section to handle Fedora rpm target dir 2025-12-11 21:48:40 +00:00
Ali Abdelaal
db5de3b854 Fix: add asusctl dir to files, remove duplicate aura_support.ron 2025-12-11 21:17:00 +00:00
Ali Abdelaal
7a3d39b8f1 Add fontconfig BuildRequires for Fedora COPR build 2025-12-11 21:01:16 +00:00
Ali Abdelaal
7a5d6325c0 Restore distro-packaging spec file for Fedora COPR builds 2025-12-11 20:46:03 +00:00
rustysec
574b954866 add support for ga403wr, zephyrus g14 2025 2025-11-29 23:49:24 -08:00
Denis Benato
a811f20f65 Release: v6.1.22 2025-11-29 02:57:34 +01:00
Denis Benato
0c9c263be6 Chore: update CHANGELOG.md 2025-11-29 02:52:27 +01:00
Denis Benato
6571c04bfe Merge branch 'devel' 2025-11-29 02:51:29 +01:00
Denis Benato
e48acbb8a2 Chore: update CHANGELOG.md 2025-11-29 02:51:16 +01:00
Denis Benato
f33496ef68 Merge branch 'main' into 'main'
Support the ROG Strix G18 (2025) G815

See merge request asus-linux/asusctl!234
2025-11-29 01:50:33 +00:00
Denis Benato
cd3176b565 Feat: improve a debug message 2025-11-29 02:48:22 +01:00
Denis Benato
7595613d7e Fix: add EXPERTBOOK to udev rules 2025-11-29 02:00:23 +01:00
Adam Bierman
b8d0245e7a Support G815L 2025-11-27 03:20:26 +00:00
Adam Bierman
54bd2ec800 Update file aura_support.ron 2025-11-27 02:44:30 +00:00
Denis Benato
b6c8566565 Feat: Treat dGPU attributes the same as PPT attributes for power-profile 2025-11-23 17:12:55 +01:00
Denis Benato
052c096014 Fix: use the correct name for nv_tgp 2025-11-23 16:56:47 +01:00
Denis Benato
81cbe3c522 Chore(Makefile): install cargo-vendor-filterer 2025-11-20 14:04:35 +01:00
68 changed files with 4891 additions and 2265 deletions

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@ vendor_*
.~lock.* .~lock.*
*.ods# *.ods#
*.patch *.patch
*.log
# gnome extension # gnome extension
node-modules node-modules

View File

@@ -31,6 +31,7 @@ stages:
- deploy - deploy
format: format:
stage: format
except: except:
- tags - tags
<<: *rust_cache <<: *rust_cache
@@ -42,6 +43,7 @@ format:
- rm -rf "$CI_PROJECT_DIR/ci-target" || true - rm -rf "$CI_PROJECT_DIR/ci-target" || true
check: check:
stage: check
except: except:
- tags - tags
<<: *rust_cache <<: *rust_cache
@@ -55,6 +57,7 @@ check:
- rm -rf "$CI_PROJECT_DIR/ci-target" || true - rm -rf "$CI_PROJECT_DIR/ci-target" || true
test: test:
stage: test
except: except:
- tags - tags
<<: *rust_cache <<: *rust_cache
@@ -65,6 +68,7 @@ test:
- rm -rf "$CI_PROJECT_DIR/ci-target" || true - rm -rf "$CI_PROJECT_DIR/ci-target" || true
release: release:
stage: release
only: only:
- tags - tags
<<: *rust_cache <<: *rust_cache
@@ -90,7 +94,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,6 +1,36 @@
# 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
- Added support for TUF keyboard powerstate control
- Improved AniMe Matrix support thanks to @Seom1177 !
- Fixed a bug with one-shot battery change, thanks @bitr8 !
- Changed the CLI interface of asusctl to be less confusing
- Added support for G835L, thanks to @shevchenko0013 !
## [6.2.0]
### Changed
- Added aura support for FX607V: thanks @jomp16
- Added testing support for G835LW
- Added support for GU605C models slash lighting: thanks @Otters
- Restore fedora: thanks @ali205412
- Add support to G614F models slash lighting
## [6.1.22]
### Changed
- Allow configuration of nv_tgp
- Treat dGPU attributes as power profiles
- Add EXPERTBOOK DMI match to ensure the service is loaded
- Support G815L thanks to @solost !
## [6.1.21] ## [6.1.21]

895
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,12 @@
[workspace.package] [workspace.package]
version = "6.1.21" 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"
@@ -37,6 +40,9 @@ tokio = { version = "^1.39.0", default-features = false, features = [
"time", "time",
"rt", "rt",
"rt-multi-thread", "rt-multi-thread",
"fs",
"io-util",
"io-util",
] } ] }
concat-idents = "^1.1" concat-idents = "^1.1"
dirs = "^4.0" dirs = "^4.0"
@@ -44,7 +50,7 @@ smol = "^2.0"
mio = "0.8.11" mio = "0.8.11"
futures-util = "0.3.31" futures-util = "0.3.31"
zbus = "5.5.0" zbus = "5.13.1"
logind-zbus = { version = "5.2.0" } #, default-features = false, features = ["non_blocking"] } logind-zbus = { version = "5.2.0" } #, default-features = false, features = ["non_blocking"] }
serde = { version = "^1.0", features = ["serde_derive"] } serde = { version = "^1.0", features = ["serde_derive"] }
@@ -57,12 +63,12 @@ glam = { version = "^0.22", features = ["serde"] }
gumdrop = "^0.8" gumdrop = "^0.8"
udev = { version = "^0.8", features = ["mio"] } udev = { version = "^0.8", features = ["mio"] }
rusb = "^0.9" rusb = "^0.9"
inotify = "^0.10.0" inotify = "^0.10"
png_pong = "^0.8" png_pong = "^0.8"
pix = "^0.13" pix = "^0.13"
tinybmp = "^0.4.0" tinybmp = "^0.4"
gif = "^0.12.0" gif = "^0.12"
versions = "6.2" versions = "6.2"

View File

@@ -158,6 +158,8 @@ vendor:
mv .cargo/config ./cargo-config mv .cargo/config ./cargo-config
rm -rf .cargo rm -rf .cargo
rm -rf vendor rm -rf vendor
# Ensure cargo-vendor-filterer is installed (CI installs it already)
command -v cargo-vendor-filterer >/dev/null 2>&1 || cargo install --locked cargo-vendor-filterer
cargo vendor-filterer --all-features --platform x86_64-unknown-linux-gnu vendor cargo vendor-filterer --all-features --platform x86_64-unknown-linux-gnu vendor
tar pcfJ vendor_asusctl_$(VERSION).tar.xz vendor tar pcfJ vendor_asusctl_$(VERSION).tar.xz vendor
rm -rf vendor rm -rf vendor

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

@@ -24,6 +24,7 @@ env_logger.workspace = true
ron.workspace = true ron.workspace = true
gumdrop.workspace = true gumdrop.workspace = true
zbus.workspace = true zbus.workspace = true
argh = "0.1"
[dev-dependencies] [dev-dependencies]
rog_dbus = { path = "../rog-dbus" } rog_dbus = { path = "../rog-dbus" }

View File

@@ -1,154 +1,151 @@
use gumdrop::Options; use argh::FromArgs;
use rog_anime::usb::{AnimAwake, AnimBooting, AnimShutdown, AnimSleeping, Brightness}; use rog_anime::usb::{AnimAwake, AnimBooting, AnimShutdown, AnimSleeping};
use rog_anime::AnimeType; use rog_anime::AnimeType;
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(subcommand, name = "anime", description = "anime commands")]
pub struct AnimeCommand { pub struct AnimeCommand {
#[options(help = "print help message")] #[argh(option, description = "override the display type")]
pub help: bool,
#[options(meta = "", help = "override the display type")]
pub override_type: Option<AnimeType>, pub override_type: Option<AnimeType>,
#[options(meta = "", help = "enable/disable the display")] #[argh(option, description = "enable/disable the display")]
pub enable_display: Option<bool>, pub enable_display: Option<bool>,
#[options(meta = "", help = "enable/disable the builtin run/powersave animation")] #[argh(
pub enable_powersave_anim: Option<bool>, option,
#[options( description = "enable/disable the builtin run/powersave animation"
meta = "",
help = "set global base brightness value <Off, Low, Med, High>"
)] )]
pub brightness: Option<Brightness>, pub enable_powersave_anim: Option<bool>,
#[options(help = "clear the display")] #[argh(
option,
description = "set global base brightness value <off, low, med, high>"
)]
pub brightness: Option<rog_anime::usb::Brightness>,
#[argh(switch, description = "clear the display")]
pub clear: bool, pub clear: bool,
#[options( #[argh(
no_short, option,
meta = "", description = "turn the anime off when external power is unplugged"
help = "turn the anime off when external power is unplugged"
)] )]
pub off_when_unplugged: Option<bool>, pub off_when_unplugged: Option<bool>,
#[options( #[argh(option, description = "turn the anime off when the laptop suspends")]
no_short,
meta = "",
help = "turn the anime off when the laptop suspends"
)]
pub off_when_suspended: Option<bool>, pub off_when_suspended: Option<bool>,
#[options( #[argh(option, description = "turn the anime off when the lid is closed")]
no_short,
meta = "",
help = "turn the anime off when the lid is closed"
)]
pub off_when_lid_closed: Option<bool>, pub off_when_lid_closed: Option<bool>,
#[options(no_short, meta = "", help = "Off with his head!!!")] #[argh(option, description = "off with his head!!!")]
pub off_with_his_head: Option<bool>, pub off_with_his_head: Option<bool>,
#[options(command)] #[argh(subcommand)]
pub command: Option<AnimeActions>, pub command: Option<AnimeActions>,
} }
#[derive(Options)] /// Anime subcommands (image, gif, builtins, etc.)
#[derive(FromArgs, Debug)]
#[argh(subcommand)]
pub enum AnimeActions { pub enum AnimeActions {
#[options(help = "display a PNG image")]
Image(AnimeImage), Image(AnimeImage),
#[options(help = "display a diagonal/pixel-perfect PNG")]
PixelImage(AnimeImageDiagonal), PixelImage(AnimeImageDiagonal),
#[options(help = "display an animated GIF")]
Gif(AnimeGif), Gif(AnimeGif),
#[options(help = "display an animated diagonal/pixel-perfect GIF")]
PixelGif(AnimeGifDiagonal), PixelGif(AnimeGifDiagonal),
#[options(help = "change which builtin animations are shown")]
SetBuiltins(Builtins), SetBuiltins(Builtins),
} }
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(
subcommand,
name = "set-builtins",
description = "change which builtin animations are shown"
)]
pub struct Builtins { pub struct Builtins {
#[options(help = "print help message")] #[argh(
pub help: bool, option,
#[options( description = "default is used if unspecified, <default:GlitchConstruction, StaticEmergence>"
meta = "",
help = "Default is used if unspecified, <default:GlitchConstruction, StaticEmergence>"
)] )]
pub boot: AnimBooting, pub boot: AnimBooting,
#[options( #[argh(
meta = "", option,
help = "Default is used if unspecified, <default:BinaryBannerScroll, RogLogoGlitch>" description = "default is used if unspecified, <default:BinaryBannerScroll, RogLogoGlitch>"
)] )]
pub awake: AnimAwake, pub awake: AnimAwake,
#[options( #[argh(
meta = "", option,
help = "Default is used if unspecified, <default:BannerSwipe, Starfield>" description = "default is used if unspecified, <default:BannerSwipe, Starfield>"
)] )]
pub sleep: AnimSleeping, pub sleep: AnimSleeping,
#[options( #[argh(
meta = "", option,
help = "Default is used if unspecified, <default:GlitchOut, SeeYa>" description = "default is used if unspecified, <default:GlitchOut, SeeYa>"
)] )]
pub shutdown: AnimShutdown, pub shutdown: AnimShutdown,
#[options(meta = "", help = "set/apply the animations <true/false>")] #[argh(option, description = "set/apply the animations <true/false>")]
pub set: Option<bool>, pub set: Option<bool>,
} }
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(subcommand, name = "image", description = "display a PNG image")]
pub struct AnimeImage { pub struct AnimeImage {
#[options(help = "print help message")] #[argh(option, description = "full path to the png to display")]
pub help: bool,
#[options(meta = "", help = "full path to the png to display")]
pub path: String, pub path: String,
#[options(meta = "", default = "1.0", help = "scale 1.0 == normal")] #[argh(option, default = "1.0", description = "scale 1.0 == normal")]
pub scale: f32, pub scale: f32,
#[options(meta = "", default = "0.0", help = "x position (float)")] #[argh(option, default = "0.0", description = "x position (float)")]
pub x_pos: f32, pub x_pos: f32,
#[options(meta = "", default = "0.0", help = "y position (float)")] #[argh(option, default = "0.0", description = "y position (float)")]
pub y_pos: f32, pub y_pos: f32,
#[options(meta = "", default = "0.0", help = "the angle in radians")] #[argh(option, default = "0.0", description = "the angle in radians")]
pub angle: f32, pub angle: f32,
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")] #[argh(option, default = "1.0", description = "brightness 0.0-1.0")]
pub bright: f32, pub bright: f32,
} }
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(
subcommand,
name = "pixel-image",
description = "display a diagonal/pixel-perfect PNG"
)]
pub struct AnimeImageDiagonal { pub struct AnimeImageDiagonal {
#[options(help = "print help message")] #[argh(option, description = "full path to the png to display")]
pub help: bool,
#[options(meta = "", help = "full path to the png to display")]
pub path: String, pub path: String,
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")] #[argh(option, default = "1.0", description = "brightness 0.0-1.0")]
pub bright: f32, pub bright: f32,
} }
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(subcommand, name = "gif", description = "display an animated GIF")]
pub struct AnimeGif { pub struct AnimeGif {
#[options(help = "print help message")] #[argh(option, description = "full path to the gif to display")]
pub help: bool,
#[options(meta = "", help = "full path to the png to display")]
pub path: String, pub path: String,
#[options(meta = "", default = "1.0", help = "scale 1.0 == normal")] #[argh(option, default = "1.0", description = "scale 1.0 == normal")]
pub scale: f32, pub scale: f32,
#[options(meta = "", default = "0.0", help = "x position (float)")] #[argh(option, default = "0.0", description = "x position (float)")]
pub x_pos: f32, pub x_pos: f32,
#[options(meta = "", default = "0.0", help = "y position (float)")] #[argh(option, default = "0.0", description = "y position (float)")]
pub y_pos: f32, pub y_pos: f32,
#[options(meta = "", default = "0.0", help = "the angle in radians")] #[argh(option, default = "0.0", description = "the angle in radians")]
pub angle: f32, pub angle: f32,
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")] #[argh(option, default = "1.0", description = "brightness 0.0-1.0")]
pub bright: f32, pub bright: f32,
#[options( #[argh(
meta = "", option,
default = "1", default = "1",
help = "how many loops to play - 0 is infinite" description = "how many loops to play - 0 is infinite"
)] )]
pub loops: u32, pub loops: u32,
} }
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(
subcommand,
name = "pixel-gif",
description = "display an animated diagonal/pixel-perfect GIF"
)]
pub struct AnimeGifDiagonal { pub struct AnimeGifDiagonal {
#[options(help = "print help message")] #[argh(option, description = "full path to the gif to display")]
pub help: bool,
#[options(meta = "", help = "full path to the png to display")]
pub path: String, pub path: String,
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")] #[argh(option, default = "1.0", description = "brightness 0.0-1.0")]
pub bright: f32, pub bright: f32,
#[options( #[argh(
meta = "", option,
default = "1", default = "1",
help = "how many loops to play - 0 is infinite" description = "how many loops to play - 0 is infinite"
)] )]
pub loops: u32, pub loops: u32,
} }

View File

@@ -1,68 +1,67 @@
use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use gumdrop::Options; use argh::FromArgs;
use rog_aura::error::Error; use rog_aura::error::Error;
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed}; use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed};
#[derive(Options, Debug)] #[derive(FromArgs, Debug, Clone)]
#[argh(
subcommand,
name = "aura-power-old",
description = "aura power (old ROGs and TUF laptops)"
)]
pub struct LedPowerCommand1 { pub struct LedPowerCommand1 {
#[options(help = "print help message")] #[argh(
pub help: bool, option,
#[options(meta = "", help = "Control if LEDs enabled while awake <true/false>")] description = "control if LEDs enabled while awake <true/false>"
)]
pub awake: Option<bool>, pub awake: Option<bool>,
#[options(help = "Use with awake option, if excluded defaults to false")]
#[argh(
switch,
description = "use with awake option; if excluded defaults to false"
)]
pub keyboard: bool, pub keyboard: bool,
#[options(help = "Use with awake option, if excluded defaults to false")]
#[argh(
switch,
description = "use with awake option; if excluded defaults to false"
)]
pub lightbar: bool, pub lightbar: bool,
#[options(meta = "", help = "Control boot animations <true/false>")]
#[argh(option, description = "control boot animations <true/false>")]
pub boot: Option<bool>, pub boot: Option<bool>,
#[options(meta = "", help = "Control suspend animations <true/false>")]
#[argh(option, description = "control suspend animations <true/false>")]
pub sleep: Option<bool>, pub sleep: Option<bool>,
} }
#[derive(Options, Debug)] #[derive(FromArgs, Debug, Clone)]
#[argh(subcommand, name = "aura-power", description = "aura power")]
pub struct LedPowerCommand2 { pub struct LedPowerCommand2 {
#[options(help = "print help message")] #[argh(subcommand)]
pub help: bool,
#[options(command)]
pub command: Option<SetAuraZoneEnabled>, pub command: Option<SetAuraZoneEnabled>,
} }
#[derive(Options, Debug)] /// Subcommands to enable/disable specific aura zones
#[derive(FromArgs, Debug, Clone)]
#[argh(subcommand)]
pub enum SetAuraZoneEnabled { pub enum SetAuraZoneEnabled {
/// Applies to both old and new models Keyboard(KeyboardPower),
#[options(help = "")] Logo(LogoPower),
Keyboard(AuraPowerStates), Lightbar(LightbarPower),
#[options(help = "")] Lid(LidPower),
Logo(AuraPowerStates), RearGlow(RearGlowPower),
#[options(help = "")] Ally(AllyPower),
Lightbar(AuraPowerStates),
#[options(help = "")]
Lid(AuraPowerStates),
#[options(help = "")]
RearGlow(AuraPowerStates),
#[options(help = "")]
Ally(AuraPowerStates),
} }
#[derive(Debug, Clone, Options)] /// Keyboard brightness argument helper
pub struct AuraPowerStates { #[derive(Debug, Clone)]
#[options(help = "print help message")]
pub help: bool,
#[options(help = "defaults to false if option unused")]
pub boot: bool,
#[options(help = "defaults to false if option unused")]
pub awake: bool,
#[options(help = "defaults to false if option unused")]
pub sleep: bool,
#[options(help = "defaults to false if option unused")]
pub shutdown: bool,
}
#[derive(Options)]
pub struct LedBrightness { pub struct LedBrightness {
level: Option<u8>, level: Option<u8>,
} }
impl LedBrightness { impl LedBrightness {
pub fn new(level: Option<u8>) -> Self { pub fn new(level: Option<u8>) -> Self {
LedBrightness { level } LedBrightness { level }
@@ -72,176 +71,302 @@ impl LedBrightness {
self.level self.level
} }
} }
impl FromStr for LedBrightness { impl FromStr for LedBrightness {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase(); let s = s.to_lowercase();
match s.as_str() { match s.as_str() {
"off" => Ok(LedBrightness { level: Some(0x00) }), "off" => Ok(Self::new(Some(0x00))),
"low" => Ok(LedBrightness { level: Some(0x01) }), "low" => Ok(Self::new(Some(0x01))),
"med" => Ok(LedBrightness { level: Some(0x02) }), "med" => Ok(Self::new(Some(0x02))),
"high" => Ok(LedBrightness { level: Some(0x03) }), "high" => Ok(Self::new(Some(0x03))),
_ => { _ => Err(Error::ParseBrightness),
print!("Invalid argument, must be one of: off, low, med, high");
Err(Error::ParseBrightness)
}
} }
} }
} }
#[allow(clippy::to_string_trait_impl)]
impl ToString for LedBrightness { impl fmt::Display for LedBrightness {
fn to_string(&self) -> String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self.level { let s = match self.level {
Some(0x00) => "low", Some(0x00) => "off",
Some(0x01) => "med", Some(0x01) => "low",
Some(0x02) => "high", Some(0x02) => "med",
Some(0x03) => "high",
_ => "unknown", _ => "unknown",
}; };
s.to_owned() write!(f, "{}", s)
} }
} }
#[derive(Debug, Clone, Options, Default)] #[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "keyboard",
description = "set power states for keyboard zone"
)]
pub struct KeyboardPower {
#[argh(switch, description = "defaults to false if option unused")]
pub boot: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub awake: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub sleep: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub shutdown: bool,
}
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "logo",
description = "set power states for logo zone"
)]
pub struct LogoPower {
#[argh(switch, description = "defaults to false if option unused")]
pub boot: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub awake: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub sleep: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub shutdown: bool,
}
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "lightbar",
description = "set power states for lightbar zone"
)]
pub struct LightbarPower {
#[argh(switch, description = "enable power while device is booting")]
pub boot: bool,
#[argh(switch, description = "enable power while device is awake")]
pub awake: bool,
#[argh(switch, description = "enable power while device is sleeping")]
pub sleep: bool,
#[argh(
switch,
description = "enable power while device is shutting down or hibernating"
)]
pub shutdown: bool,
}
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "lid",
description = "set power states for lid zone"
)]
pub struct LidPower {
#[argh(switch, description = "defaults to false if option unused")]
pub boot: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub awake: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub sleep: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub shutdown: bool,
}
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "rear-glow",
description = "set power states for rear glow zone"
)]
pub struct RearGlowPower {
#[argh(switch, description = "defaults to false if option unused")]
pub boot: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub awake: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub sleep: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub shutdown: bool,
}
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "ally",
description = "set power states for ally zone"
)]
pub struct AllyPower {
#[argh(switch, description = "defaults to false if option unused")]
pub boot: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub awake: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub sleep: bool,
#[argh(switch, description = "defaults to false if option unused")]
pub shutdown: bool,
}
/// Single speed-based effect
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "rainbow-cycle",
description = "single speed-based effect"
)]
pub struct SingleSpeed { pub struct SingleSpeed {
#[options(help = "print help message")] #[argh(option, description = "set the speed: low, med, high")]
help: bool,
#[options(no_long, meta = "WORD", help = "set the speed: low, med, high")]
pub speed: Speed, pub speed: Speed,
#[options(
no_long, #[argh(
meta = "", option,
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left" default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)] )]
pub zone: AuraZone, pub zone: AuraZone,
} }
#[derive(Debug, Clone, Options, Default)] /// Single speed effect with direction
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "rainbow-wave",
description = "single speed effect with direction"
)]
pub struct SingleSpeedDirection { pub struct SingleSpeedDirection {
#[options(help = "print help message")] #[argh(option, description = "set the direction: up, down, left, right")]
help: bool,
#[options(no_long, meta = "", help = "set the direction: up, down, left, right")]
pub direction: Direction, pub direction: Direction,
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
#[argh(option, description = "set the speed: low, med, high")]
pub speed: Speed, pub speed: Speed,
#[options(
no_long, #[argh(
meta = "", option,
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left" default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)] )]
pub zone: AuraZone, pub zone: AuraZone,
} }
#[derive(Debug, Clone, Default, Options)] /// Static single-colour effect
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "static",
description = "static single-colour effect"
)]
pub struct SingleColour { pub struct SingleColour {
#[options(help = "print help message")] #[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
help: bool,
#[options(no_long, meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour: Colour, pub colour: Colour,
#[options(
no_long, #[argh(
meta = "", option,
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left" default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)] )]
pub zone: AuraZone, pub zone: AuraZone,
} }
#[derive(Debug, Clone, Default, Options)] /// Single-colour effect with speed
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "highlight",
description = "single-colour effect with speed"
)]
pub struct SingleColourSpeed { pub struct SingleColourSpeed {
#[options(help = "print help message")] #[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
help: bool,
#[options(no_long, meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour: Colour, pub colour: Colour,
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
#[argh(option, description = "set the speed: low, med, high")]
pub speed: Speed, pub speed: Speed,
#[options(
no_long, #[argh(
meta = "", option,
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left" default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)] )]
pub zone: AuraZone, pub zone: AuraZone,
} }
#[derive(Debug, Clone, Options, Default)] /// Two-colour breathing effect
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "breathe",
description = "two-colour breathing effect"
)]
pub struct TwoColourSpeed { pub struct TwoColourSpeed {
#[options(help = "print help message")] #[argh(option, description = "set the first RGB value e.g. ff00ff")]
help: bool,
#[options(no_long, meta = "", help = "set the first RGB value e.g, ff00ff")]
pub colour: Colour, pub colour: Colour,
#[options(no_long, meta = "", help = "set the second RGB value e.g, ff00ff")]
#[argh(option, description = "set the second RGB value e.g. ff00ff")]
pub colour2: Colour, pub colour2: Colour,
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
#[argh(option, description = "set the speed: low, med, high")]
pub speed: Speed, pub speed: Speed,
#[options(
no_long, #[argh(
meta = "", option,
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left" default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)] )]
pub zone: AuraZone, pub zone: AuraZone,
} }
#[derive(Debug, Clone, Default, Options)] /// Multi-zone colour settings
#[derive(FromArgs, Debug, Clone, Default)]
#[allow(dead_code)] #[allow(dead_code)]
#[argh(description = "multi-zone colour settings")]
pub struct MultiZone { pub struct MultiZone {
#[options(help = "print help message")] #[argh(option, short = 'a', description = "set the RGB value e.g. ff00ff")]
help: bool,
#[options(short = "a", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour1: Colour, pub colour1: Colour,
#[options(short = "b", meta = "", help = "set the RGB value e.g, ff00ff")]
#[argh(option, short = 'b', description = "set the RGB value e.g. ff00ff")]
pub colour2: Colour, pub colour2: Colour,
#[options(short = "c", meta = "", help = "set the RGB value e.g, ff00ff")]
#[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
pub colour3: Colour, pub colour3: Colour,
#[options(short = "d", meta = "", help = "set the RGB value e.g, ff00ff")]
#[argh(option, short = 'd', description = "set the RGB value e.g. ff00ff")]
pub colour4: Colour, pub colour4: Colour,
} }
#[derive(Debug, Clone, Default, Options)] /// Multi-colour with speed
#[derive(FromArgs, Debug, Clone, Default)]
#[allow(dead_code)] #[allow(dead_code)]
#[argh(description = "multi-colour with speed")]
pub struct MultiColourSpeed { pub struct MultiColourSpeed {
#[options(help = "print help message")] #[argh(option, short = 'a', description = "set the RGB value e.g. ff00ff")]
help: bool,
#[options(short = "a", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour1: Colour, pub colour1: Colour,
#[options(short = "b", meta = "", help = "set the RGB value e.g, ff00ff")]
#[argh(option, short = 'b', description = "set the RGB value e.g. ff00ff")]
pub colour2: Colour, pub colour2: Colour,
#[options(short = "c", meta = "", help = "set the RGB value e.g, ff00ff")]
#[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
pub colour3: Colour, pub colour3: Colour,
#[options(short = "d", meta = "", help = "set the RGB value e.g, ff00ff")]
#[argh(option, short = 'd', description = "set the RGB value e.g. ff00ff")]
pub colour4: Colour, pub colour4: Colour,
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
#[argh(option, description = "set the speed: low, med, high")]
pub speed: Speed, pub speed: Speed,
} }
/// Byte value for setting the built-in mode. /// Builtin aura effects
/// #[derive(FromArgs, Debug)]
/// Enum corresponds to the required integer value #[argh(subcommand)]
// NOTE: The option names here must match those in rog-aura crate
#[derive(Options)]
pub enum SetAuraBuiltin { pub enum SetAuraBuiltin {
#[options(help = "set a single static colour")] Static(SingleColour), // 0
Static(SingleColour), // 0 Breathe(TwoColourSpeed), // 1
#[options(help = "pulse between one or two colours")] RainbowCycle(SingleSpeed), // 2
Breathe(TwoColourSpeed), // 1
#[options(help = "strobe through all colours")]
RainbowCycle(SingleSpeed), // 2
#[options(help = "rainbow cycling in one of four directions")]
RainbowWave(SingleSpeedDirection), // 3 RainbowWave(SingleSpeedDirection), // 3
#[options(help = "rain pattern mimicking raindrops")] Stars(TwoColourSpeed), // 4
Stars(TwoColourSpeed), // 4 Rain(SingleSpeed), // 5
#[options(help = "rain pattern of three preset colours")] Highlight(SingleColourSpeed), // 6
Rain(SingleSpeed), // 5 Laser(SingleColourSpeed), // 7
#[options(help = "pressed keys are highlighted to fade")] Ripple(SingleColourSpeed), // 8
Highlight(SingleColourSpeed), // 6 Pulse(SingleColour), // 10
#[options(help = "pressed keys generate horizontal laser")] Comet(SingleColour), // 11
Laser(SingleColourSpeed), // 7 Flash(SingleColour), // 12
#[options(help = "pressed keys ripple outwards like a splash")]
Ripple(SingleColourSpeed), // 8
#[options(help = "set a rapid pulse")]
Pulse(SingleColour), // 10
#[options(help = "set a vertical line zooming from left")]
Comet(SingleColour), // 11
#[options(help = "set a wide vertical line zooming from left")]
Flash(SingleColour), // 12
} }
impl Default for SetAuraBuiltin { impl Default for SetAuraBuiltin {

View File

@@ -1,4 +1,4 @@
use gumdrop::Options; use argh::FromArgs;
use rog_platform::platform::PlatformProfile; use rog_platform::platform::PlatformProfile;
use crate::anime_cli::AnimeCommand; use crate::anime_cli::AnimeCommand;
@@ -7,128 +7,308 @@ use crate::fan_curve_cli::FanCurveCommand;
use crate::scsi_cli::ScsiCommand; use crate::scsi_cli::ScsiCommand;
use crate::slash_cli::SlashCommand; use crate::slash_cli::SlashCommand;
#[derive(Default, Options)] #[derive(FromArgs, Default, Debug)]
/// asusctl command-line options
pub struct CliStart { pub struct CliStart {
#[options(help_flag, help = "print help message")] #[argh(subcommand)]
pub help: bool, pub command: CliCommand,
#[options(help = "show program version number")]
pub version: bool,
#[options(help = "show supported functions of this laptop")]
pub show_supported: bool,
#[options(meta = "", help = "<off, low, med, high>")]
pub kbd_bright: Option<LedBrightness>,
#[options(help = "Toggle to next keyboard brightness")]
pub next_kbd_bright: bool,
#[options(help = "Toggle to previous keyboard brightness")]
pub prev_kbd_bright: bool,
#[options(meta = "", help = "Set your battery charge limit <20-100>")]
pub chg_limit: Option<u8>,
#[options(help = "Toggle one-shot battery charge to 100%")]
pub one_shot_chg: bool,
#[options(command)]
pub command: Option<CliCommand>,
} }
#[derive(Options)] /// Top-level subcommands for asusctl
#[derive(FromArgs, Debug)]
#[argh(subcommand)]
pub enum CliCommand { pub enum CliCommand {
#[options(help = "Set the keyboard lighting from built-in modes")]
Aura(LedModeCommand), Aura(LedModeCommand),
#[options(help = "Set the LED power states")]
AuraPowerOld(LedPowerCommand1), AuraPowerOld(LedPowerCommand1),
#[options(help = "Set the LED power states")]
AuraPower(LedPowerCommand2), AuraPower(LedPowerCommand2),
#[options(help = "Set or select platform_profile")] Brightness(BrightnessCommand),
Profile(ProfileCommand), Profile(ProfileCommand),
#[options(help = "Set, select, or modify fan curves if supported")]
FanCurve(FanCurveCommand), FanCurve(FanCurveCommand),
#[options(help = "Set the graphics mode (obsoleted by supergfxctl)")]
Graphics(GraphicsCommand),
#[options(name = "anime", help = "Manage AniMe Matrix")]
Anime(AnimeCommand), Anime(AnimeCommand),
#[options(name = "slash", help = "Manage Slash Ledbar")]
Slash(SlashCommand), Slash(SlashCommand),
#[options(name = "scsi", help = "Manage SCSI external drive")]
Scsi(ScsiCommand), Scsi(ScsiCommand),
#[options(
help = "Change platform settings. This is a new interface exposed by the asus-armoury \
driver, some of the settings will be the same as the older platform interface"
)]
Armoury(ArmouryCommand), Armoury(ArmouryCommand),
#[options(name = "backlight", help = "Set screen backlight levels")]
Backlight(BacklightCommand), Backlight(BacklightCommand),
Battery(BatteryCommand),
Info(InfoCommand),
} }
#[derive(Debug, Clone, Options)] impl Default for CliCommand {
fn default() -> Self {
CliCommand::Info(InfoCommand::default())
}
}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "profile", description = "profile management")]
pub struct ProfileCommand { pub struct ProfileCommand {
#[options(help = "print help message")] #[argh(subcommand)]
pub help: bool, pub command: ProfileSubCommand,
#[options(help = "toggle to next profile in list")]
pub next: bool,
#[options(help = "list available profiles")]
pub list: bool,
#[options(help = "get profile")]
pub profile_get: bool,
#[options(meta = "", help = "set the active profile")]
pub profile_set: Option<PlatformProfile>,
#[options(short = "a", meta = "", help = "set the profile to use on AC power")]
pub profile_set_ac: Option<PlatformProfile>,
#[options(
short = "b",
meta = "",
help = "set the profile to use on battery power"
)]
pub profile_set_bat: Option<PlatformProfile>,
} }
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(subcommand)]
pub enum ProfileSubCommand {
Next(ProfileNextCommand),
List(ProfileListCommand),
Get(ProfileGetCommand),
Set(ProfileSetCommand),
}
impl Default for ProfileSubCommand {
fn default() -> Self {
ProfileSubCommand::List(ProfileListCommand::default())
}
}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "next",
description = "toggle to next profile in list"
)]
pub struct ProfileNextCommand {}
#[derive(FromArgs, Debug, Default)]
#[argh(subcommand, name = "list", description = "list available profiles")]
pub struct ProfileListCommand {}
#[derive(FromArgs, Debug, Default)]
#[argh(subcommand, name = "get", description = "get profile")]
pub struct ProfileGetCommand {}
#[derive(FromArgs, Debug, Default)]
#[argh(subcommand, name = "set", description = "set profile")]
pub struct ProfileSetCommand {
#[argh(positional, description = "profile to set")]
pub profile: PlatformProfile,
#[argh(
switch,
short = 'a',
description = "set the profile to use on AC power"
)]
pub ac: bool,
#[argh(
switch,
short = 'b',
description = "set the profile to use on battery power"
)]
pub battery: bool,
}
#[derive(FromArgs, Debug, Default)]
#[argh(subcommand, name = "aura", description = "led mode commands")]
pub struct LedModeCommand { pub struct LedModeCommand {
#[options(help = "print help message")] #[argh(switch, description = "switch to next aura mode")]
pub help: bool,
#[options(help = "switch to next aura mode")]
pub next_mode: bool, pub next_mode: bool,
#[options(help = "switch to previous aura mode")]
#[argh(switch, description = "switch to previous aura mode")]
pub prev_mode: bool, pub prev_mode: bool,
#[options(command)]
#[argh(subcommand)]
pub command: Option<SetAuraBuiltin>, pub command: Option<SetAuraBuiltin>,
} }
#[derive(Options)] #[derive(FromArgs, Debug, Default)]
pub struct GraphicsCommand { #[argh(
#[options(help = "print help message")] subcommand,
pub help: bool, name = "armoury",
} description = "armoury / firmware attributes"
)]
#[derive(Options, Debug)]
pub struct ArmouryCommand { pub struct ArmouryCommand {
#[options(help = "print help message")] #[argh(subcommand)]
pub help: bool, pub command: ArmourySubCommand,
#[options(
free,
help = "append each value name followed by the value to set. `-1` sets to default"
)]
pub free: Vec<String>,
} }
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(subcommand)]
pub enum ArmourySubCommand {
Set(ArmouryPropertySetCommand),
Get(ArmouryPropertyGetCommand),
List(ArmouryPropertyListCommand),
}
impl Default for ArmourySubCommand {
fn default() -> Self {
ArmourySubCommand::List(ArmouryPropertyListCommand::default())
}
}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "set",
description = "set an asus-armoury firmware-attribute"
)]
pub struct ArmouryPropertySetCommand {
#[argh(
positional,
description = "name of the attribute to set (see asus-armoury list for available properties)"
)]
pub property: String,
#[argh(positional, description = "value to set for the given attribute")]
pub value: i32,
}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "list",
description = "list all firmware-attributes supported by asus-armoury"
)]
pub struct ArmouryPropertyListCommand {}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "get",
description = "get a firmware-attribute from asus-armoury"
)]
pub struct ArmouryPropertyGetCommand {
#[argh(
positional,
description = "name of the property to get (see asus-armoury list for available properties)"
)]
pub property: String,
}
#[derive(FromArgs, Debug, Default)]
#[argh(subcommand, name = "backlight", description = "backlight options")]
pub struct BacklightCommand { pub struct BacklightCommand {
#[options(help = "print help message")] #[argh(option, description = "set screen brightness <0-100>")]
pub help: bool,
#[options(meta = "", help = "Set screen brightness <0-100>")]
pub screenpad_brightness: Option<i32>, pub screenpad_brightness: Option<i32>,
#[options(
meta = "", #[argh(
help = "Set screenpad gamma brightness 0.5 - 2.2, 1.0 == linear" option,
description = "set screenpad gamma brightness 0.5 - 2.2, 1.0 == linear"
)] )]
pub screenpad_gamma: Option<f32>, pub screenpad_gamma: Option<f32>,
#[options(
meta = "", #[argh(
help = "Set screenpad brightness to sync with primary display" option,
description = "set screenpad brightness to sync with primary display"
)] )]
pub sync_screenpad_brightness: Option<bool>, pub sync_screenpad_brightness: Option<bool>,
} }
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "battery", description = "battery options")]
pub struct BatteryCommand {
#[argh(subcommand)]
pub command: BatterySubCommand,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand)]
pub enum BatterySubCommand {
Limit(BatteryLimitCommand),
OneShot(BatteryOneShotCommand),
Info(BatteryInfoCommand),
}
impl Default for BatterySubCommand {
fn default() -> Self {
BatterySubCommand::OneShot(BatteryOneShotCommand::default())
}
}
#[derive(FromArgs, Debug)]
#[argh(
subcommand,
name = "limit",
description = "set battery charge limit <20-100>"
)]
pub struct BatteryLimitCommand {
#[argh(positional, description = "charge limit percentage 20-100")]
pub limit: u8,
}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "oneshot",
description = "one-shot full charge (optional percent)"
)]
pub struct BatteryOneShotCommand {
#[argh(positional, description = "optional target percent (defaults to 100)")]
pub percent: Option<u8>,
}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "info",
description = "show current battery charge limit"
)]
pub struct BatteryInfoCommand {}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "info",
description = "show program version and system info"
)]
pub struct InfoCommand {
#[argh(switch, description = "show supported functions of this laptop")]
pub show_supported: bool,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "leds", description = "keyboard brightness control")]
pub struct BrightnessCommand {
#[argh(subcommand)]
pub command: BrightnessSubCommand,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand)]
pub enum BrightnessSubCommand {
Set(BrightnessSetCommand),
Get(BrightnessGetCommand),
Next(BrightnessNextCommand),
Prev(BrightnessPrevCommand),
}
impl Default for BrightnessSubCommand {
fn default() -> Self {
BrightnessSubCommand::Get(BrightnessGetCommand::default())
}
}
#[derive(FromArgs, Debug)]
#[argh(
subcommand,
name = "set",
description = "set keyboard brightness <off, low, med, high>"
)]
pub struct BrightnessSetCommand {
#[argh(positional, description = "brightness level: off, low, med, high")]
pub level: LedBrightness,
}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "get",
description = "get current keyboard brightness"
)]
pub struct BrightnessGetCommand {}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "next",
description = "toggle to next keyboard brightness"
)]
pub struct BrightnessNextCommand {}
#[derive(FromArgs, Debug, Default)]
#[argh(
subcommand,
name = "prev",
description = "toggle to previous keyboard brightness"
)]
pub struct BrightnessPrevCommand {}

View File

@@ -1,49 +1,44 @@
use gumdrop::Options; use argh::FromArgs;
use rog_platform::platform::PlatformProfile; use rog_platform::platform::PlatformProfile;
use rog_profiles::fan_curve_set::CurveData; use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::FanCurvePU; use rog_profiles::FanCurvePU;
#[derive(Debug, Clone, Options)] #[derive(FromArgs, Debug, Clone)]
#[argh(subcommand, name = "fan-curve", description = "fan curve commands")]
pub struct FanCurveCommand { pub struct FanCurveCommand {
#[options(help = "print help message")] #[argh(switch, description = "get enabled fan profiles")]
pub help: bool,
#[options(help = "get enabled fan profiles")]
pub get_enabled: bool, pub get_enabled: bool,
#[options(help = "set the active profile's fan curve to default")] #[argh(switch, description = "set the active profile's fan curve to default")]
pub default: bool, pub default: bool,
#[options( #[argh(
meta = "", option,
help = "profile to modify fan-curve for. Shows data if no options provided" description = "profile to modify fan-curve for. shows data if no options provided"
)] )]
pub mod_profile: Option<PlatformProfile>, pub mod_profile: Option<PlatformProfile>,
#[options( #[argh(
meta = "", option,
help = "enable or disable <true/false> fan all curves for a profile. `--mod_profile` \ description = "enable or disable <true/false> fan all curves for a profile; --mod_profile required"
required"
)] )]
pub enable_fan_curves: Option<bool>, pub enable_fan_curves: Option<bool>,
#[options( #[argh(
meta = "", option,
help = "enable or disable <true/false> a single fan curve for a profile. `--mod_profile` \ description = "enable or disable <true/false> a single fan curve for a profile; --mod_profile and --fan required"
and `--fan` required"
)] )]
pub enable_fan_curve: Option<bool>, pub enable_fan_curve: Option<bool>,
#[options( #[argh(
meta = "", option,
help = "select fan <cpu/gpu/mid> to modify. `--mod_profile` required" description = "select fan <cpu/gpu/mid> to modify; --mod_profile required"
)] )]
pub fan: Option<FanCurvePU>, pub fan: Option<FanCurvePU>,
#[options( #[argh(
meta = "", option,
help = "data format = 30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%. \ description = "data format = 30c:1%,49c:2%,...; --mod-profile required. If '%' is omitted the fan range is 0-255"
`--mod-profile` required. If '%' is omitted the fan range is 0-255"
)] )]
pub data: Option<CurveData>, pub data: Option<CurveData>,
} }

View File

@@ -1,5 +1,4 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use std::env::args;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use std::thread::sleep; use std::thread::sleep;
@@ -8,12 +7,11 @@ use anime_cli::{AnimeActions, AnimeCommand};
use aura_cli::{LedPowerCommand1, LedPowerCommand2}; use aura_cli::{LedPowerCommand1, LedPowerCommand2};
use dmi_id::DMIID; use dmi_id::DMIID;
use fan_curve_cli::FanCurveCommand; use fan_curve_cli::FanCurveCommand;
use gumdrop::{Opt, Options};
use log::{error, info, LevelFilter}; use log::{error, info, LevelFilter};
use rog_anime::usb::get_anime_type; use rog_anime::usb::get_anime_type;
use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType, Vec2}; use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType, Vec2};
use rog_aura::keyboard::{AuraPowerState, LaptopAuraPower}; use rog_aura::keyboard::{AuraPowerState, LaptopAuraPower};
use rog_aura::{self, AuraDeviceType, AuraEffect, PowerZones}; use rog_aura::{self, AuraEffect, PowerZones};
use rog_dbus::asus_armoury::AsusArmouryProxyBlocking; use rog_dbus::asus_armoury::AsusArmouryProxyBlocking;
use rog_dbus::list_iface_blocking; use rog_dbus::list_iface_blocking;
use rog_dbus::scsi_aura::ScsiAuraProxyBlocking; use rog_dbus::scsi_aura::ScsiAuraProxyBlocking;
@@ -32,7 +30,6 @@ use scsi_cli::ScsiCommand;
use zbus::blocking::proxy::ProxyImpl; use zbus::blocking::proxy::ProxyImpl;
use zbus::blocking::Connection; use zbus::blocking::Connection;
use crate::aura_cli::{AuraPowerStates, LedBrightness};
use crate::cli_opts::*; use crate::cli_opts::*;
use crate::slash_cli::SlashCommand; use crate::slash_cli::SlashCommand;
@@ -56,22 +53,7 @@ fn main() {
.format_timestamp(None) .format_timestamp(None)
.init(); .init();
let self_version = env!("CARGO_PKG_VERSION"); let parsed: CliStart = argh::from_env();
println!("Starting version {self_version}");
let args: Vec<String> = args().skip(1).collect();
let missing_argument_k = gumdrop::Error::missing_argument(Opt::Short('k'));
let parsed = match CliStart::parse_args_default(&args) {
Ok(p) => p,
Err(err) if err.to_string() == missing_argument_k.to_string() => CliStart {
kbd_bright: Some(LedBrightness::new(None)),
..Default::default()
},
Err(err) => {
println!("Error: {}", err);
return;
}
};
let conn = Connection::system().unwrap(); let conn = Connection::system().unwrap();
if let Ok(platform_proxy) = PlatformProxyBlocking::new(&conn).map_err(|e| { if let Ok(platform_proxy) = PlatformProxyBlocking::new(&conn).map_err(|e| {
@@ -90,6 +72,7 @@ fn main() {
} }
}; };
let self_version = env!("CARGO_PKG_VERSION");
if asusd_version != self_version { if asusd_version != self_version {
println!("Version mismatch: asusctl = {self_version}, asusd = {asusd_version}"); println!("Version mismatch: asusctl = {self_version}, asusd = {asusd_version}");
return; return;
@@ -110,12 +93,6 @@ fn main() {
} }
}; };
if parsed.version {
println!("asusctl v{}", env!("CARGO_PKG_VERSION"));
println!();
print_info();
}
if let Err(err) = do_parsed(&parsed, &supported_interfaces, &supported_properties, conn) { if let Err(err) = do_parsed(&parsed, &supported_interfaces, &supported_properties, conn) {
print_error_help(&*err, &supported_interfaces, &supported_properties); print_error_help(&*err, &supported_interfaces, &supported_properties);
} }
@@ -142,9 +119,9 @@ fn print_info() {
let dmi = DMIID::new().unwrap_or_default(); let dmi = DMIID::new().unwrap_or_default();
let board_name = dmi.board_name; let board_name = dmi.board_name;
let prod_family = dmi.product_family; let prod_family = dmi.product_family;
println!("asusctl version: {}", env!("CARGO_PKG_VERSION")); println!("Software version: {}", env!("CARGO_PKG_VERSION"));
println!(" Product family: {}", prod_family.trim()); println!(" Product family: {}", prod_family.trim());
println!(" Board name: {}", board_name.trim()); println!(" Board name: {}", board_name.trim());
} }
fn check_service(name: &str) -> bool { fn check_service(name: &str) -> bool {
@@ -209,149 +186,63 @@ fn do_parsed(
conn: Connection, conn: Connection,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
match &parsed.command { match &parsed.command {
Some(CliCommand::Aura(mode)) => handle_led_mode(mode)?, CliCommand::Aura(mode) => handle_led_mode(mode)?,
Some(CliCommand::AuraPowerOld(pow)) => handle_led_power1(pow)?, CliCommand::AuraPowerOld(pow) => handle_led_power1(pow)?,
Some(CliCommand::AuraPower(pow)) => handle_led_power2(pow)?, CliCommand::AuraPower(pow) => handle_led_power2(pow)?,
Some(CliCommand::Profile(cmd)) => { CliCommand::Brightness(cmd) => handle_brightness(cmd)?,
handle_throttle_profile(&conn, supported_properties, cmd)? CliCommand::Profile(cmd) => handle_throttle_profile(&conn, supported_properties, cmd)?,
} CliCommand::FanCurve(cmd) => handle_fan_curve(&conn, cmd)?,
Some(CliCommand::FanCurve(cmd)) => { CliCommand::Anime(cmd) => handle_anime(cmd)?,
handle_fan_curve(&conn, cmd)?; CliCommand::Slash(cmd) => handle_slash(cmd)?,
} CliCommand::Scsi(cmd) => handle_scsi(cmd)?,
Some(CliCommand::Graphics(_)) => do_gfx(), CliCommand::Armoury(cmd) => handle_armoury_command(cmd)?,
Some(CliCommand::Anime(cmd)) => handle_anime(cmd)?, CliCommand::Backlight(cmd) => handle_backlight(cmd)?,
Some(CliCommand::Slash(cmd)) => handle_slash(cmd)?, CliCommand::Battery(cmd) => handle_battery(cmd, &conn)?,
Some(CliCommand::Scsi(cmd)) => handle_scsi(cmd)?, CliCommand::Info(info_opt) => {
Some(CliCommand::Armoury(cmd)) => handle_armoury_command(cmd)?, handle_info(info_opt, supported_interfaces, supported_properties)?
Some(CliCommand::Backlight(cmd)) => handle_backlight(cmd)?,
None => {
if (!parsed.show_supported
&& parsed.kbd_bright.is_none()
&& parsed.chg_limit.is_none()
&& !parsed.next_kbd_bright
&& !parsed.prev_kbd_bright
&& !parsed.one_shot_chg)
|| parsed.help
{
println!("{}", CliStart::usage());
println!();
if let Some(cmdlist) = CliStart::command_list() {
let dev_type =
if let Ok(proxy) = find_iface::<AuraProxyBlocking>("xyz.ljones.Aura") {
// TODO: commands on all?
proxy
.first()
.unwrap()
.device_type()
.unwrap_or(AuraDeviceType::Unknown)
} else {
AuraDeviceType::Unknown
};
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
for command in commands.iter().filter(|command| {
if command.trim().starts_with("fan-curve")
&& !supported_interfaces.contains(&"xyz.ljones.FanCurves".to_string())
{
return false;
}
if command.trim().starts_with("aura")
&& !supported_interfaces.contains(&"xyz.ljones.Aura".to_string())
{
return false;
}
if command.trim().starts_with("anime")
&& !supported_interfaces.contains(&"xyz.ljones.Anime".to_string())
{
return false;
}
if command.trim().starts_with("slash")
&& !supported_interfaces.contains(&"xyz.ljones.Slash".to_string())
{
return false;
}
if command.trim().starts_with("platform")
&& !supported_interfaces.contains(&"xyz.ljones.Platform".to_string())
{
return false;
}
if command.trim().starts_with("armoury")
&& !supported_interfaces.contains(&"xyz.ljones.AsusArmoury".to_string())
{
return false;
}
if command.trim().starts_with("backlight")
&& !supported_interfaces.contains(&"xyz.ljones.Backlight".to_string())
{
return false;
}
if !dev_type.is_old_laptop()
&& !dev_type.is_tuf_laptop()
&& command.trim().starts_with("aura-power-old")
{
return false;
}
if !dev_type.is_new_laptop() && command.trim().starts_with("aura-power") {
return false;
}
true
}) {
println!("{}", command);
}
}
println!("\nExtra help can be requested on any command or subcommand:");
println!(" asusctl aura --help");
println!(" asusctl aura static --help");
}
} }
} }
if let Some(brightness) = &parsed.kbd_bright { Ok(())
if let Ok(aura) = find_iface::<AuraProxyBlocking>("xyz.ljones.Aura") { }
for aura in aura.iter() {
match brightness.level() { fn handle_battery(
None => { cmd: &BatteryCommand,
let level = aura.brightness()?; conn: &Connection,
println!("Current keyboard led brightness: {level:?}"); ) -> Result<(), Box<dyn std::error::Error>> {
} match &cmd.command {
Some(level) => aura.set_brightness(rog_aura::LedBrightness::from(level))?, BatterySubCommand::Limit(l) => {
} let proxy = PlatformProxyBlocking::new(conn)?;
proxy.set_charge_control_end_threshold(l.limit)?;
}
BatterySubCommand::OneShot(o) => {
let proxy = PlatformProxyBlocking::new(conn)?;
if let Some(p) = o.percent {
proxy.set_charge_control_end_threshold(p)?;
} }
} else { proxy.one_shot_full_charge()?;
println!("No aura interface found"); }
BatterySubCommand::Info(_) => {
let proxy = PlatformProxyBlocking::new(conn)?;
let limit = proxy.charge_control_end_threshold()?;
println!("Current battery charge limit: {}%", limit);
} }
} }
if parsed.next_kbd_bright { Ok(())
if let Ok(aura) = find_iface::<AuraProxyBlocking>("xyz.ljones.Aura") { }
for aura in aura.iter() {
let brightness = aura.brightness()?;
aura.set_brightness(brightness.next())?;
}
} else {
println!("No aura interface found");
}
}
if parsed.prev_kbd_bright { fn handle_info(
if let Ok(aura) = find_iface::<AuraProxyBlocking>("xyz.ljones.Aura") { info_opt: &InfoCommand,
for aura in aura.iter() { supported_interfaces: &[String],
let brightness = aura.brightness()?; supported_properties: &[Properties],
aura.set_brightness(brightness.prev())?; ) -> Result<(), Box<dyn std::error::Error>> {
} println!("asusctl v{}", env!("CARGO_PKG_VERSION"));
} else { println!();
println!("No aura interface found"); print_info();
} println!();
}
if parsed.show_supported { if info_opt.show_supported {
println!("Supported Core Functions:\n{:#?}", supported_interfaces); println!("Supported Core Functions:\n{:#?}", supported_interfaces);
println!( println!(
"Supported Platform Properties:\n{:#?}", "Supported Platform Properties:\n{:#?}",
@@ -372,35 +263,14 @@ fn do_parsed(
} }
} }
if let Some(chg_limit) = parsed.chg_limit {
let proxy = PlatformProxyBlocking::new(&conn)?;
proxy.set_charge_control_end_threshold(chg_limit)?;
}
if parsed.one_shot_chg {
let proxy = PlatformProxyBlocking::new(&conn)?;
proxy.one_shot_full_charge()?;
}
Ok(()) Ok(())
} }
fn do_gfx() {
println!(
"Please use supergfxctl for graphics switching. supergfxctl is the result of making \
asusctl graphics switching generic so all laptops can use it"
);
println!("This command will be removed in future");
}
fn handle_backlight(cmd: &BacklightCommand) -> Result<(), Box<dyn std::error::Error>> { fn handle_backlight(cmd: &BacklightCommand) -> Result<(), Box<dyn std::error::Error>> {
if (cmd.screenpad_brightness.is_none() if cmd.screenpad_brightness.is_none()
&& cmd.screenpad_gamma.is_none() && cmd.screenpad_gamma.is_none()
&& cmd.sync_screenpad_brightness.is_none()) && cmd.sync_screenpad_brightness.is_none()
|| cmd.help
{ {
println!("Missing arg or command\n\n{}", cmd.self_usage());
let backlights = find_iface::<BacklightProxyBlocking>("xyz.ljones.Backlight")?; let backlights = find_iface::<BacklightProxyBlocking>("xyz.ljones.Backlight")?;
for backlight in backlights { for backlight in backlights {
println!("Current screenpad settings:"); println!("Current screenpad settings:");
@@ -433,8 +303,50 @@ fn handle_backlight(cmd: &BacklightCommand) -> Result<(), Box<dyn std::error::Er
Ok(()) Ok(())
} }
fn handle_brightness(cmd: &BrightnessCommand) -> Result<(), Box<dyn std::error::Error>> {
let Ok(aura_proxies) = find_iface::<AuraProxyBlocking>("xyz.ljones.Aura") else {
println!("No aura interface found");
return Ok(());
};
match &cmd.command {
BrightnessSubCommand::Set(s) => {
for aura in aura_proxies.iter() {
if let Some(level) = s.level.level() {
aura.set_brightness(rog_aura::LedBrightness::from(level))?;
} else {
let current = aura.brightness()?;
println!("Current keyboard led brightness: {current:?}");
}
}
}
BrightnessSubCommand::Get(_) => {
for aura in aura_proxies.iter() {
let level = aura.brightness()?;
println!("Current keyboard led brightness: {level:?}");
}
return Ok(());
}
BrightnessSubCommand::Next(_) => {
for aura in aura_proxies.iter() {
let brightness = aura.brightness()?;
aura.set_brightness(brightness.next())?;
}
}
BrightnessSubCommand::Prev(_) => {
for aura in aura_proxies.iter() {
let brightness = aura.brightness()?;
aura.set_brightness(brightness.prev())?;
}
}
}
Ok(())
}
fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> { fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
if (cmd.command.is_none() if cmd.command.is_none()
&& cmd.enable_display.is_none() && cmd.enable_display.is_none()
&& cmd.enable_powersave_anim.is_none() && cmd.enable_powersave_anim.is_none()
&& cmd.brightness.is_none() && cmd.brightness.is_none()
@@ -442,13 +354,9 @@ fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
&& cmd.off_when_suspended.is_none() && cmd.off_when_suspended.is_none()
&& cmd.off_when_unplugged.is_none() && cmd.off_when_unplugged.is_none()
&& cmd.off_with_his_head.is_none() && cmd.off_with_his_head.is_none()
&& !cmd.clear) && !cmd.clear
|| cmd.help
{ {
println!("Missing arg or command\n\n{}", cmd.self_usage()); println!("Missing arg or command; run 'asusctl anime --help' for usage");
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
} }
let animes = find_iface::<AnimeProxyBlocking>("xyz.ljones.Anime").map_err(|e| { let animes = find_iface::<AnimeProxyBlocking>("xyz.ljones.Anime").map_err(|e| {
@@ -495,11 +403,10 @@ fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
if let Some(action) = cmd.command.as_ref() { if let Some(action) = cmd.command.as_ref() {
match action { match action {
AnimeActions::Image(image) => { AnimeActions::Image(image) => {
if image.help_requested() || image.path.is_empty() { if image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage()); println!(
if let Some(lst) = image.self_command_list() { "Missing arg or command; run 'asusctl anime image --help' for usage"
println!("\n{}", lst); );
}
return Ok(()); return Ok(());
} }
verify_brightness(image.bright); verify_brightness(image.bright);
@@ -516,11 +423,8 @@ fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
proxy.write(<AnimeDataBuffer>::try_from(&matrix)?)?; proxy.write(<AnimeDataBuffer>::try_from(&matrix)?)?;
} }
AnimeActions::PixelImage(image) => { AnimeActions::PixelImage(image) => {
if image.help_requested() || image.path.is_empty() { if image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage()); println!("Missing arg or command; run 'asusctl anime pixel-image --help' for usage");
if let Some(lst) = image.self_command_list() {
println!("\n{}", lst);
}
return Ok(()); return Ok(());
} }
verify_brightness(image.bright); verify_brightness(image.bright);
@@ -535,11 +439,10 @@ fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
proxy.write(matrix.into_data_buffer(anime_type)?)?; proxy.write(matrix.into_data_buffer(anime_type)?)?;
} }
AnimeActions::Gif(gif) => { AnimeActions::Gif(gif) => {
if gif.help_requested() || gif.path.is_empty() { if gif.path.is_empty() {
println!("Missing arg or command\n\n{}", gif.self_usage()); println!(
if let Some(lst) = gif.self_command_list() { "Missing arg or command; run 'asusctl anime gif --help' for usage"
println!("\n{}", lst); );
}
return Ok(()); return Ok(());
} }
verify_brightness(gif.bright); verify_brightness(gif.bright);
@@ -569,11 +472,8 @@ fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
} }
} }
AnimeActions::PixelGif(gif) => { AnimeActions::PixelGif(gif) => {
if gif.help_requested() || gif.path.is_empty() { if gif.path.is_empty() {
println!("Missing arg or command\n\n{}", gif.self_usage()); println!("Missing arg or command; run 'asusctl anime pixel-gif --help' for usage");
if let Some(lst) = gif.self_command_list() {
println!("\n{}", lst);
}
return Ok(()); return Ok(());
} }
verify_brightness(gif.bright); verify_brightness(gif.bright);
@@ -600,14 +500,8 @@ fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
} }
} }
AnimeActions::SetBuiltins(builtins) => { AnimeActions::SetBuiltins(builtins) => {
if builtins.help_requested() || builtins.set.is_none() { if builtins.set.is_none() {
println!( println!("Missing arg; run 'asusctl anime set-builtins --help' for usage");
"\nAny unspecified args will be set to default (first shown var)\n"
);
println!("\n{}", builtins.self_usage());
if let Some(lst) = builtins.self_command_list() {
println!("\n{}", lst);
}
return Ok(()); return Ok(());
} }
@@ -634,24 +528,19 @@ fn verify_brightness(brightness: f32) {
} }
fn handle_slash(cmd: &SlashCommand) -> Result<(), Box<dyn std::error::Error>> { fn handle_slash(cmd: &SlashCommand) -> Result<(), Box<dyn std::error::Error>> {
if (cmd.brightness.is_none() if cmd.brightness.is_none()
&& cmd.interval.is_none() && cmd.interval.is_none()
&& cmd.show_on_boot.is_none() && cmd.show_on_boot.is_none()
&& cmd.show_on_shutdown.is_none() && cmd.show_on_shutdown.is_none()
&& cmd.show_on_sleep.is_none() && cmd.show_on_sleep.is_none()
&& cmd.show_on_battery.is_none() && cmd.show_on_battery.is_none()
&& cmd.show_battery_warning.is_none() && cmd.show_battery_warning.is_none()
// && cmd.show_on_lid_closed.is_none()
&& cmd.mode.is_none() && cmd.mode.is_none()
&& !cmd.list && !cmd.list
&& !cmd.enable && !cmd.enable
&& !cmd.disable) && !cmd.disable
|| cmd.help
{ {
println!("Missing arg or command\n\n{}", cmd.self_usage()); println!("Missing arg or command; run 'asusctl slash --help' for usage");
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
} }
let slashes = find_iface::<SlashProxyBlocking>("xyz.ljones.Slash")?; let slashes = find_iface::<SlashProxyBlocking>("xyz.ljones.Slash")?;
@@ -702,13 +591,8 @@ fn handle_slash(cmd: &SlashCommand) -> Result<(), Box<dyn std::error::Error>> {
} }
fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box<dyn std::error::Error>> { fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box<dyn std::error::Error>> {
if (!cmd.list && cmd.enable.is_none() && cmd.mode.is_none() && cmd.colours.is_empty()) if !cmd.list && cmd.enable.is_none() && cmd.mode.is_none() && cmd.colours.is_empty() {
|| cmd.help println!("Missing arg or command; run 'asusctl scsi --help' for usage");
{
println!("Missing arg or command\n\n{}", cmd.self_usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
} }
let scsis = find_iface::<ScsiAuraProxyBlocking>("xyz.ljones.ScsiAura")?; let scsis = find_iface::<ScsiAuraProxyBlocking>("xyz.ljones.ScsiAura")?;
@@ -774,38 +658,15 @@ fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box<dyn std::error::Error>> {
fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box<dyn std::error::Error>> { fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box<dyn std::error::Error>> {
if mode.command.is_none() && !mode.prev_mode && !mode.next_mode { if mode.command.is_none() && !mode.prev_mode && !mode.next_mode {
if !mode.help { println!("Missing arg or command; run 'asusctl aura --help' for usage");
println!("Missing arg or command\n"); // print available modes when possible
} if let Ok(aura) = find_iface::<AuraProxyBlocking>("xyz.ljones.Aura") {
println!("{}\n", mode.self_usage());
println!("Commands available");
if let Some(cmdlist) = LedModeCommand::command_list() {
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
// TODO: multiple rgb check
let aura = find_iface::<AuraProxyBlocking>("xyz.ljones.Aura")?;
let modes = aura.first().unwrap().supported_basic_modes()?; let modes = aura.first().unwrap().supported_basic_modes()?;
for command in commands.iter().filter(|command| { println!("Available modes:");
for mode in &modes { for m in modes {
let mut mode = <&str>::from(mode).to_string(); println!(" {:?}", m);
if let Some(pos) = mode.chars().skip(1).position(|c| c.is_uppercase()) {
mode.insert(pos + 1, '-');
}
if command.trim().starts_with(&mode.to_lowercase()) {
return true;
}
}
// TODO
// if !supported.basic_zones.is_empty() && command.trim().starts_with("multi") {
// return true;
// }
false
}) {
println!("{}", command);
} }
} }
println!("\nHelp can also be requested on modes, e.g: static --help");
return Ok(()); return Ok(());
} }
@@ -837,10 +698,6 @@ fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box<dyn std::error::Erro
aura.set_led_mode(modes[pos])?; aura.set_led_mode(modes[pos])?;
} }
} else if let Some(mode) = mode.command.as_ref() { } else if let Some(mode) = mode.command.as_ref() {
if mode.help_requested() {
println!("{}", mode.self_usage());
return Ok(());
}
for aura in aura { for aura in aura {
aura.set_led_mode_data(<AuraEffect>::from(mode))?; aura.set_led_mode_data(<AuraEffect>::from(mode))?;
} }
@@ -863,10 +720,7 @@ fn handle_led_power1(power: &LedPowerCommand1) -> Result<(), Box<dyn std::error:
&& !power.keyboard && !power.keyboard
&& !power.lightbar && !power.lightbar
{ {
if !power.help { println!("Missing arg or command; run 'asusctl aura-power-old --help' for usage");
println!("Missing arg or command\n");
}
println!("{}\n", power.self_usage());
return Ok(()); return Ok(());
} }
@@ -918,51 +772,47 @@ fn handle_led_power2(power: &LedPowerCommand2) -> Result<(), Box<dyn std::error:
continue; continue;
} }
if power.command().is_none() { if power.command.is_none() {
if !power.help { println!("Missing arg or command; run 'asusctl aura-power --help' for usage");
println!("Missing arg or command\n");
}
println!("{}\n", power.self_usage());
println!("Commands available"); println!("Commands available");
if let Some(cmdlist) = LedPowerCommand2::command_list() {
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
for command in &commands {
println!("{}", command);
}
}
println!("\nHelp can also be requested on commands, e.g: boot --help");
return Ok(()); return Ok(());
} }
if let Some(pow) = power.command.as_ref() { if let Some(_pow) = power.command.as_ref() {
if pow.help_requested() {
println!("{}", pow.self_usage());
return Ok(());
}
let mut states = aura.led_power()?; let mut states = aura.led_power()?;
let mut set = |zone: PowerZones, set_to: &AuraPowerStates| { let mut set =
for state in states.states.iter_mut() { |zone: PowerZones, boot_v: bool, awake_v: bool, sleep_v: bool, shutdown_v: bool| {
if state.zone == zone { for state in states.states.iter_mut() {
state.boot = set_to.boot; if state.zone == zone {
state.awake = set_to.awake; state.boot = boot_v;
state.sleep = set_to.sleep; state.awake = awake_v;
state.shutdown = set_to.shutdown; state.sleep = sleep_v;
break; state.shutdown = shutdown_v;
break;
}
} }
} };
};
if let Some(cmd) = &power.command { if let Some(cmd) = &power.command {
match cmd { match cmd {
aura_cli::SetAuraZoneEnabled::Keyboard(k) => set(PowerZones::Keyboard, k), aura_cli::SetAuraZoneEnabled::Keyboard(k) => {
aura_cli::SetAuraZoneEnabled::Logo(l) => set(PowerZones::Logo, l), set(PowerZones::Keyboard, k.boot, k.awake, k.sleep, k.shutdown)
aura_cli::SetAuraZoneEnabled::Lightbar(l) => set(PowerZones::Lightbar, l), }
aura_cli::SetAuraZoneEnabled::Lid(l) => set(PowerZones::Lid, l), aura_cli::SetAuraZoneEnabled::Logo(l) => {
aura_cli::SetAuraZoneEnabled::RearGlow(r) => set(PowerZones::RearGlow, r), set(PowerZones::Logo, l.boot, l.awake, l.sleep, l.shutdown)
aura_cli::SetAuraZoneEnabled::Ally(r) => set(PowerZones::Ally, r), }
aura_cli::SetAuraZoneEnabled::Lightbar(l) => {
set(PowerZones::Lightbar, l.boot, l.awake, l.sleep, l.shutdown)
}
aura_cli::SetAuraZoneEnabled::Lid(l) => {
set(PowerZones::Lid, l.boot, l.awake, l.sleep, l.shutdown)
}
aura_cli::SetAuraZoneEnabled::RearGlow(r) => {
set(PowerZones::RearGlow, r.boot, r.awake, r.sleep, r.shutdown)
}
aura_cli::SetAuraZoneEnabled::Ally(r) => {
set(PowerZones::Ally, r.boot, r.awake, r.sleep, r.shutdown)
}
} }
} }
@@ -983,51 +833,37 @@ fn handle_throttle_profile(
return Err(ProfileError::NotSupported.into()); return Err(ProfileError::NotSupported.into());
} }
if !cmd.next
&& !cmd.list
&& cmd.profile_set.is_none()
&& !cmd.profile_get
&& cmd.profile_set_ac.is_none()
&& cmd.profile_set_bat.is_none()
{
if !cmd.help {
println!("Missing arg or command\n");
}
println!("{}", ProfileCommand::usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
return Ok(());
}
let proxy = PlatformProxyBlocking::new(conn)?; let proxy = PlatformProxyBlocking::new(conn)?;
let current = proxy.platform_profile()?; let current = proxy.platform_profile()?;
let choices = proxy.platform_profile_choices()?; let choices = proxy.platform_profile_choices()?;
if cmd.next { match &cmd.command {
proxy.set_platform_profile(PlatformProfile::next(current, &choices))?; crate::cli_opts::ProfileSubCommand::Next(_) => {
} else if let Some(profile) = cmd.profile_set { proxy.set_platform_profile(PlatformProfile::next(current, &choices))?;
proxy.set_platform_profile(profile)?; }
} else if let Some(profile) = cmd.profile_set_ac { crate::cli_opts::ProfileSubCommand::Set(s) => {
proxy.set_platform_profile_on_ac(profile)?; if !s.ac && !s.battery {
} else if let Some(profile) = cmd.profile_set_bat { proxy.set_platform_profile(s.profile)?;
proxy.set_platform_profile_on_battery(profile)?; } else {
} if s.ac {
proxy.set_platform_profile_on_ac(s.profile)?;
if cmd.list { }
for p in &choices { if s.battery {
println!("{:?}", p); proxy.set_platform_profile_on_battery(s.profile)?;
}
}
}
crate::cli_opts::ProfileSubCommand::List(_) => {
for p in &choices {
println!("{:?}", p);
}
}
crate::cli_opts::ProfileSubCommand::Get(_) => {
println!("Active profile: {current:?}");
println!();
println!("AC profile {:?}", proxy.platform_profile_on_ac()?);
println!("Battery profile {:?}", proxy.platform_profile_on_battery()?);
} }
}
if cmd.profile_get {
println!("Active profile is {current:?}");
println!("Profile on AC is {:?}", proxy.platform_profile_on_ac()?);
println!(
"Profile on Battery is {:?}",
proxy.platform_profile_on_battery()?
);
} }
Ok(()) Ok(())
@@ -1044,14 +880,7 @@ fn handle_fan_curve(
}; };
if !cmd.get_enabled && !cmd.default && cmd.mod_profile.is_none() { if !cmd.get_enabled && !cmd.default && cmd.mod_profile.is_none() {
if !cmd.help { println!("Missing arg or command; run 'asusctl fan-curve --help' for usage");
println!("Missing arg or command\n");
}
println!("{}", FanCurveCommand::usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
return Ok(()); return Ok(());
} }
@@ -1190,33 +1019,33 @@ fn print_firmware_attr(attr: &AsusArmouryProxyBlocking) -> Result<(), Box<dyn st
#[allow(clippy::manual_is_multiple_of, clippy::nonminimal_bool)] #[allow(clippy::manual_is_multiple_of, clippy::nonminimal_bool)]
fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box<dyn std::error::Error>> { fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box<dyn std::error::Error>> {
{ // If nested subcommand provided, handle set/get/list.
if cmd.free.is_empty() || (cmd.free.len() % 2 != 0) || cmd.help { match &cmd.command {
const USAGE: &str = "Usage: asusctl platform panel_overdrive 1 nv_dynamic_boost 5"; ArmourySubCommand::List(_) => {
if cmd.free.len() % 2 != 0 { if let Ok(attrs) = find_iface::<AsusArmouryProxyBlocking>("xyz.ljones.AsusArmoury") {
println!( for attr in attrs.iter() {
"Incorrect number of args, each attribute label must be paired with a setting:"
);
println!("{USAGE}");
return Ok(());
}
if let Ok(attr) = find_iface::<AsusArmouryProxyBlocking>("xyz.ljones.AsusArmoury") {
println!("\n{USAGE}\n");
println!("Available firmware attributes: ");
for attr in attr.iter() {
print_firmware_attr(attr)?; print_firmware_attr(attr)?;
} }
} }
return Ok(()); Ok(())
} }
ArmourySubCommand::Get(g) => {
if let Ok(attr) = find_iface::<AsusArmouryProxyBlocking>("xyz.ljones.AsusArmoury") { if let Ok(attrs) = find_iface::<AsusArmouryProxyBlocking>("xyz.ljones.AsusArmoury") {
for cmd in cmd.free.chunks(2) { for attr in attrs.iter() {
for attr in attr.iter() {
let name = attr.name()?; let name = attr.name()?;
if <&str>::from(name) == cmd[0] { if <&str>::from(name) == g.property {
let mut value: i32 = cmd[1].parse()?; print_firmware_attr(attr)?;
}
}
}
Ok(())
}
ArmourySubCommand::Set(s) => {
if let Ok(attrs) = find_iface::<AsusArmouryProxyBlocking>("xyz.ljones.AsusArmoury") {
for attr in attrs.iter() {
let name = attr.name()?;
if <&str>::from(name) == s.property {
let mut value: i32 = s.value;
if value == -1 { if value == -1 {
info!("Setting to default"); info!("Setting to default");
value = attr.default_value()?; value = attr.default_value()?;
@@ -1226,7 +1055,7 @@ fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box<dyn std::error
} }
} }
} }
Ok(())
} }
} }
Ok(())
} }

View File

@@ -1,35 +1,30 @@
use gumdrop::Options; use argh::FromArgs;
use rog_scsi::{AuraMode, Colour, Direction, Speed}; use rog_scsi::{AuraMode, Colour, Direction, Speed};
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(subcommand, name = "scsi", description = "scsi LED commands")]
pub struct ScsiCommand { pub struct ScsiCommand {
#[options(help = "print help message")] #[argh(option, description = "enable the SCSI drive LEDs")]
pub help: bool,
#[options(help = "Enable the SCSI drive LEDs")]
pub enable: Option<bool>, pub enable: Option<bool>,
#[options(meta = "", help = "Set LED mode (so 'list' for all options)")] #[argh(option, description = "set LED mode (use 'list' for all options)")]
pub mode: Option<AuraMode>, pub mode: Option<AuraMode>,
#[options( #[argh(
meta = "", option,
help = "Set LED mode speed <slowest, slow, med, fast, fastest> (does not apply to all)" description = "set LED mode speed <slowest, slow, med, fast, fastest>"
)] )]
pub speed: Option<Speed>, pub speed: Option<Speed>,
#[options( #[argh(option, description = "set LED mode direction <forward, reverse>")]
meta = "",
help = "Set LED mode direction <forward, reverse> (does not apply to all)"
)]
pub direction: Option<Direction>, pub direction: Option<Direction>,
#[options( #[argh(
meta = "", option,
help = "Set LED colours <hex>, specify up to 4 with repeated arg" description = "set LED colours <hex>, specify up to 4 with repeated arg"
)] )]
pub colours: Vec<Colour>, pub colours: Vec<Colour>,
#[options(help = "list available animations")] #[argh(switch, description = "list available animations")]
pub list: bool, pub list: bool,
} }

View File

@@ -1,37 +1,34 @@
use gumdrop::Options; use argh::FromArgs;
use rog_slash::SlashMode; use rog_slash::SlashMode;
#[derive(Options)] #[derive(FromArgs, Debug)]
#[argh(subcommand, name = "slash", description = "slash ledbar commands")]
pub struct SlashCommand { pub struct SlashCommand {
#[options(help = "print help message")] #[argh(switch, description = "enable the Slash Ledbar")]
pub help: bool,
#[options(help = "Enable the Slash Ledbar")]
pub enable: bool, pub enable: bool,
#[options(help = "Disable the Slash Ledbar")] #[argh(switch, description = "disable the Slash Ledbar")]
pub disable: bool, pub disable: bool,
#[options(short = "l", meta = "", help = "Set brightness value <0-255>")] #[argh(option, short = 'l', description = "set brightness value <0-255>")]
pub brightness: Option<u8>, pub brightness: Option<u8>,
#[options(meta = "", help = "Set interval value <0-5>")] #[argh(option, description = "set interval value <0-5>")]
pub interval: Option<u8>, pub interval: Option<u8>,
#[options(meta = "", help = "Set SlashMode (so 'list' for all options)")] #[argh(option, description = "set SlashMode (use 'list' for options)")]
pub mode: Option<SlashMode>, pub mode: Option<SlashMode>,
#[options(help = "list available animations")] #[argh(switch, description = "list available animations")]
pub list: bool, pub list: bool,
#[options(short = "B", meta = "", help = "Show the animation on boot")] #[argh(option, short = 'B', description = "show the animation on boot")]
pub show_on_boot: Option<bool>, pub show_on_boot: Option<bool>,
#[options(short = "S", meta = "", help = "Show the animation on shutdown")] #[argh(option, short = 'S', description = "show the animation on shutdown")]
pub show_on_shutdown: Option<bool>, pub show_on_shutdown: Option<bool>,
#[options(short = "s", meta = "", help = "Show the animation on sleep")] #[argh(option, short = 's', description = "show the animation on sleep")]
pub show_on_sleep: Option<bool>, pub show_on_sleep: Option<bool>,
#[options(short = "b", meta = "", help = "Show the animation on battery")] #[argh(option, short = 'b', description = "show the animation on battery")]
pub show_on_battery: Option<bool>, pub show_on_battery: Option<bool>,
// #[options(short = "L", meta = "", help = "Show the animation on lid closed")] #[argh(
// pub show_on_lid_closed: Option<bool>, option,
#[options( short = 'w',
short = "w", description = "show the low-battery warning animation"
meta = "",
help = "Show the low-battery warning animation"
)] )]
pub show_battery_warning: Option<bool>, pub show_battery_warning: Option<bool>,
} }

View File

@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use config_traits::StdConfig; use config_traits::StdConfig;
use log::{debug, error, info}; use log::{debug, error, info, warn};
use rog_platform::asus_armoury::{AttrValue, Attribute, FirmwareAttribute, FirmwareAttributes}; use rog_platform::asus_armoury::{AttrValue, Attribute, FirmwareAttribute, FirmwareAttributes};
use rog_platform::platform::{PlatformProfile, RogPlatform}; use rog_platform::platform::{PlatformProfile, RogPlatform};
use rog_platform::power::AsusPower; use rog_platform::power::AsusPower;
@@ -170,7 +170,9 @@ impl crate::Reloadable for AsusArmouryAttribute {
info!("Reloading {}", self.attr.name()); info!("Reloading {}", self.attr.name());
let name: FirmwareAttribute = self.attr.name().into(); let name: FirmwareAttribute = self.attr.name().into();
if name.is_ppt() { // Treat dGPU attributes the same as PPT attributes for power-profile
// behaviour so they follow AC/DC tuning groups.
if name.is_ppt() || name.is_dgpu() {
let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let power_plugged = self let power_plugged = self
.power .power
@@ -203,7 +205,13 @@ impl crate::Reloadable for AsusArmouryAttribute {
self.attr.base_path_exists(); self.attr.base_path_exists();
e e
})?; })?;
info!("Set {} to {:?}", self.attr.name(), tune); info!(
"Restored PPT armoury setting {} to {:?}",
self.attr.name(),
tune
);
} else {
info!("Ignored restoring PPT armoury setting {} as tuning group is disabled or no saved value", self.attr.name());
} }
} else { } else {
// Handle non-PPT attributes (boolean and other settings) // Handle non-PPT attributes (boolean and other settings)
@@ -211,7 +219,10 @@ impl crate::Reloadable for AsusArmouryAttribute {
self.attr self.attr
.set_current_value(&AttrValue::Integer(*saved_value)) .set_current_value(&AttrValue::Integer(*saved_value))
.map_err(|e| { .map_err(|e| {
error!("Could not set {} value: {e:?}", self.attr.name()); error!(
"Error restoring armoury setting {}: {e:?}",
self.attr.name()
);
self.attr.base_path_exists(); self.attr.base_path_exists();
e e
})?; })?;
@@ -220,6 +231,11 @@ impl crate::Reloadable for AsusArmouryAttribute {
self.attr.name(), self.attr.name(),
saved_value saved_value
); );
} else {
info!(
"No saved armoury setting for {}: skipping restore",
self.attr.name()
);
} }
} }
@@ -277,7 +293,7 @@ impl AsusArmouryAttribute {
async fn restore_default(&self) -> fdo::Result<()> { async fn restore_default(&self) -> fdo::Result<()> {
self.attr.restore_default()?; self.attr.restore_default()?;
if self.name().is_ppt() { if self.name().is_ppt() || self.name().is_dgpu() {
let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let power_plugged = self let power_plugged = self
.power .power
@@ -336,7 +352,7 @@ impl AsusArmouryAttribute {
#[zbus(property)] #[zbus(property)]
async fn current_value(&self) -> fdo::Result<i32> { async fn current_value(&self) -> fdo::Result<i32> {
if self.name().is_ppt() { if self.name().is_ppt() || self.name().is_dgpu() {
let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let power_plugged = self let power_plugged = self
.power .power
@@ -369,86 +385,9 @@ impl AsusArmouryAttribute {
)) ))
} }
async fn stored_value_for_power(&self, on_ac: bool) -> fdo::Result<i32> {
if !self.name().is_ppt() {
return Err(fdo::Error::NotSupported(
"Stored values are only available for PPT attributes".to_string(),
));
}
let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let config = self.config.lock().await;
if let Some(tuning) = config.select_tunings_ref(on_ac, profile) {
if let Some(tune) = tuning.group.get(&self.name()) {
return Ok(*tune);
}
}
if let AttrValue::Integer(i) = self.attr.default_value() {
return Ok(*i);
}
Err(fdo::Error::Failed(
"Could not read stored value".to_string(),
))
}
async fn set_value_for_power(&mut self, on_ac: bool, value: i32) -> fdo::Result<()> {
if !self.name().is_ppt() {
return Err(fdo::Error::NotSupported(
"Setting stored values is only supported for PPT attributes".to_string(),
));
}
let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let apply_now;
{
let mut config = self.config.lock().await;
let tuning = config.select_tunings(on_ac, profile);
if let Some(tune) = tuning.group.get_mut(&self.name()) {
*tune = value;
} else {
tuning.group.insert(self.name(), value);
debug!(
"Store {} value for {} power = {}",
self.attr.name(),
if on_ac { "AC" } else { "DC" },
value
);
}
apply_now = tuning.enabled;
config.write();
}
if apply_now {
let power_plugged = self
.power
.get_online()
.map_err(|e| {
error!("Could not get power status: {e:?}");
e
})
.unwrap_or_default()
!= 0;
if power_plugged == on_ac {
self.attr
.set_current_value(&AttrValue::Integer(value))
.map_err(|e| {
error!("Could not set value: {e:?}");
e
})?;
}
}
Ok(())
}
#[zbus(property)] #[zbus(property)]
async fn set_current_value(&mut self, value: i32) -> fdo::Result<()> { async fn set_current_value(&mut self, value: i32) -> fdo::Result<()> {
if self.name().is_ppt() { if self.name().is_ppt() || self.name().is_dgpu() {
let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let power_plugged = self let power_plugged = self
.power .power
@@ -472,44 +411,44 @@ impl AsusArmouryAttribute {
self.attr self.attr
.set_current_value(&AttrValue::Integer(value)) .set_current_value(&AttrValue::Integer(value))
.map_err(|e| { .map_err(|e| {
error!("Could not set value: {e:?}"); error!(
"Could not set value to PPT property {}: {e:?}",
self.attr.name()
);
e e
})?; })?;
} else {
warn!(
"Tuning group is disabled: skipping setting value to PPT property {}",
self.attr.name()
);
} }
} else { } else {
self.attr self.attr
.set_current_value(&AttrValue::Integer(value)) .set_current_value(&AttrValue::Integer(value))
.map_err(|e| { .map_err(|e| {
error!("Could not set value: {e:?}"); error!(
"Could not set value {value} to attribute {}: {e:?}",
self.attr.name()
);
e e
})?; })?;
let has_attr = self let mut settings = self.config.lock().await;
.config settings
.lock()
.await
.armoury_settings .armoury_settings
.contains_key(&self.name()); .entry(self.name())
if has_attr { .and_modify(|setting| {
if let Some(setting) = self debug!("Set config for {} = {value}", self.attr.name());
.config *setting = value;
.lock() })
.await .or_insert_with(|| {
.armoury_settings debug!("Adding config for {} = {value}", self.attr.name());
.get_mut(&self.name()) value
{ });
*setting = value
}
} else {
debug!("Adding config for {}", self.attr.name());
self.config
.lock()
.await
.armoury_settings
.insert(self.name(), value);
debug!("Set config for {} = {:?}", self.attr.name(), value);
}
} }
// write config after setting value
self.config.lock().await.write(); self.config.lock().await.write();
Ok(()) Ok(())
} }
@@ -538,8 +477,7 @@ pub async fn start_attributes_zbus(
"Skipping attribute '{}' due to reload error: {e:?}", "Skipping attribute '{}' due to reload error: {e:?}",
attr.attr.name() attr.attr.name()
); );
// continue with others break;
continue;
} }
let attr_name = attr.attribute_name(); let attr_name = attr.attribute_name();
@@ -577,7 +515,7 @@ pub async fn set_config_or_default(
) { ) {
for attr in attrs.attributes().iter() { for attr in attrs.attributes().iter() {
let name: FirmwareAttribute = attr.name().into(); let name: FirmwareAttribute = attr.name().into();
if name.is_ppt() { if name.is_ppt() || name.is_dgpu() {
let tuning = config.select_tunings(power_plugged, profile); let tuning = config.select_tunings(power_plugged, profile);
if !tuning.enabled { if !tuning.enabled {
debug!("Tuning group is not enabled, skipping"); debug!("Tuning group is not enabled, skipping");

View File

@@ -71,7 +71,13 @@ impl AuraZbus {
#[zbus(property)] #[zbus(property)]
async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> { async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> {
if let Some(bl) = self.0.backlight.as_ref() { if let Some(bl) = self.0.backlight.as_ref() {
return Ok(bl.lock().await.set_brightness(brightness.into())?); let res = bl.lock().await.set_brightness(brightness.into());
if res.is_ok() {
let mut config = self.0.config.lock().await;
config.brightness = brightness;
config.write();
}
return Ok(res?);
} }
Err(ZbErr::Failed("No sysfs brightness control".to_string())) Err(ZbErr::Failed("No sysfs brightness control".to_string()))
} }

View File

@@ -132,7 +132,7 @@ impl DeviceManager {
if let Some(usb_id) = usb_device.attribute_value("idProduct") { if let Some(usb_id) = usb_device.attribute_value("idProduct") {
if let Some(vendor_id) = usb_device.attribute_value("idVendor") { if let Some(vendor_id) = usb_device.attribute_value("idVendor") {
if vendor_id != "0b05" { if vendor_id != "0b05" {
debug!("Not ASUS vendor ID"); debug!("Not ASUS vendor ID: {}", vendor_id.to_string_lossy());
return Ok(devices); return Ok(devices);
} }
// Almost all devices are identified by the productId. // Almost all devices are identified by the productId.

View File

@@ -8,6 +8,12 @@ use serde::{Deserialize, Serialize};
const CONFIG_FILE: &str = "asusd.ron"; const CONFIG_FILE: &str = "asusd.ron";
/// Default value for base_charge_control_end_threshold when not present in config.
/// Returns 0 so restore_charge_limit() skips restoration for upgraded configs.
fn default_base_charge_limit() -> u8 {
0
}
#[derive(Default, Clone, Deserialize, Serialize, PartialEq)] #[derive(Default, Clone, Deserialize, Serialize, PartialEq)]
pub struct Tuning { pub struct Tuning {
pub enabled: bool, pub enabled: bool,
@@ -19,8 +25,8 @@ type Tunings = HashMap<PlatformProfile, Tuning>;
pub struct Config { pub struct Config {
// The current charge limit applied // The current charge limit applied
pub charge_control_end_threshold: u8, pub charge_control_end_threshold: u8,
/// Save charge limit for restoring /// Save charge limit for restoring after one-shot full charge
#[serde(skip)] #[serde(default = "default_base_charge_limit")]
pub base_charge_control_end_threshold: u8, pub base_charge_control_end_threshold: u8,
pub disable_nvidia_powerd_on_battery: bool, pub disable_nvidia_powerd_on_battery: bool,
/// An optional command/script to run when power is changed to AC /// An optional command/script to run when power is changed to AC
@@ -86,6 +92,9 @@ impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
charge_control_end_threshold: 100, charge_control_end_threshold: 100,
// NOTE: This is intentionally 100 (not 0 like the serde default).
// New installs get 100 (no limit). Upgraded configs missing this
// field get 0 via serde, which skips restore_charge_limit().
base_charge_control_end_threshold: 100, base_charge_control_end_threshold: 100,
disable_nvidia_powerd_on_battery: true, disable_nvidia_powerd_on_battery: true,
ac_command: Default::default(), ac_command: Default::default(),

View File

@@ -11,6 +11,7 @@ ENV{DMI_FAMILY}=="*Zenbook*", GOTO="asusd_start"
ENV{DMI_FAMILY}=="*ProArt*", GOTO="asusd_start" ENV{DMI_FAMILY}=="*ProArt*", GOTO="asusd_start"
ENV{DMI_FAMILY}=="*TX Air*", GOTO="asusd_start" ENV{DMI_FAMILY}=="*TX Air*", GOTO="asusd_start"
ENV{DMI_FAMILY}=="*TX Gaming*", GOTO="asusd_start" ENV{DMI_FAMILY}=="*TX Gaming*", GOTO="asusd_start"
ENV{DMI_FAMILY}=="*EXPERTBOOK*", GOTO="asusd_start"
# No match so # No match so
GOTO="asusd_end" GOTO="asusd_end"
@@ -18,4 +19,11 @@ LABEL="asusd_start"
ACTION=="add|change", DRIVER=="asus-nb-wmi", TAG+="systemd", ENV{SYSTEMD_WANTS}+="asusd.service" ACTION=="add|change", DRIVER=="asus-nb-wmi", TAG+="systemd", ENV{SYSTEMD_WANTS}+="asusd.service"
ACTION=="add|remove", DRIVER=="asus-nb-wmi", TAG+="systemd", ENV{SYSTEMD_WANTS}+="asusd.service" ACTION=="add|remove", DRIVER=="asus-nb-wmi", TAG+="systemd", ENV{SYSTEMD_WANTS}+="asusd.service"
# ASUS Custom Fan Curve Control - Grant user write access
# This allows rog-control-center to adjust fan curves without sudo
SUBSYSTEM=="hwmon", ATTR{name}=="asus_custom_fan_curve", \
RUN+="/bin/sh -c 'chmod 0666 /sys%p/pwm*'", \
RUN+="/bin/sh -c 'chmod 0666 /sys%p/temp*_auto_point*_pwm'", \
RUN+="/bin/sh -c 'chmod 0666 /sys%p/temp*_auto_point*_temp'"
LABEL="asusd_end" LABEL="asusd_end"

View File

@@ -9,7 +9,7 @@ Environment=IS_SERVICE=1
# Reduce noisy span logs while keeping useful debug info for asusd and related crates. # Reduce noisy span logs while keeping useful debug info for asusd and related crates.
# Keep global level at info but allow debug for our crates; silence tracing::span (very noisy) # Keep global level at info but allow debug for our crates; silence tracing::span (very noisy)
# RUST_LOG format: <module>=<level>,... (levels: error,warn,info,debug,trace) # RUST_LOG format: <module>=<level>,... (levels: error,warn,info,debug,trace)
Environment=RUST_LOG="info,asusd=debug,rog_platform=debug,tracing::span=error" Environment=RUST_LOG="info,asusd=debug,rog_platform=debug,tracing::span=error,zbus::object_server=error,zbus::connection::handshake::common=error,zbus::connection::handshake::client=error"
# required to prevent init issues with hid_asus and MCU # required to prevent init issues with hid_asus and MCU
ExecStartPre=/bin/sleep 1 ExecStartPre=/bin/sleep 1
ExecStart=/usr/bin/asusd ExecStart=/usr/bin/asusd

View File

@@ -0,0 +1,190 @@
#
# spec file for package asus-nb-ctrl
#
# Copyright (c) 2020-2025 Luke Jones <luke@ljones.dev>
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
%if %{defined fedora}
%global debug_package %{nil}
%endif
%define version 6.3.0
%define specrelease %{?dist}
%define pkg_release 1%{specrelease}
# Use hardening ldflags.
%global rustflags -Clink-arg=-Wl,-z,relro,-z,now
Name: asusctl
Version: %{version}
Release: %{pkg_release}
Summary: Control fan speeds, LEDs, graphics modes, and charge levels for ASUS notebooks
License: MPLv2
Group: System Environment/Kernel
URL: https://gitlab.com/asus-linux/asusctl
Source: https://gitlab.com/asus-linux/asusctl/-/archive/%{version}/%{name}-%{version}.tar.gz
%if %{defined fedora}
BuildRequires: rust-packaging
BuildRequires: systemd-rpm-macros
%else
BuildRequires: cargo-packaging
%endif
BuildRequires: git
BuildRequires: clang-devel
BuildRequires: cargo
BuildRequires: cmake
BuildRequires: rust
BuildRequires: rust-std-static
BuildRequires: pkgconfig(gbm)
BuildRequires: pkgconfig(libinput)
BuildRequires: pkgconfig(libseat)
BuildRequires: pkgconfig(libudev)
BuildRequires: pkgconfig(xkbcommon)
BuildRequires: pkgconfig(libzstd)
BuildRequires: pkgconfig(fontconfig)
BuildRequires: desktop-file-utils
%description
asus-nb-ctrl 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.
It provides an interface for rootless control of some system functions such as
fan speeds, keyboard LEDs, battery charge level, and graphics modes.
asus-nb-ctrl enables third-party apps to use the above with dbus methods.
%package rog-gui
Summary: An experimental GUI for %{name}
Requires: %{name} = %{version}-%{release}
%description rog-gui
A one-stop-shop GUI tool for asusd/asusctl. It aims to provide most controls,
a notification service, and ability to run in the background.
%prep
%autosetup
mkdir -p .cargo
cat > .cargo/config.toml << 'EOF'
[term]
verbose = true
[net]
offline = false
EOF
%build
export RUSTFLAGS="%{rustflags}"
%if %{defined fedora}
/usr/bin/cargo build --release --locked
%else
/usr/bin/cargo auditable build --release --locked
%endif
%install
export RUSTFLAGS="%{rustflags}"
%define _target_dir target/release
# Install binaries
install -D -m 0755 %{_target_dir}/asusd %{buildroot}%{_bindir}/asusd
install -D -m 0755 %{_target_dir}/asusd-user %{buildroot}%{_bindir}/asusd-user
install -D -m 0755 %{_target_dir}/asusctl %{buildroot}%{_bindir}/asusctl
install -D -m 0755 %{_target_dir}/rog-control-center %{buildroot}%{_bindir}/rog-control-center
# Install systemd units
install -D -m 0644 data/asusd.service %{buildroot}%{_unitdir}/asusd.service
install -D -m 0644 data/asusd-user.service %{buildroot}%{_userunitdir}/asusd-user.service
# Install udev rules
install -D -m 0644 data/asusd.rules %{buildroot}%{_udevrulesdir}/99-asusd.rules
# Install dbus config
install -D -m 0644 data/asusd.conf %{buildroot}%{_datadir}/dbus-1/system.d/asusd.conf
# Install asusd data
install -D -m 0644 rog-aura/data/aura_support.ron %{buildroot}%{_datadir}/asusd/aura_support.ron
cp -r rog-anime/data/anime %{buildroot}%{_datadir}/asusd/
# Install rog-gui data
install -D -m 0644 rog-control-center/data/rog-control-center.desktop %{buildroot}%{_datadir}/applications/rog-control-center.desktop
install -D -m 0644 rog-control-center/data/rog-control-center.png %{buildroot}%{_datadir}/icons/hicolor/512x512/apps/rog-control-center.png
mkdir -p %{buildroot}%{_datadir}/rog-gui/layouts
cp -r rog-aura/data/layouts/*.ron %{buildroot}%{_datadir}/rog-gui/layouts/
# Install icons
install -D -m 0644 data/icons/asus_notif_yellow.png %{buildroot}%{_datadir}/icons/hicolor/512x512/apps/asus_notif_yellow.png
install -D -m 0644 data/icons/asus_notif_green.png %{buildroot}%{_datadir}/icons/hicolor/512x512/apps/asus_notif_green.png
install -D -m 0644 data/icons/asus_notif_blue.png %{buildroot}%{_datadir}/icons/hicolor/512x512/apps/asus_notif_blue.png
install -D -m 0644 data/icons/asus_notif_red.png %{buildroot}%{_datadir}/icons/hicolor/512x512/apps/asus_notif_red.png
install -D -m 0644 data/icons/asus_notif_orange.png %{buildroot}%{_datadir}/icons/hicolor/512x512/apps/asus_notif_orange.png
install -D -m 0644 data/icons/asus_notif_white.png %{buildroot}%{_datadir}/icons/hicolor/512x512/apps/asus_notif_white.png
install -D -m 0644 data/icons/scalable/gpu-compute.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/status/gpu-compute.svg
install -D -m 0644 data/icons/scalable/gpu-hybrid.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/status/gpu-hybrid.svg
install -D -m 0644 data/icons/scalable/gpu-integrated.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/status/gpu-integrated.svg
install -D -m 0644 data/icons/scalable/gpu-nvidia.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/status/gpu-nvidia.svg
install -D -m 0644 data/icons/scalable/gpu-vfio.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/status/gpu-vfio.svg
install -D -m 0644 data/icons/scalable/notification-reboot.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/status/notification-reboot.svg
# Install docs
install -D -m 0644 README.md %{buildroot}%{_docdir}/%{name}/README.md
install -D -m 0644 rog-anime/README.md %{buildroot}%{_docdir}/%{name}/README-anime.md
install -D -m 0644 rog-anime/data/diagonal-template.png %{buildroot}%{_docdir}/%{name}/diagonal-template.png
# Install LICENSE to asusctl datadir
install -D -m 0644 LICENSE %{buildroot}%{_datadir}/asusctl/LICENSE
desktop-file-validate %{buildroot}%{_datadir}/applications/rog-control-center.desktop
%post
%systemd_post asusd.service
%preun
%systemd_preun asusd.service
%postun
%systemd_postun_with_restart asusd.service
%files
%license LICENSE
%{_bindir}/asusd
%{_bindir}/asusd-user
%{_bindir}/asusctl
%{_unitdir}/asusd.service
%{_userunitdir}/asusd-user.service
%{_udevrulesdir}/99-asusd.rules
%{_datadir}/dbus-1/system.d/asusd.conf
%{_datadir}/icons/hicolor/512x512/apps/asus_notif_yellow.png
%{_datadir}/icons/hicolor/512x512/apps/asus_notif_green.png
%{_datadir}/icons/hicolor/512x512/apps/asus_notif_red.png
%{_datadir}/icons/hicolor/512x512/apps/asus_notif_blue.png
%{_datadir}/icons/hicolor/512x512/apps/asus_notif_orange.png
%{_datadir}/icons/hicolor/512x512/apps/asus_notif_white.png
%{_datadir}/icons/hicolor/scalable/status/gpu-compute.svg
%{_datadir}/icons/hicolor/scalable/status/gpu-hybrid.svg
%{_datadir}/icons/hicolor/scalable/status/gpu-integrated.svg
%{_datadir}/icons/hicolor/scalable/status/gpu-nvidia.svg
%{_datadir}/icons/hicolor/scalable/status/gpu-vfio.svg
%{_datadir}/icons/hicolor/scalable/status/notification-reboot.svg
%{_docdir}/%{name}/
%{_datadir}/asusctl/
%{_datadir}/asusd/
%files rog-gui
%{_bindir}/rog-control-center
%{_datadir}/applications/rog-control-center.desktop
%{_datadir}/icons/hicolor/512x512/apps/rog-control-center.png
%{_datadir}/rog-gui
%changelog

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

@@ -64,6 +64,7 @@ pub enum AnimeType {
GA402, GA402,
GU604, GU604,
G635L, G635L,
G835L,
#[default] #[default]
Unsupported, Unsupported,
} }
@@ -72,13 +73,21 @@ impl FromStr for AnimeType {
type Err = AnimeError; type Err = AnimeError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(match s { let dmi = s.to_uppercase();
"ga401" | "GA401" => Self::GA401,
"ga402" | "GA402" => Self::GA402, if dmi.contains("GA401") {
"gu604" | "GU604" => Self::GU604, return Ok(Self::GA401);
"g635L" | "G635L" => Self::G635L, } else if dmi.contains("GA402") {
_ => Self::Unsupported, return Ok(Self::GA402);
}) } else if dmi.contains("GU604") {
return Ok(Self::GU604);
} else if dmi.contains("G635L") {
return Ok(Self::G635L);
} else if dmi.contains("G835L") {
return Ok(Self::G835L);
}
Ok(Self::Unsupported)
} }
} }
@@ -102,6 +111,7 @@ impl AnimeType {
pub fn width(&self) -> usize { pub fn width(&self) -> usize {
match self { match self {
AnimeType::GU604 => 70, AnimeType::GU604 => 70,
AnimeType::G835L => 74,
_ => 74, _ => 74,
} }
} }
@@ -111,6 +121,7 @@ impl AnimeType {
match self { match self {
AnimeType::GA401 => 36, AnimeType::GA401 => 36,
AnimeType::GU604 => 43, AnimeType::GU604 => 43,
AnimeType::G835L => 39,
_ => 39, _ => 39,
} }
} }
@@ -120,6 +131,7 @@ impl AnimeType {
match self { match self {
AnimeType::GA401 => PANE_LEN * 2, AnimeType::GA401 => PANE_LEN * 2,
AnimeType::GU604 => PANE_LEN * 3, AnimeType::GU604 => PANE_LEN * 3,
AnimeType::G835L => PANE_LEN * 3,
_ => PANE_LEN * 3, _ => PANE_LEN * 3,
} }
} }
@@ -184,7 +196,11 @@ impl TryFrom<AnimeDataBuffer> for AnimePacketType {
let mut buffers = match anime.anime { let mut buffers = match anime.anime {
AnimeType::GA401 => vec![[0; 640]; 2], AnimeType::GA401 => vec![[0; 640]; 2],
AnimeType::GA402 | AnimeType::GU604 | AnimeType::G635L | AnimeType::Unsupported => { AnimeType::GA402
| AnimeType::GU604
| AnimeType::G635L
| AnimeType::G835L
| AnimeType::Unsupported => {
vec![[0; 640]; 3] vec![[0; 640]; 3]
} }
}; };
@@ -197,7 +213,11 @@ impl TryFrom<AnimeDataBuffer> for AnimePacketType {
if matches!( if matches!(
anime.anime, anime.anime,
AnimeType::GA402 | AnimeType::GU604 | AnimeType::Unsupported AnimeType::GA402
| AnimeType::GU604
| AnimeType::G635L
| AnimeType::G835L
| AnimeType::Unsupported
) { ) {
buffers[2][..7].copy_from_slice(&USB_PREFIX3); buffers[2][..7].copy_from_slice(&USB_PREFIX3);
} }

View File

@@ -243,7 +243,7 @@ impl From<AnimShutdown> for i32 {
#[inline] #[inline]
pub fn get_anime_type() -> AnimeType { pub fn get_anime_type() -> AnimeType {
let dmi = DMIID::new().unwrap_or_default(); let dmi = DMIID::new().unwrap_or_default();
let board_name = dmi.board_name; let board_name = dmi.board_name.to_uppercase();
if board_name.contains("GA401I") || board_name.contains("GA401Q") { if board_name.contains("GA401I") || board_name.contains("GA401Q") {
AnimeType::GA401 AnimeType::GA401
@@ -253,6 +253,8 @@ pub fn get_anime_type() -> AnimeType {
AnimeType::GU604 AnimeType::GU604
} else if board_name.contains("G635L") { } else if board_name.contains("G635L") {
AnimeType::G635L AnimeType::G635L
} else if board_name.contains("G835L") {
AnimeType::G835L
} else { } else {
AnimeType::Unsupported AnimeType::Unsupported
} }

View File

@@ -38,7 +38,16 @@
( (
device_name: "FA617NS", device_name: "FA617NS",
product_id: "", product_id: "",
layout_name: "fx505d", layout_name: "fa507",
basic_modes: [Static, Breathe, Pulse],
basic_zones: [],
advanced_type: r#None,
power_zones: [Keyboard],
),
(
device_name: "FA617NT",
product_id: "",
layout_name: "fa507",
basic_modes: [Static, Breathe, Pulse], basic_modes: [Static, Breathe, Pulse],
basic_zones: [], basic_zones: [],
advanced_type: r#None, advanced_type: r#None,
@@ -47,7 +56,16 @@
( (
device_name: "FA617XS", device_name: "FA617XS",
product_id: "", product_id: "",
layout_name: "fx505d", layout_name: "fa507",
basic_modes: [Static, Breathe, Pulse],
basic_zones: [],
advanced_type: r#None,
power_zones: [Keyboard],
),
(
device_name: "FA617XT",
product_id: "",
layout_name: "fa507",
basic_modes: [Static, Breathe, Pulse], basic_modes: [Static, Breathe, Pulse],
basic_zones: [], basic_zones: [],
advanced_type: r#None, advanced_type: r#None,
@@ -107,6 +125,15 @@
advanced_type: r#None, advanced_type: r#None,
power_zones: [Keyboard], power_zones: [Keyboard],
), ),
(
device_name: "FX607V",
product_id: "",
layout_name: "fa506i",
basic_modes: [Static, Breathe, RainbowCycle, Pulse],
basic_zones: [],
advanced_type: Zoned([SingleZone]),
power_zones: [Keyboard],
),
( (
device_name: "FX617X", device_name: "FX617X",
product_id: "", product_id: "",
@@ -287,6 +314,15 @@
advanced_type: r#None, advanced_type: r#None,
power_zones: [Keyboard, Lightbar], power_zones: [Keyboard, Lightbar],
), ),
(
device_name: "G614JU",
product_id: "",
layout_name: "g634j-per-key",
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Star, Rain, Highlight, Laser, Ripple, Pulse, Comet, Flash],
basic_zones: [],
advanced_type: PerKey,
power_zones: [Keyboard, Lightbar, Logo],
),
( (
device_name: "G614JZ", device_name: "G614JZ",
product_id: "", product_id: "",
@@ -512,6 +548,15 @@
advanced_type: PerKey, advanced_type: PerKey,
power_zones: [Keyboard, Lightbar], power_zones: [Keyboard, Lightbar],
), ),
(
device_name: "G815L",
product_id: "",
layout_name: "g814ji-per-key",
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Star, Rain, Highlight, Laser, Ripple, Pulse, Comet, Flash],
basic_zones: [],
advanced_type: PerKey,
power_zones: [Keyboard, Lightbar],
),
( (
device_name: "G834J", device_name: "G834J",
product_id: "", product_id: "",
@@ -521,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

@@ -359,6 +359,12 @@ impl From<AuraEffect> for AuraModeNum {
} }
} }
#[cfg(feature = "dbus")]
impl zbus::zvariant::Basic for AuraModeNum {
const SIGNATURE_CHAR: char = 'u';
const SIGNATURE_STR: &'static str = "u";
}
/// Base effects have no zoning, while multizone is 1-4 /// Base effects have no zoning, while multizone is 1-4
#[cfg_attr( #[cfg_attr(
feature = "dbus", feature = "dbus",

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 }
@@ -28,6 +29,7 @@ rog_dbus = { path = "../rog-dbus" }
rog_aura = { path = "../rog-aura" } rog_aura = { path = "../rog-aura" }
rog_profiles = { path = "../rog-profiles" } rog_profiles = { path = "../rog-profiles" }
rog_platform = { path = "../rog-platform" } rog_platform = { path = "../rog-platform" }
rog_slash = { path = "../rog-slash" }
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", default-features = false } supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", default-features = false }
dmi_id = { path = "../dmi-id" } dmi_id = { path = "../dmi-id" }
@@ -38,6 +40,7 @@ env_logger.workspace = true
tokio.workspace = true tokio.workspace = true
serde.workspace = true serde.workspace = true
zbus.workspace = true zbus.workspace = true
ron.workspace = true
dirs.workspace = true dirs.workspace = true
notify-rust.workspace = true notify-rust.workspace = true
concat-idents.workspace = true concat-idents.workspace = true

View File

@@ -11,16 +11,21 @@ use gumdrop::Options;
use log::{debug, info, warn, LevelFilter}; use log::{debug, info, warn, LevelFilter};
use rog_control_center::cli_options::CliStart; use rog_control_center::cli_options::CliStart;
use rog_control_center::config::Config; use rog_control_center::config::Config;
use tokio::runtime::Runtime;
thread_local! {
pub static UI: std::cell::RefCell<Option<MainWindow>> = Default::default();
pub static TRAY_TOOLTIP: std::cell::RefCell<Option<TrayTooltip>> = Default::default();
}
use rog_control_center::error::Result; use rog_control_center::error::Result;
use rog_control_center::notify::start_notifications; use rog_control_center::notify::start_notifications;
use rog_control_center::slint::ComponentHandle; use rog_control_center::slint::ComponentHandle;
use rog_control_center::tray::init_tray; use rog_control_center::tray::{init_tray, TrayEvent, TrayStats};
use rog_control_center::ui::setup_window; use rog_control_center::ui::setup_window;
use rog_control_center::zbus_proxies::{ use rog_control_center::zbus_proxies::{
AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH, AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH,
}; };
use rog_control_center::{print_versions, MainWindow}; use rog_control_center::{print_versions, MainWindow, TrayTooltip};
use tokio::runtime::Runtime;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@@ -107,7 +112,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 +142,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 +161,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;
@@ -153,17 +170,33 @@ async fn main() -> Result<()> {
start_notifications(config.clone(), &rt)?; start_notifications(config.clone(), &rt)?;
if enable_tray_icon { let (tray_tx, mut tray_rx) = tokio::sync::mpsc::unbounded_channel();
init_tray(supported_properties, config.clone()); // Channel for broadcasting system stats to the tray tooltip
} let (stats_tx, stats_rx) = tokio::sync::watch::channel(TrayStats::default());
thread_local! { pub static UI: std::cell::RefCell<Option<MainWindow>> = Default::default()}; if enable_tray_icon {
init_tray(supported_properties, config.clone(), tray_tx, stats_rx);
}
// i_slint_backend_selector::with_platform(|_| Ok(())).unwrap(); // i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();
if !startup_in_background { if !startup_in_background {
if let Ok(mut app_state) = app_state.lock() { if let Ok(mut app_state) = app_state.lock() {
*app_state = AppState::MainWindowShouldOpen; *app_state = AppState::MainWindowShouldOpen;
} }
} else {
// Even in background, we need the UI handle for status polling and tray sync
let config_copy = config.clone();
let stats_tx_copy = stats_tx.clone();
slint::invoke_from_event_loop(move || {
UI.with(|ui_cell| {
let mut ui = ui_cell.borrow_mut();
if ui.is_none() {
let newui = setup_window(config_copy, stats_tx_copy.clone());
ui.replace(newui);
}
});
})
.ok();
} }
if std::env::var("RUST_TRANSLATIONS").is_ok() { if std::env::var("RUST_TRANSLATIONS").is_ok() {
@@ -178,9 +211,18 @@ async fn main() -> Result<()> {
thread::spawn(move || { thread::spawn(move || {
let mut state = AppState::StartingUp; let mut state = AppState::StartingUp;
loop { loop {
// Handle tray events
while let Ok(event) = tray_rx.try_recv() {
match event {
TrayEvent::ToggleTooltip(_, _) => {
// Native tooltip handled by ksni, no custom window needed
}
}
}
if is_rog_ally { if is_rog_ally {
let config_copy_2 = config.clone(); let config_copy_2 = config.clone();
let newui = setup_window(config.clone()); let newui = setup_window(config.clone(), stats_tx.clone());
newui.window().on_close_requested(move || { newui.window().on_close_requested(move || {
exit(0); exit(0);
}); });
@@ -203,76 +245,78 @@ 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 { let stats_tx_loop = stats_tx.clone();
if let Ok(mut app_state) = app_state.lock() { slint::invoke_from_event_loop(move || {
*app_state = AppState::MainWindowOpen; UI.with(|ui| {
} 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, stats_tx_loop.clone());
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,13 @@ 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_dbus::zbus_platform::PlatformProxy;
use rog_platform::platform::PlatformProfile;
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;
@@ -32,6 +30,7 @@ pub struct EnabledNotifications {
pub enabled: bool, pub enabled: bool,
pub receive_notify_gfx: bool, pub receive_notify_gfx: bool,
pub receive_notify_gfx_status: bool, pub receive_notify_gfx_status: bool,
pub receive_notify_platform_profile: bool,
} }
impl Default for EnabledNotifications { impl Default for EnabledNotifications {
@@ -40,6 +39,7 @@ impl Default for EnabledNotifications {
enabled: true, enabled: true,
receive_notify_gfx: true, receive_notify_gfx: true,
receive_notify_gfx_status: true, receive_notify_gfx_status: true,
receive_notify_platform_profile: true,
} }
} }
} }
@@ -90,6 +90,57 @@ fn start_dpu_status_mon(config: Arc<Mutex<Config>>) {
} }
} }
/// Start monitoring for platform profile changes (triggered by Fn+F5 or software)
/// and display an OSD notification when the profile changes.
fn start_platform_profile_mon(config: Arc<Mutex<Config>>, rt: &Runtime) {
let enabled_notifications_copy = config.clone();
rt.spawn(async move {
let conn = match zbus::Connection::system().await {
Ok(c) => c,
Err(e) => {
error!("zbus signal: platform_profile_mon: {e}");
return;
}
};
let proxy = match PlatformProxy::builder(&conn).build().await {
Ok(p) => p,
Err(e) => {
error!("zbus signal: platform_profile_mon proxy: {e}");
return;
}
};
// Get initial profile to avoid notification on startup
let mut last_profile = proxy.platform_profile().await.ok();
info!("Started platform profile change monitor");
use futures_util::StreamExt;
let mut stream = proxy.receive_platform_profile_changed().await;
while let Some(e) = stream.next().await {
if let Ok(config) = enabled_notifications_copy.lock() {
if !config.notifications.enabled
|| !config.notifications.receive_notify_platform_profile
{
continue;
}
}
if let Ok(new_profile) = e.get().await {
// Only show notification if profile actually changed
if last_profile != Some(new_profile) {
debug!("Platform profile changed to: {:?}", new_profile);
if let Err(e) = do_platform_profile_notif("Power Profile:", &new_profile)
.show()
.map(|n| n.on_close(|_| ()))
{
warn!("Failed to show platform profile notification: {e}");
}
last_profile = Some(new_profile);
}
}
}
});
}
pub fn start_notifications( pub fn start_notifications(
config: Arc<Mutex<Config>>, config: Arc<Mutex<Config>>,
rt: &Runtime, rt: &Runtime,
@@ -145,12 +196,11 @@ 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"); info!("Starting platform profile change monitor");
start_dpu_status_mon(enabled_notifications_copy.clone()); start_platform_profile_mon(config.clone(), rt);
};
// 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 +239,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,
@@ -304,96 +268,27 @@ fn do_gpu_status_notif(message: &str, data: &GfxPower) -> Notification {
notif notif
} }
fn do_gfx_action_notif(message: &str, action: GfxUserAction, mode: GpuMode) -> Result<()> { /// Create a notification for platform profile (power mode) changes.
if matches!(action, GfxUserAction::Reboot) { /// Uses profile-specific icons and user-friendly names.
do_mux_notification("Graphics mode change requires reboot", &mode).ok(); fn do_platform_profile_notif(message: &str, profile: &PlatformProfile) -> Notification {
return Ok(()); let profile_name = match profile {
} PlatformProfile::Balanced => "Balanced",
PlatformProfile::Performance => "Performance",
PlatformProfile::Quiet => "Quiet",
PlatformProfile::LowPower => "Low Power",
PlatformProfile::Custom => "Custom",
};
let mut notif = base_notification(message, &profile_name.to_owned());
let mut notif = Notification::new(); // Use appropriate icons for each profile
// These icons should be available in the system or ROG icon pack
let icon = match profile {
PlatformProfile::Balanced => "asus_notif_blue", // Blue for balanced
PlatformProfile::Performance => "asus_notif_red", // Red for performance
PlatformProfile::Quiet => "asus_notif_green", // Green for quiet/power saving
PlatformProfile::LowPower => "asus_notif_green", // Green for low power
PlatformProfile::Custom => "asus_notif_white", // White for custom
};
notif.icon(icon);
notif 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

@@ -59,6 +59,29 @@ struct AsusTray {
current_title: String, current_title: String,
current_icon: Icon, current_icon: Icon,
proxy: ROGCCZbusProxyBlocking<'static>, proxy: ROGCCZbusProxyBlocking<'static>,
tray_channel: Option<tokio::sync::mpsc::UnboundedSender<TrayEvent>>,
// System stats for native tooltip
cpu_temp: String,
gpu_temp: String,
cpu_fan: String,
gpu_fan: String,
power_w: String,
power_profile: String,
}
#[derive(Debug, Clone, Copy)]
pub enum TrayEvent {
ToggleTooltip(i32, i32),
}
#[derive(Debug, Clone, Default)]
pub struct TrayStats {
pub cpu_temp: String,
pub gpu_temp: String,
pub cpu_fan: String,
pub gpu_fan: String,
pub power_w: String,
pub power_profile: String,
} }
impl ksni::Tray for AsusTray { impl ksni::Tray for AsusTray {
@@ -78,6 +101,26 @@ impl ksni::Tray for AsusTray {
ksni::Status::Active ksni::Status::Active
} }
fn activate(&mut self, x: i32, y: i32) {
if let Some(tx) = &self.tray_channel {
let _ = tx.send(TrayEvent::ToggleTooltip(x, y));
}
}
fn tool_tip(&self) -> ksni::ToolTip {
ksni::ToolTip {
title: "ROG Control Center".into(),
description: format!(
"<b>Profile:</b> {}\n<b>CPU:</b> {}°C | <b>GPU:</b> {}°C\n<b>Fans:</b> {} / {} RPM\n<b>Power:</b> {} W",
self.power_profile,
self.cpu_temp, self.gpu_temp,
self.cpu_fan, self.gpu_fan,
self.power_w
),
..Default::default()
}
}
fn menu(&self) -> Vec<ksni::MenuItem<Self>> { fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
use ksni::menu::*; use ksni::menu::*;
vec![ vec![
@@ -155,7 +198,12 @@ fn find_dgpu() -> Option<Device> {
} }
/// The tray is controlled somewhat by `Arc<Mutex<SystemState>>` /// The tray is controlled somewhat by `Arc<Mutex<SystemState>>`
pub fn init_tray(_supported_properties: Vec<Properties>, config: Arc<Mutex<Config>>) { pub fn init_tray(
_supported_properties: Vec<Properties>,
config: Arc<Mutex<Config>>,
tray_channel: tokio::sync::mpsc::UnboundedSender<TrayEvent>,
mut stats_rx: tokio::sync::watch::Receiver<TrayStats>,
) {
tokio::spawn(async move { tokio::spawn(async move {
let user_con = zbus::blocking::Connection::session().unwrap(); let user_con = zbus::blocking::Connection::session().unwrap();
let proxy = ROGCCZbusProxyBlocking::new(&user_con).unwrap(); let proxy = ROGCCZbusProxyBlocking::new(&user_con).unwrap();
@@ -166,11 +214,19 @@ pub fn init_tray(_supported_properties: Vec<Properties>, config: Arc<Mutex<Confi
current_title: TRAY_LABEL.to_string(), current_title: TRAY_LABEL.to_string(),
current_icon: rog_red.clone(), current_icon: rog_red.clone(),
proxy, proxy,
tray_channel: Some(tray_channel),
// Initialize stats fields
cpu_temp: "--".into(),
gpu_temp: "--".into(),
cpu_fan: "--".into(),
gpu_fan: "--".into(),
power_w: "--".into(),
power_profile: "Unknown".into(),
}; };
// TODO: return an error to the UI // TODO: return an error to the UI
let mut tray; let mut tray;
match tray_init.spawn_without_dbus_name().await { match tray_init.disable_dbus_name(true).spawn().await {
Ok(t) => tray = t, Ok(t) => tray = t,
Err(e) => { Err(e) => {
log::error!( log::error!(
@@ -225,28 +281,45 @@ pub fn init_tray(_supported_properties: Vec<Properties>, config: Arc<Mutex<Confi
info!("Started ROGTray"); info!("Started ROGTray");
let mut last_power = GfxPower::Unknown; let mut last_power = GfxPower::Unknown;
let dev = find_dgpu(); let dev = find_dgpu();
// Loop with select! to handle both periodic checks and stats updates
loop { loop {
tokio::time::sleep(Duration::from_millis(1000)).await; tokio::select! {
if let Ok(lock) = config.try_lock() { _ = stats_rx.changed() => {
if !lock.enable_tray_icon { let stats = stats_rx.borrow().clone();
return; tray.update(move |t| {
t.cpu_temp = stats.cpu_temp;
t.gpu_temp = stats.gpu_temp;
t.cpu_fan = stats.cpu_fan;
t.gpu_fan = stats.gpu_fan;
t.power_w = stats.power_w;
t.power_profile = stats.power_profile;
}).await;
} }
} _ = tokio::time::sleep(Duration::from_millis(1000)) => {
if has_supergfx { if let Ok(lock) = config.try_lock() {
if let Ok(mode) = gfx_proxy.mode().await { if !lock.enable_tray_icon {
if let Ok(power) = gfx_proxy.power().await { return;
if last_power != power {
set_tray_icon_and_tip(mode, power, &mut tray, has_supergfx).await;
last_power = power;
} }
} }
} // Handle GPU icon updates
} else if let Some(dev) = dev.as_ref() { if has_supergfx {
if let Ok(power) = dev.get_runtime_status() { if let Ok(mode) = gfx_proxy.mode().await {
if last_power != power { if let Ok(power) = gfx_proxy.power().await {
set_tray_icon_and_tip(GfxMode::Hybrid, power, &mut tray, has_supergfx) if last_power != power {
.await; set_tray_icon_and_tip(mode, power, &mut tray, has_supergfx).await;
last_power = power; last_power = power;
}
}
}
} else if let Some(dev) = dev.as_ref() {
if let Ok(power) = dev.get_runtime_status() {
if last_power != power {
set_tray_icon_and_tip(GfxMode::Hybrid, power, &mut tray, has_supergfx)
.await;
last_power = power;
}
}
} }
} }
} }

View File

@@ -0,0 +1,200 @@
//! Software-based keyboard animation for keyboards that only support Static mode.
//! Provides Rainbow and Color Cycle animations via timer-based color updates.
use log::{info, warn};
use slint::Weak;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Arc;
use std::time::Duration;
use crate::MainWindow;
/// Animation mode enum matching the UI
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum AnimationMode {
#[default]
None,
Rainbow,
ColorCycle,
}
impl From<i32> for AnimationMode {
fn from(v: i32) -> Self {
match v {
1 => AnimationMode::Rainbow,
2 => AnimationMode::ColorCycle,
_ => AnimationMode::None,
}
}
}
/// Shared state for the animator
pub struct AnimatorState {
/// Current animation mode
pub mode: AtomicU32,
/// Animation speed in milliseconds (update interval)
pub speed_ms: AtomicU32,
/// Stop signal
pub stop: AtomicBool,
/// Current hue for rainbow mode (0-360)
hue: AtomicU32,
}
impl Default for AnimatorState {
fn default() -> Self {
Self {
mode: AtomicU32::new(0),
speed_ms: AtomicU32::new(200),
stop: AtomicBool::new(false),
hue: AtomicU32::new(0),
}
}
}
/// Convert HSV to RGB (H: 0-360, S: 0-100, V: 0-100)
fn hsv_to_rgb(h: u32, s: u32, v: u32) -> (u8, u8, u8) {
let s = s as f32 / 100.0;
let v = v as f32 / 100.0;
let c = v * s;
let h_prime = (h as f32 / 60.0) % 6.0;
let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
let m = v - c;
let (r, g, b) = match h_prime as u32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
(
((r + m) * 255.0) as u8,
((g + m) * 255.0) as u8,
((b + m) * 255.0) as u8,
)
}
/// Format RGB as hex color string for asusctl
fn rgb_to_hex(r: u8, g: u8, b: u8) -> String {
format!("{:02x}{:02x}{:02x}", r, g, b)
}
// Simple LCG for random numbers to avoid pulling in rand crate
fn next_random(seed: &mut u64) -> u32 {
*seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
(*seed >> 32) as u32
}
/// Start the animation loop (runs in tokio task)
pub fn start_animator(state: Arc<AnimatorState>, _ui_weak: Weak<MainWindow>) {
info!("Starting keyboard animator");
tokio::spawn(async move {
// Local state for Color Cycle (RGB)
let mut current_r: f32 = 255.0;
let mut current_g: f32 = 0.0;
let mut current_b: f32 = 0.0;
let mut target_r: f32 = 0.0;
let mut target_g: f32 = 255.0;
let mut target_b: f32 = 0.0;
let mut seed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(12345);
loop {
// Check for stop signal
if state.stop.load(Ordering::Relaxed) {
info!("Animator stopping");
break;
}
let mode = AnimationMode::from(state.mode.load(Ordering::Relaxed) as i32);
// Cap speed at 150ms for stability
let raw_speed = state.speed_ms.load(Ordering::Relaxed);
let effective_speed = raw_speed.max(150) as u64;
if mode == AnimationMode::None {
// No animation, sleep longer
tokio::time::sleep(Duration::from_millis(500)).await;
continue;
}
// Calculate next color
let hex_color = match mode {
AnimationMode::Rainbow => {
// Hue step 1 for smooth, granular transitions
let hue = state.hue.fetch_add(1, Ordering::Relaxed) % 360;
let (r, g, b) = hsv_to_rgb(hue, 100, 100);
rgb_to_hex(r, g, b)
}
AnimationMode::ColorCycle => {
// RGB Linear Interpolation (Fading) - NOT Rainbow
// 1. Check distance to target
let dist_sq = (target_r - current_r).powi(2)
+ (target_g - current_g).powi(2)
+ (target_b - current_b).powi(2);
// If close, pick new random target color
if dist_sq < 100.0 {
let next_h = next_random(&mut seed) % 360;
let (r, g, b) = hsv_to_rgb(next_h, 100, 100);
target_r = r as f32;
target_g = g as f32;
target_b = b as f32;
}
// 2. Lerp towards target (5% per frame for smooth ease-out)
let factor = 0.05;
current_r += (target_r - current_r) * factor;
current_g += (target_g - current_g) * factor;
current_b += (target_b - current_b) * factor;
rgb_to_hex(current_r as u8, current_g as u8, current_b as u8)
}
AnimationMode::None => continue,
};
// Send color update via asusctl command (blocking, AWAITED to prevent races)
let hex = hex_color.clone();
let _ = tokio::task::spawn_blocking(move || {
let result = Command::new("asusctl")
.args([
"aura", "static", "-c", &hex,
])
.output();
if let Err(e) = result {
warn!("Failed to set aura color: {}", e);
}
})
.await;
// Sleep for the animation speed interval
tokio::time::sleep(Duration::from_millis(effective_speed)).await;
}
});
}
/// Stop the animator
pub fn stop_animator(state: &Arc<AnimatorState>) {
state.stop.store(true, Ordering::Relaxed);
state.mode.store(0, Ordering::Relaxed);
}
/// Set animation mode
pub fn set_animation_mode(state: &Arc<AnimatorState>, mode: AnimationMode) {
state.mode.store(mode as u32, Ordering::Relaxed);
// Reset stop flag in case we're restarting
state.stop.store(false, Ordering::Relaxed);
}
/// Set animation speed
pub fn set_animation_speed(state: &Arc<AnimatorState>, speed_ms: u32) {
let clamped = speed_ms.clamp(50, 2000);
state.speed_ms.store(clamped, Ordering::Relaxed);
}

View File

@@ -1,6 +1,12 @@
pub mod aura_animator;
pub mod setup_anime; pub mod setup_anime;
pub mod setup_aura; pub mod setup_aura;
pub mod setup_fan_curve_custom;
pub mod setup_fans; pub mod setup_fans;
pub mod setup_screenpad;
pub mod setup_slash;
pub mod setup_status;
pub mod setup_supergfx;
pub mod setup_system; pub mod setup_system;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -11,9 +17,14 @@ use rog_dbus::list_iface_blocking;
use slint::{ComponentHandle, SharedString, Weak}; use slint::{ComponentHandle, SharedString, Weak};
use crate::config::Config; use crate::config::Config;
use crate::tray::TrayStats;
use crate::ui::setup_anime::setup_anime_page; use crate::ui::setup_anime::setup_anime_page;
use crate::ui::setup_aura::setup_aura_page; use crate::ui::setup_aura::setup_aura_page;
use crate::ui::setup_fans::setup_fan_curve_page; use crate::ui::setup_fans::setup_fan_curve_page;
use crate::ui::setup_screenpad::setup_screenpad;
use crate::ui::setup_slash::setup_slash;
use crate::ui::setup_status::setup_status;
use crate::ui::setup_supergfx::setup_supergfx;
use crate::ui::setup_system::{setup_system_page, setup_system_page_callbacks}; use crate::ui::setup_system::{setup_system_page, setup_system_page_callbacks};
use crate::{AppSettingsPageData, MainWindow}; use crate::{AppSettingsPageData, MainWindow};
@@ -82,7 +93,10 @@ pub fn show_toast(
}; };
} }
pub fn setup_window(config: Arc<Mutex<Config>>) -> MainWindow { pub fn setup_window(
config: Arc<Mutex<Config>>,
stats_tx: tokio::sync::watch::Sender<TrayStats>,
) -> MainWindow {
slint::set_xdg_app_id("rog-control-center") slint::set_xdg_app_id("rog-control-center")
.map_err(|e| warn!("Couldn't set application ID: {e:?}")) .map_err(|e| warn!("Couldn't set application ID: {e:?}"))
.ok(); .ok();
@@ -101,9 +115,27 @@ pub fn setup_window(config: Arc<Mutex<Config>>) -> MainWindow {
available.contains(&"xyz.ljones.Platform".to_string()), available.contains(&"xyz.ljones.Platform".to_string()),
available.contains(&"xyz.ljones.Aura".to_string()), available.contains(&"xyz.ljones.Aura".to_string()),
available.contains(&"xyz.ljones.Anime".to_string()), available.contains(&"xyz.ljones.Anime".to_string()),
available.contains(&"xyz.ljones.Slash".to_string()),
// Supergfx check
{
if let Ok(conn) = zbus::blocking::Connection::system() {
zbus::blocking::fdo::DBusProxy::new(&conn)
.ok()
.and_then(|p| {
p.name_has_owner("org.supergfxctl.Daemon".try_into().ok()?)
.ok()
})
.unwrap_or(false)
} else {
false
}
},
// Screenpad check (Backlight interface)
available.contains(&"xyz.ljones.Backlight".to_string()),
available.contains(&"xyz.ljones.FanCurves".to_string()), available.contains(&"xyz.ljones.FanCurves".to_string()),
true, true,
true, true,
true,
] ]
.into(), .into(),
); );
@@ -112,6 +144,33 @@ pub fn setup_window(config: Arc<Mutex<Config>>) -> MainWindow {
slint::quit_event_loop().unwrap(); slint::quit_event_loop().unwrap();
}); });
// Auto-hide toast logic
let toast_gen = Arc::new(Mutex::new(0u64));
let ui_weak = ui.as_weak();
ui.on_start_toast_timer(move || {
let toast_gen_clone = toast_gen.clone();
let ui_weak_clone = ui_weak.clone();
let my_gen = {
if let Ok(mut g) = toast_gen.lock() {
*g += 1;
*g
} else {
0
}
};
if my_gen > 0 {
tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_secs(4)).await;
if let Ok(g) = toast_gen_clone.lock() {
if *g == my_gen {
let _ =
ui_weak_clone.upgrade_in_event_loop(move |ui| ui.invoke_hide_toast());
}
}
});
}
});
setup_app_settings_page(&ui, config.clone()); setup_app_settings_page(&ui, config.clone());
if available.contains(&"xyz.ljones.Platform".to_string()) { if available.contains(&"xyz.ljones.Platform".to_string()) {
setup_system_page(&ui, config.clone()); setup_system_page(&ui, config.clone());
@@ -123,10 +182,24 @@ pub fn setup_window(config: Arc<Mutex<Config>>) -> MainWindow {
if available.contains(&"xyz.ljones.Anime".to_string()) { if available.contains(&"xyz.ljones.Anime".to_string()) {
setup_anime_page(&ui, config.clone()); setup_anime_page(&ui, config.clone());
} }
if available.contains(&"xyz.ljones.FanCurves".to_string()) { if available.contains(&"xyz.ljones.Slash".to_string()) {
setup_fan_curve_page(&ui, config); setup_slash(&ui, config.clone());
} }
// Always try to setup supergfx if detected above, but for simplicity here we assume if sidebar has it (re-check or just run)
// We didn't capture the boolean above. Let's just run it, it handles its own availability check internally via async proxy creation.
setup_supergfx(&ui, config.clone());
if available.contains(&"xyz.ljones.Backlight".to_string()) {
setup_screenpad(&ui, config.clone());
}
if available.contains(&"xyz.ljones.FanCurves".to_string()) {
setup_fan_curve_page(&ui, config.clone());
}
setup_status(&ui, config, stats_tx);
ui ui
} }
@@ -153,18 +226,49 @@ pub fn setup_app_settings_page(ui: &MainWindow, config: Arc<Mutex<Config>>) {
lock.write(); lock.write();
} }
}); });
// Master notifications toggle
let config_copy = config.clone(); let config_copy = config.clone();
global.on_set_enable_dgpu_notifications(move |enable| { global.on_set_notifications_enabled(move |enable| {
if let Ok(mut lock) = config_copy.try_lock() { if let Ok(mut lock) = config_copy.try_lock() {
lock.notifications.enabled = enable; lock.notifications.enabled = enable;
lock.write(); lock.write();
} }
}); });
// Granular notification toggles
let config_copy = config.clone();
global.on_set_notify_gfx_switch(move |enable| {
if let Ok(mut lock) = config_copy.try_lock() {
lock.notifications.receive_notify_gfx = enable;
lock.write();
}
});
let config_copy = config.clone();
global.on_set_notify_gfx_status(move |enable| {
if let Ok(mut lock) = config_copy.try_lock() {
lock.notifications.receive_notify_gfx_status = enable;
lock.write();
}
});
let config_copy = config.clone();
global.on_set_notify_platform_profile(move |enable| {
if let Ok(mut lock) = config_copy.try_lock() {
lock.notifications.receive_notify_platform_profile = enable;
lock.write();
}
});
// Initialize UI values from config
if let Ok(lock) = config.try_lock() { if let Ok(lock) = config.try_lock() {
global.set_run_in_background(lock.run_in_background); global.set_run_in_background(lock.run_in_background);
global.set_startup_in_background(lock.startup_in_background); global.set_startup_in_background(lock.startup_in_background);
global.set_enable_tray_icon(lock.enable_tray_icon); global.set_enable_tray_icon(lock.enable_tray_icon);
global.set_enable_dgpu_notifications(lock.notifications.enabled); global.set_notifications_enabled(lock.notifications.enabled);
global.set_notify_gfx_switch(lock.notifications.receive_notify_gfx);
global.set_notify_gfx_status(lock.notifications.receive_notify_gfx_status);
global.set_notify_platform_profile(lock.notifications.receive_notify_platform_profile);
} }
} }

View File

@@ -7,6 +7,9 @@ use rog_dbus::zbus_aura::AuraProxy;
use slint::{ComponentHandle, Model, RgbaColor, SharedString}; use slint::{ComponentHandle, Model, RgbaColor, SharedString};
use crate::config::Config; use crate::config::Config;
use crate::ui::aura_animator::{
set_animation_mode, set_animation_speed, start_animator, AnimationMode, AnimatorState,
};
use crate::ui::show_toast; use crate::ui::show_toast;
use crate::{ use crate::{
set_ui_callbacks, set_ui_props_async, AuraPageData, MainWindow, PowerZones as SlintPowerZones, set_ui_callbacks, set_ui_props_async, AuraPageData, MainWindow, PowerZones as SlintPowerZones,
@@ -123,8 +126,17 @@ pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
.ok(); .ok();
} }
// Create animator state (shared across callbacks)
let animator_state = Arc::new(AnimatorState::default());
if let Ok(modes) = aura.supported_basic_modes().await { if let Ok(modes) = aura.supported_basic_modes().await {
log::debug!("Available LED modes {modes:?}"); log::debug!("Available LED modes {modes:?}");
// Check if only Static mode is available (enable software animation)
let static_only = modes.len() == 1 && modes.iter().any(|m| *m == 0.into());
let handle_for_anim = handle.clone();
let animator_state_clone = animator_state.clone();
handle handle
.upgrade_in_event_loop(move |handle| { .upgrade_in_event_loop(move |handle| {
let m: Vec<i32> = modes.iter().map(|n| (*n).into()).collect(); let m: Vec<i32> = modes.iter().map(|n| (*n).into()).collect();
@@ -143,6 +155,33 @@ pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
handle handle
.global::<AuraPageData>() .global::<AuraPageData>()
.set_available_mode_names(res.as_slice().into()); .set_available_mode_names(res.as_slice().into());
// Enable software animation if only Static mode is available
if static_only {
info!("Only Static mode available - enabling software animation controls");
handle
.global::<AuraPageData>()
.set_soft_animation_available(true);
// Start the animator thread
start_animator(animator_state_clone.clone(), handle_for_anim.clone());
// Connect mode callback
let state_for_mode = animator_state_clone.clone();
handle
.global::<AuraPageData>()
.on_cb_soft_animation_mode(move |mode| {
set_animation_mode(&state_for_mode, AnimationMode::from(mode));
});
// Connect speed callback
let state_for_speed = animator_state_clone.clone();
handle
.global::<AuraPageData>()
.on_cb_soft_animation_speed(move |speed| {
set_animation_speed(&state_for_speed, speed as u32);
});
}
}) })
.map_err(|e| error!("{e:}")) .map_err(|e| error!("{e:}"))
.ok(); .ok();

View File

@@ -0,0 +1,241 @@
use std::fs;
use std::path::{Path, PathBuf};
use log::{error, info};
use serde::{Deserialize, Serialize};
use crate::{FanType, MainWindow, Node};
const ASUS_CUSTOM_FAN_NAME: &str = "asus_custom_fan_curve";
const CONFIG_FILE_NAME: &str = "custom_fans.ron";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomCurvePoint {
pub temp: u8,
pub pwm: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CustomFanConfig {
pub cpu_curve: Vec<CustomCurvePoint>,
pub gpu_curve: Vec<CustomCurvePoint>,
pub enabled: bool,
}
#[derive(Clone)]
struct SysfsPaths {
root: PathBuf,
}
impl SysfsPaths {
fn new() -> Option<Self> {
let hwmon = Path::new("/sys/class/hwmon");
if let Ok(entries) = fs::read_dir(hwmon) {
for entry in entries.flatten() {
let path = entry.path();
let name_path = path.join("name");
if let Ok(name) = fs::read_to_string(&name_path) {
if name.trim() == ASUS_CUSTOM_FAN_NAME {
info!("Found ASUS Custom Fan Control at {:?}", path);
return Some(Self { root: path });
}
}
}
}
None
}
fn enable_path(&self, index: u8) -> PathBuf {
self.root.join(format!("pwm{}_enable", index))
}
fn point_pwm_path(&self, fan_idx: u8, point_idx: u8) -> PathBuf {
self.root
.join(format!("pwm{}_auto_point{}_pwm", fan_idx, point_idx))
}
fn point_temp_path(&self, fan_idx: u8, point_idx: u8) -> PathBuf {
self.root
.join(format!("pwm{}_auto_point{}_temp", fan_idx, point_idx))
}
}
// Helper to write with logging
fn write_sysfs(path: &Path, value: &str) -> std::io::Result<()> {
// debug!("Writing {} to {:?}", value, path);
fs::write(path, value)
}
fn _read_sysfs_u8(path: &Path) -> std::io::Result<u8> {
let s = fs::read_to_string(path)?;
s.trim()
.parse::<u8>()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
}
pub fn is_custom_fan_supported() -> bool {
SysfsPaths::new().is_some()
}
// Logic to apply a full curve to a specific fan (1=CPU, 2=GPU usually)
// Implements the "Gradual Descent" algorithm
fn apply_curve_to_fan(
paths: &SysfsPaths,
fan_idx: u8,
points: &[CustomCurvePoint],
) -> std::io::Result<()> {
// Sort target points by temp (Hardware Requirement)
let mut sorted_target = points.to_vec();
sorted_target.sort_by_key(|p| p.temp);
// Ensure we have 8 points (fill with last if needed, or sensible default)
while sorted_target.len() < 8 {
if let Some(last) = sorted_target.last() {
sorted_target.push(last.clone());
} else {
sorted_target.push(CustomCurvePoint {
temp: 100,
pwm: 255,
});
}
}
sorted_target.truncate(8);
// Validate Temp Order (Synchronous Check)
for (i, p) in sorted_target.iter().enumerate() {
if i > 0 {
let prev_temp = sorted_target[i - 1].temp;
if p.temp < prev_temp {
error!("Invalid temp order");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Temp disorder",
));
}
}
}
// Spawn completely detached thread for ALL I/O
let paths_clone = paths.clone();
let sorted_target = sorted_target.clone();
std::thread::spawn(move || {
let paths = paths_clone;
// 1. Enable custom mode
if let Err(e) = write_sysfs(&paths.enable_path(fan_idx), "1") {
error!("Failed to enable custom fan mode: {}", e);
return;
}
// 2. Write Temps
for (i, p) in sorted_target.iter().enumerate() {
let point_idx = (i + 1) as u8;
if let Err(e) = write_sysfs(
&paths.point_temp_path(fan_idx, point_idx),
&p.temp.to_string(),
) {
error!("Failed to write temp point {}: {}", point_idx, e);
}
}
// 3. Write PWMs directly (hardware handles gradual transition)
for (i, target_p) in sorted_target.iter().enumerate() {
let point_idx = (i + 1) as u8;
if let Err(e) = write_sysfs(
&paths.point_pwm_path(fan_idx, point_idx),
&target_p.pwm.to_string(),
) {
error!("Failed to write PWM point {}: {}", point_idx, e);
}
}
// 4. Ensure enable is set
let _ = write_sysfs(&paths.enable_path(fan_idx), "1");
});
Ok(())
}
fn set_fan_auto(paths: &SysfsPaths, fan_idx: u8) -> std::io::Result<()> {
// 2 = Auto (usually)
write_sysfs(&paths.enable_path(fan_idx), "2")
}
fn load_config() -> CustomFanConfig {
if let Some(config_dir) = dirs::config_dir() {
let path = config_dir.join("rog").join(CONFIG_FILE_NAME);
if let Ok(content) = fs::read_to_string(path) {
if let Ok(cfg) = ron::from_str(&content) {
return cfg;
}
}
}
CustomFanConfig::default()
}
fn save_config(config: &CustomFanConfig) {
if let Some(config_dir) = dirs::config_dir() {
let rog_dir = config_dir.join("rog");
let _ = fs::create_dir_all(&rog_dir);
let path = rog_dir.join(CONFIG_FILE_NAME);
if let Ok(s) = ron::ser::to_string_pretty(config, ron::ser::PrettyConfig::default()) {
let _ = fs::write(path, s);
}
}
}
// Public entry point called from setup_fans.rs or similar
// Returns immediately - all work is done in a detached thread
pub fn apply_custom_fan_curve(
_handle_weak: slint::Weak<MainWindow>,
fan_type: FanType,
enabled: bool,
nodes: Vec<Node>,
) {
// Fan Index: 1=CPU, 2=GPU usually.
let fan_idx = match fan_type {
FanType::CPU => 1,
FanType::GPU => 2,
_ => return, // Ignore others
};
// Convert nodes to points (fast, CPU-only)
let points: Vec<CustomCurvePoint> = nodes
.iter()
.map(|n| CustomCurvePoint {
temp: n.x as u8,
pwm: n.y as u8,
})
.collect();
// Spawn a completely detached thread for ALL I/O
std::thread::spawn(move || {
// Get paths (blocking FS operation)
let Some(paths) = SysfsPaths::new() else {
error!("No custom fan support found");
return;
};
// Save config
let mut cfg = load_config();
if enabled {
match fan_type {
FanType::CPU => cfg.cpu_curve = points.clone(),
FanType::GPU => cfg.gpu_curve = points.clone(),
_ => {}
}
}
cfg.enabled = enabled;
save_config(&cfg);
// Apply curve or set auto
if enabled {
if let Err(e) = apply_curve_to_fan(&paths, fan_idx, &points) {
error!("Failed to apply fan curve: {}", e);
}
} else if let Err(e) = set_fan_auto(&paths, fan_idx) {
error!("Failed to set fan auto: {}", e);
}
});
}

View File

@@ -1,4 +1,6 @@
use std::sync::{Arc, Mutex}; use crate::ui::show_toast;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use log::error; use log::error;
use rog_dbus::zbus_fan_curves::FanCurvesProxy; use rog_dbus::zbus_fan_curves::FanCurvesProxy;
@@ -8,7 +10,25 @@ use rog_profiles::fan_curve_set::CurveData;
use slint::{ComponentHandle, Model, Weak}; use slint::{ComponentHandle, Model, Weak};
use crate::config::Config; use crate::config::Config;
use crate::{FanPageData, FanType, MainWindow, Node}; use crate::{FanPageData, FanType, MainWindow, Node, Profile};
// Isolated Rust-side cache for fan curves (not affected by Slint reactivity)
type FanCacheKey = (i32, i32); // (Profile as i32, FanType as i32)
static FAN_CACHE: OnceLock<Mutex<HashMap<FanCacheKey, Vec<Node>>>> = OnceLock::new();
fn fan_cache() -> &'static Mutex<HashMap<FanCacheKey, Vec<Node>>> {
FAN_CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
fn cache_fan_curve(profile: Profile, fan_type: FanType, nodes: Vec<Node>) {
let key = (profile as i32, fan_type as i32);
fan_cache().lock().unwrap().insert(key, nodes);
}
fn get_cached_fan_curve(profile: Profile, fan_type: FanType) -> Option<Vec<Node>> {
let key = (profile as i32, fan_type as i32);
fan_cache().lock().unwrap().get(&key).cloned()
}
pub fn update_fan_data( pub fn update_fan_data(
handle: Weak<MainWindow>, handle: Weak<MainWindow>,
@@ -19,7 +39,7 @@ pub fn update_fan_data(
handle handle
.upgrade_in_event_loop(move |handle| { .upgrade_in_event_loop(move |handle| {
let global = handle.global::<FanPageData>(); let global = handle.global::<FanPageData>();
let collect = |temp: &[u8], pwm: &[u8]| -> slint::ModelRc<Node> { let _collect = |temp: &[u8], pwm: &[u8]| -> slint::ModelRc<Node> {
let tmp: Vec<Node> = temp let tmp: Vec<Node> = temp
.iter() .iter()
.zip(pwm.iter()) .zip(pwm.iter())
@@ -33,61 +53,100 @@ pub fn update_fan_data(
for fan in bal { for fan in bal {
global.set_balanced_available(true); global.set_balanced_available(true);
let nodes_vec: Vec<Node> = fan
.temp
.iter()
.zip(fan.pwm.iter())
.map(|(x, y)| Node {
x: *x as f32,
y: *y as f32,
})
.collect();
let nodes: slint::ModelRc<Node> = nodes_vec.as_slice().into();
match fan.fan { match fan.fan {
rog_profiles::FanCurvePU::CPU => { rog_profiles::FanCurvePU::CPU => {
global.set_cpu_fan_available(true); global.set_cpu_fan_available(true);
global.set_balanced_cpu_enabled(fan.enabled); global.set_balanced_cpu_enabled(fan.enabled);
global.set_balanced_cpu(collect(&fan.temp, &fan.pwm)) global.set_balanced_cpu(nodes.clone());
cache_fan_curve(Profile::Balanced, FanType::CPU, nodes_vec);
} }
rog_profiles::FanCurvePU::GPU => { rog_profiles::FanCurvePU::GPU => {
global.set_gpu_fan_available(true); global.set_gpu_fan_available(true);
global.set_balanced_gpu_enabled(fan.enabled); global.set_balanced_gpu_enabled(fan.enabled);
global.set_balanced_gpu(collect(&fan.temp, &fan.pwm)) global.set_balanced_gpu(nodes.clone());
cache_fan_curve(Profile::Balanced, FanType::GPU, nodes_vec);
} }
rog_profiles::FanCurvePU::MID => { rog_profiles::FanCurvePU::MID => {
global.set_mid_fan_available(true); global.set_mid_fan_available(true);
global.set_balanced_mid_enabled(fan.enabled); global.set_balanced_mid_enabled(fan.enabled);
global.set_balanced_mid(collect(&fan.temp, &fan.pwm)) global.set_balanced_mid(nodes.clone());
cache_fan_curve(Profile::Balanced, FanType::Middle, nodes_vec);
} }
} }
} }
for fan in perf { for fan in perf {
global.set_performance_available(true); global.set_performance_available(true);
let nodes_vec: Vec<Node> = fan
.temp
.iter()
.zip(fan.pwm.iter())
.map(|(x, y)| Node {
x: *x as f32,
y: *y as f32,
})
.collect();
let nodes: slint::ModelRc<Node> = nodes_vec.as_slice().into();
match fan.fan { match fan.fan {
rog_profiles::FanCurvePU::CPU => { rog_profiles::FanCurvePU::CPU => {
global.set_cpu_fan_available(true); global.set_cpu_fan_available(true);
global.set_performance_cpu_enabled(fan.enabled); global.set_performance_cpu_enabled(fan.enabled);
global.set_performance_cpu(collect(&fan.temp, &fan.pwm)) global.set_performance_cpu(nodes.clone());
cache_fan_curve(Profile::Performance, FanType::CPU, nodes_vec);
} }
rog_profiles::FanCurvePU::GPU => { rog_profiles::FanCurvePU::GPU => {
global.set_gpu_fan_available(true); global.set_gpu_fan_available(true);
global.set_performance_gpu_enabled(fan.enabled); global.set_performance_gpu_enabled(fan.enabled);
global.set_performance_gpu(collect(&fan.temp, &fan.pwm)) global.set_performance_gpu(nodes.clone());
cache_fan_curve(Profile::Performance, FanType::GPU, nodes_vec);
} }
rog_profiles::FanCurvePU::MID => { rog_profiles::FanCurvePU::MID => {
global.set_mid_fan_available(true); global.set_mid_fan_available(true);
global.set_performance_mid_enabled(fan.enabled); global.set_performance_mid_enabled(fan.enabled);
global.set_performance_mid(collect(&fan.temp, &fan.pwm)) global.set_performance_mid(nodes.clone());
cache_fan_curve(Profile::Performance, FanType::Middle, nodes_vec);
} }
} }
} }
for fan in quiet { for fan in quiet {
global.set_quiet_available(true); global.set_quiet_available(true);
let nodes_vec: Vec<Node> = fan
.temp
.iter()
.zip(fan.pwm.iter())
.map(|(x, y)| Node {
x: *x as f32,
y: *y as f32,
})
.collect();
let nodes: slint::ModelRc<Node> = nodes_vec.as_slice().into();
match fan.fan { match fan.fan {
rog_profiles::FanCurvePU::CPU => { rog_profiles::FanCurvePU::CPU => {
global.set_cpu_fan_available(true); global.set_cpu_fan_available(true);
global.set_quiet_cpu_enabled(fan.enabled); global.set_quiet_cpu_enabled(fan.enabled);
global.set_quiet_cpu(collect(&fan.temp, &fan.pwm)) global.set_quiet_cpu(nodes.clone());
cache_fan_curve(Profile::Quiet, FanType::CPU, nodes_vec);
} }
rog_profiles::FanCurvePU::GPU => { rog_profiles::FanCurvePU::GPU => {
global.set_gpu_fan_available(true); global.set_gpu_fan_available(true);
global.set_quiet_gpu_enabled(fan.enabled); global.set_quiet_gpu_enabled(fan.enabled);
global.set_quiet_gpu(collect(&fan.temp, &fan.pwm)) global.set_quiet_gpu(nodes.clone());
cache_fan_curve(Profile::Quiet, FanType::GPU, nodes_vec);
} }
rog_profiles::FanCurvePU::MID => { rog_profiles::FanCurvePU::MID => {
global.set_mid_fan_available(true); global.set_mid_fan_available(true);
global.set_quiet_mid_enabled(fan.enabled); global.set_quiet_mid_enabled(fan.enabled);
global.set_quiet_mid(collect(&fan.temp, &fan.pwm)) global.set_quiet_mid(nodes.clone());
cache_fan_curve(Profile::Quiet, FanType::Middle, nodes_vec);
} }
} }
} }
@@ -168,15 +227,25 @@ pub fn setup_fan_curve_page(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
update_fan_data(handle, balanced, perf, quiet); update_fan_data(handle, balanced, perf, quiet);
let choices_for_ui = platform_profile_choices.clone();
let handle_next1 = handle_copy.clone(); let handle_next1 = handle_copy.clone();
if let Err(e) = handle_copy.upgrade_in_event_loop(move |handle| { if let Err(e) = handle_copy.upgrade_in_event_loop(move |handle| {
let handle_weak_for_fans = handle.as_weak();
let global = handle.global::<FanPageData>(); let global = handle.global::<FanPageData>();
let fans1 = fans.clone(); let fans1 = fans.clone();
let choices = choices_for_ui.clone();
global.on_set_profile_default(move |profile| { global.on_set_profile_default(move |profile| {
let fans = fans1.clone(); let fans = fans1.clone();
let handle_next = handle_next1.clone(); let handle_next = handle_next1.clone();
let choices = choices.clone();
tokio::spawn(async move { tokio::spawn(async move {
if fans.set_curves_to_defaults(profile.into()).await.is_err() { let mut target: PlatformProfile = profile.into();
if target == PlatformProfile::Quiet
&& !choices.contains(&PlatformProfile::Quiet)
{
target = PlatformProfile::LowPower;
}
if fans.set_curves_to_defaults(target).await.is_err() {
return; return;
} }
let Ok(balanced) = fans let Ok(balanced) = fans
@@ -203,17 +272,103 @@ pub fn setup_fan_curve_page(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
update_fan_data(handle_next, balanced, perf, quiet); update_fan_data(handle_next, balanced, perf, quiet);
}); });
}); });
let handle_weak_for_cancel = handle_weak_for_fans.clone();
global.on_set_fan_data(move |fan, profile, enabled, data| { global.on_set_fan_data(move |fan, profile, enabled, data| {
if crate::ui::setup_fan_curve_custom::is_custom_fan_supported() {
let handle_weak = handle_weak_for_fans.clone();
let data: Vec<Node> = data.iter().collect();
use log::info;
info!("MainThread: Request to apply custom curve for {:?}", fan);
// Explicitly spawn a thread to handle this, preventing ANY main thread blocking
std::thread::spawn(move || {
info!("WorkerThread: applying curve for {:?}", fan);
crate::ui::setup_fan_curve_custom::apply_custom_fan_curve(
handle_weak.clone(),
fan,
enabled,
data,
);
info!("WorkerThread: returned from apply (async), clearing busy flag for {:?}", fan);
// Clear busy flag
let _ = handle_weak.upgrade_in_event_loop(move |h| {
let g = h.global::<FanPageData>();
match fan {
FanType::CPU => g.set_is_busy_cpu(false),
FanType::GPU => g.set_is_busy_gpu(false),
FanType::Middle => g.set_is_busy_mid(false),
}
info!("MainThread: cleared busy flag for {:?}", fan);
});
});
return;
}
let fans = fans.clone(); let fans = fans.clone();
let data: Vec<Node> = data.iter().collect(); let handle_weak = handle_weak_for_fans.clone();
let data = fan_data_for(fan, enabled, data); let nodes_vec: Vec<Node> = data.iter().collect();
let _data_copy = nodes_vec.clone();
let cache_copy = nodes_vec.clone(); // Clone for cache update
let fan_data = fan_data_for(fan, enabled, nodes_vec);
tokio::spawn(async move { tokio::spawn(async move {
fans.set_fan_curve(profile.into(), data) show_toast(
.await "Fan curve applied".into(),
.map_err(|e| error!("{e:}")) "Failed to apply fan curve".into(),
.ok() handle_weak.clone(),
fans.set_fan_curve(profile.into(), fan_data).await,
);
let _ = handle_weak.upgrade_in_event_loop(move |h| {
let g = h.global::<FanPageData>();
// Update Rust-side cache (isolated from Slint properties)
cache_fan_curve(profile, fan, cache_copy);
match fan {
FanType::CPU => g.set_is_busy_cpu(false),
FanType::GPU => g.set_is_busy_gpu(false),
FanType::Middle => g.set_is_busy_mid(false),
}
});
}); });
}); });
global.on_cancel(move |fan_type, profile| {
let handle_weak = handle_weak_for_cancel.clone();
let _ = handle_weak.upgrade_in_event_loop(move |h: MainWindow| {
let global = h.global::<FanPageData>();
// Retrieve from isolated Rust cache
let nodes_opt = get_cached_fan_curve(profile, fan_type);
if let Some(nodes_vec) = nodes_opt {
use log::info;
info!("Canceling {:?} {:?} - restoring {} nodes from isolated cache", fan_type, profile, nodes_vec.len());
let new_model: slint::ModelRc<Node> = nodes_vec.as_slice().into();
match (profile, fan_type) {
(crate::Profile::Balanced, FanType::CPU) => global.set_balanced_cpu(new_model),
(crate::Profile::Balanced, FanType::GPU) => global.set_balanced_gpu(new_model),
(crate::Profile::Balanced, FanType::Middle) => global.set_balanced_mid(new_model),
(crate::Profile::Performance, FanType::CPU) => global.set_performance_cpu(new_model),
(crate::Profile::Performance, FanType::GPU) => global.set_performance_gpu(new_model),
(crate::Profile::Performance, FanType::Middle) => global.set_performance_mid(new_model),
(crate::Profile::Quiet, FanType::CPU) => global.set_quiet_cpu(new_model),
(crate::Profile::Quiet, FanType::GPU) => global.set_quiet_gpu(new_model),
(crate::Profile::Quiet, FanType::Middle) => global.set_quiet_mid(new_model),
_ => {}
}
} else {
log::warn!("Cancel failed: No cached data for {:?} {:?}", fan_type, profile);
}
});
});
// Initialize warning
if crate::ui::setup_fan_curve_custom::is_custom_fan_supported() {
global.set_show_custom_warning(true);
}
}) { }) {
error!("setup_fan_curve_page: upgrade_in_event_loop: {e:?}"); error!("setup_fan_curve_page: upgrade_in_event_loop: {e:?}");
} }

View File

@@ -0,0 +1,143 @@
use log::{debug, error};
use rog_dbus::zbus_backlight::BacklightProxy;
use slint::ComponentHandle;
use std::sync::{Arc, Mutex};
use crate::config::Config;
use crate::ui::show_toast;
use crate::{MainWindow, ScreenpadPageData};
pub fn setup_screenpad(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
let handle = ui.as_weak();
tokio::spawn(async move {
// Create the connections/proxies here
let conn = match zbus::Connection::system().await {
Ok(conn) => conn,
Err(e) => {
error!("Failed to connect to system bus for Screenpad: {e:}");
return;
}
};
let backlight = match BacklightProxy::builder(&conn).build().await {
Ok(backlight) => backlight,
Err(e) => {
error!("Failed to create backlight proxy for Screenpad: {e:}");
return;
}
};
// Initialize state
debug!("Initializing Screenpad page data");
// Use helper to set initial properties
if let Ok(val) = backlight.screenpad_brightness().await {
handle
.upgrade_in_event_loop(move |h| {
h.global::<ScreenpadPageData>().set_brightness(val);
// Assume power is on if brightness > 0
h.global::<ScreenpadPageData>().set_power(val > 0);
})
.ok();
}
if let Ok(gamma_str) = backlight.screenpad_gamma().await {
if let Ok(gamma) = gamma_str.parse::<f32>() {
handle
.upgrade_in_event_loop(move |h| {
h.global::<ScreenpadPageData>().set_gamma(gamma);
})
.ok();
}
}
if let Ok(sync) = backlight.screenpad_sync_with_primary().await {
handle
.upgrade_in_event_loop(move |h| {
h.global::<ScreenpadPageData>().set_sync_with_primary(sync);
})
.ok();
}
// Set up callbacks
let handle_copy = handle.clone();
let backlight_copy = backlight.clone();
handle
.upgrade_in_event_loop(move |h| {
let global = h.global::<ScreenpadPageData>();
// Brightness Callback
let hl = handle_copy.clone();
let bl = backlight_copy.clone();
global.on_cb_brightness(move |val| {
let bl = bl.clone();
let hl = hl.clone();
tokio::spawn(async move {
show_toast(
format!("Screenpad brightness set to {}", val).into(),
"Failed to set Screenpad brightness".into(),
hl,
bl.set_screenpad_brightness(val).await,
);
});
});
// Gamma Callback
let hl = handle_copy.clone();
let bl = backlight_copy.clone();
global.on_cb_gamma(move |val| {
let bl = bl.clone();
let hl = hl.clone();
tokio::spawn(async move {
show_toast(
format!("Screenpad gamma set to {:.2}", val).into(),
"Failed to set Screenpad gamma".into(),
hl,
bl.set_screenpad_gamma(&val.to_string()).await,
);
});
});
// Sync Callback
let hl = handle_copy.clone();
let bl = backlight_copy.clone();
global.on_cb_sync_with_primary(move |val| {
let bl = bl.clone();
let hl = hl.clone();
tokio::spawn(async move {
show_toast(
format!(
"Screenpad sync {}",
if val { "enabled" } else { "disabled" }
)
.into(),
"Failed to toggle Screenpad sync".into(),
hl,
bl.set_screenpad_sync_with_primary(val).await,
);
});
});
// Power Callback (Toggle brightness to 0/last or 100)
let hl = handle_copy.clone();
let bl = backlight_copy.clone();
global.on_cb_power(move |val| {
let bl = bl.clone();
let hl = hl.clone();
tokio::spawn(async move {
let target = if val { 100 } else { 0 };
let _ = bl.set_screenpad_brightness(target).await;
hl.upgrade_in_event_loop(move |h| {
h.global::<ScreenpadPageData>().set_brightness(target);
})
.ok();
});
});
})
.ok();
// Optional: Value watches for external changes
// (Similar to setup_system.rs if needed)
});
}

View File

@@ -0,0 +1,132 @@
use crate::config::Config;
use crate::set_ui_callbacks;
use crate::ui::show_toast;
use crate::{MainWindow, SlashPageData};
use rog_dbus::{find_iface_async, zbus_slash::SlashProxy};
use rog_slash::SlashMode;
use slint::{ComponentHandle, Model};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
pub fn setup_slash(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
let ui_weak = ui.as_weak();
tokio::spawn(async move {
// Find the Slash interface proxy
let proxies = match find_iface_async::<SlashProxy>("xyz.ljones.Slash").await {
Ok(p) => p,
Err(e) => {
log::warn!("Failed to find Slash interface: {}", e);
return;
}
};
let proxy = match proxies.first() {
Some(p) => p.clone(),
None => return,
};
// UI Callbacks (MUST be done on UI thread to access global state)
{
let proxy_copy = proxy.clone();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let proxy = proxy_copy.clone();
let ui_handle = ui;
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.enabled(), "Slash enabled {}", "Failed to enable slash");
// Fix: Cast f32 (UI) to u8 (DBus)
set_ui_callbacks!(ui_handle, SlashPageData(as f32), proxy.brightness(as u8), "Slash brightness set to {}", "Failed to set slash brightness");
set_ui_callbacks!(ui_handle, SlashPageData(as f32), proxy.interval(as u8), "Slash interval set to {}", "Failed to set slash interval");
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_battery_warning(), "Battery warning set to {}", "Failed to set battery warning");
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_battery(), "Show on battery set to {}", "Failed to set show on battery");
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_boot(), "Show on boot set to {}", "Failed to set show on boot");
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_shutdown(), "Show on shutdown set to {}", "Failed to set show on shutdown");
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_sleep(), "Show on sleep set to {}", "Failed to set show on sleep");
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_lid_closed(), "Show on lid closed set to {}", "Failed to set show on lid closed");
});
}
// Custom Mode Logic - Callback setup
{
let proxy_copy = proxy.clone();
let ui_weak_copy = ui_weak.clone();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let data = ui.global::<SlashPageData>();
data.on_cb_mode_index(move |idx| {
let proxy_copy = proxy_copy.clone();
let handle_weak = ui_weak_copy.clone();
let mode_str_opt = if let Some(h) = handle_weak.upgrade() {
let d = h.global::<SlashPageData>();
if idx >= 0 && (idx as usize) < d.get_modes().row_count() {
Some(d.get_modes().row_data(idx as usize).unwrap_or_default())
} else {
None
}
} else {
None
};
if let Some(mode_str) = mode_str_opt {
if let Ok(mode) = SlashMode::from_str(&mode_str) {
tokio::spawn(async move {
show_toast(
format!("Slash mode set to {}", mode).into(),
"Failed to set slash mode".into(),
handle_weak,
proxy_copy.set_mode(mode).await,
);
});
}
}
});
});
}
// D-Bus Signal -> UI
let proxy_copy = proxy.clone();
let handle_copy = ui_weak.clone();
tokio::spawn(async move {
let mut changes = proxy_copy.receive_mode_changed().await;
use futures_util::StreamExt;
while let Some(change) = changes.next().await {
if let Ok(mode) = change.get().await {
let mode_str = mode.to_string();
let handle_copy = handle_copy.clone();
let _ = slint::invoke_from_event_loop(move || {
if let Some(h) = handle_copy.upgrade() {
let d = h.global::<SlashPageData>();
let model = d.get_modes();
for (i, m) in model.iter().enumerate() {
if m == mode_str {
d.set_mode_index(i as i32);
break;
}
}
}
});
}
}
});
if let Ok(m) = proxy.mode().await {
let mode_str = m.to_string();
let _ = slint::invoke_from_event_loop(move || {
if let Some(h) = ui_weak.upgrade() {
let d = h.global::<SlashPageData>();
let model = d.get_modes();
for (i, m) in model.iter().enumerate() {
if m == mode_str {
d.set_mode_index(i as i32);
break;
}
}
}
});
}
});
}

View File

@@ -0,0 +1,150 @@
use crate::config::Config;
use crate::tray::TrayStats;
use crate::{MainWindow, SystemStatus};
use rog_dbus::zbus_platform::PlatformProxy;
use slint::ComponentHandle;
use std::collections::VecDeque;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tokio::fs;
use tokio::time::Duration;
use zbus::Connection;
pub fn setup_status(
ui: &MainWindow,
_config: Arc<Mutex<Config>>,
stats_tx: tokio::sync::watch::Sender<TrayStats>,
) {
let ui_weak = ui.as_weak();
tokio::spawn(async move {
let mut power_history: VecDeque<i32> = VecDeque::with_capacity(150); // 300s window at 2s poll
// DBus connection for profile
let conn = Connection::system().await.ok();
let platform = if let Some(c) = &conn {
PlatformProxy::new(c).await.ok()
} else {
None
};
loop {
let (cpu_temp, gpu_temp, cpu_fan, gpu_fan) = read_hwmon().await;
let power_microwatts = read_power().await;
// Rolling average logic
if power_history.len() >= 150 {
power_history.pop_front();
}
power_history.push_back(power_microwatts);
let sum: i64 = power_history.iter().map(|&x| x as i64).sum();
let avg_microwatts = if !power_history.is_empty() {
sum / power_history.len() as i64
} else {
0
};
// Convert to Watts
let power_w = power_microwatts as f64 / 1_000_000.0;
let avg_w = avg_microwatts as f64 / 1_000_000.0;
// Fetch profile
let mut profile_str = "Unknown".to_string();
if let Some(p) = &platform {
if let Ok(prof) = p.platform_profile().await {
profile_str = format!("{:?}", prof);
}
}
let ui_weak_loop = ui_weak.clone(); // Clone ui_weak for this iteration
// Send to Tray
let _ = stats_tx.send(TrayStats {
cpu_temp: format!("{}", cpu_temp),
gpu_temp: format!("{}", gpu_temp),
cpu_fan: format!("{}", cpu_fan),
gpu_fan: format!("{}", gpu_fan),
power_w: format!("{:.1}", power_w),
power_profile: profile_str,
});
let _ = slint::invoke_from_event_loop(move || {
if let Some(ui) = ui_weak_loop.upgrade() {
let global = ui.global::<SystemStatus>();
global.set_cpu_temp(cpu_temp);
global.set_gpu_temp(gpu_temp);
global.set_cpu_fan(cpu_fan);
global.set_gpu_fan(gpu_fan);
global.set_power_w(slint::SharedString::from(format!("{:.1}", power_w)));
global.set_power_avg_w(slint::SharedString::from(format!("{:.1}", avg_w)));
}
});
tokio::time::sleep(Duration::from_secs(2)).await;
}
});
}
async fn read_hwmon() -> (i32, i32, i32, i32) {
let mut cpu_temp = 0;
let mut gpu_temp = 0;
let mut cpu_fan = 0;
let mut gpu_fan = 0;
let mut entries = match fs::read_dir("/sys/class/hwmon").await {
Ok(e) => e,
Err(_) => return (0, 0, 0, 0),
};
while let Ok(Some(entry)) = entries.next_entry().await {
let path = entry.path();
let name_path = path.join("name");
if let Ok(name_str) = fs::read_to_string(&name_path).await {
let name = name_str.trim();
if name == "k10temp" || name == "coretemp" || name == "zenpower" {
// Try temp1_input (TCtl/Package)
if let Ok(temp) = read_val(&path.join("temp1_input")).await {
cpu_temp = temp / 1000;
}
} else if name == "amdgpu" || name == "nvidia" {
if let Ok(temp) = read_val(&path.join("temp1_input")).await {
gpu_temp = temp / 1000;
}
} else if name == "asus" || name == "asus_custom_fan_curve" {
if let Ok(fan) = read_val(&path.join("fan1_input")).await {
cpu_fan = fan;
}
if let Ok(fan) = read_val(&path.join("fan2_input")).await {
gpu_fan = fan;
}
}
}
}
(cpu_temp, gpu_temp, cpu_fan, gpu_fan)
}
async fn read_val(path: &PathBuf) -> Result<i32, ()> {
let s = fs::read_to_string(path).await.map_err(|_| ())?;
s.trim().parse::<i32>().map_err(|_| ())
}
async fn read_power() -> i32 {
let mut p = 0;
// Try BAT0 then BAT1
if let Ok(v) = read_val(&PathBuf::from("/sys/class/power_supply/BAT0/power_now")).await {
p = v.abs();
} else if let Ok(v) = read_val(&PathBuf::from("/sys/class/power_supply/BAT1/power_now")).await {
p = v.abs();
}
// Check status
if let Ok(s) = fs::read_to_string("/sys/class/power_supply/BAT0/status").await {
if s.trim() == "Discharging" {
return -p;
}
}
p
}

View File

@@ -0,0 +1,102 @@
use crate::config::Config;
use crate::ui::show_toast;
use crate::{MainWindow, SupergfxPageData};
use slint::{ComponentHandle, Model, SharedString, VecModel};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use zbus::proxy;
#[proxy(
interface = "org.supergfxctl.Daemon",
default_service = "org.supergfxctl.Daemon",
default_path = "/org/supergfxctl/Gfx"
)]
trait Supergfx {
fn supported(&self) -> zbus::Result<Vec<String>>;
fn mode(&self) -> zbus::Result<String>;
fn set_mode(&self, mode: &str) -> zbus::Result<()>;
fn vendor(&self) -> zbus::Result<String>;
}
pub fn setup_supergfx(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
let ui_weak = ui.as_weak();
tokio::spawn(async move {
let conn = match zbus::Connection::system().await {
Ok(c) => c,
Err(e) => {
log::warn!("Failed to connect to system bus: {}", e);
return;
}
};
let proxy = match SupergfxProxy::new(&conn).await {
Ok(p) => p,
Err(e) => {
log::warn!("Failed to create Supergfx proxy: {}", e);
return;
}
};
// Register Callbacks on UI Thread
{
let proxy_copy = proxy.clone();
let ui_weak_copy = ui_weak.clone();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let handle_copy = ui_weak_copy.clone();
ui.global::<SupergfxPageData>()
.on_set_mode(move |mode_str| {
let proxy = proxy_copy.clone();
let handle = handle_copy.clone();
tokio::spawn(async move {
show_toast(
format!("Switching to {}. Logout required.", mode_str).into(),
"Failed to set mode".into(),
handle,
proxy.set_mode(&mode_str).await,
);
});
});
});
}
// Fetch Initial State
// Vendor
if let Ok(vendor) = proxy.vendor().await {
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
ui.global::<SupergfxPageData>().set_vendor(vendor.into())
});
}
// Supported Modes
if let Ok(supported) = proxy.supported().await {
let modes: Vec<SharedString> = supported
.iter()
.map(|s| SharedString::from(s.as_str()))
.collect();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let mode_model = Rc::new(VecModel::from(modes));
ui.global::<SupergfxPageData>()
.set_supported_modes(mode_model.into())
});
}
// Current Mode
if let Ok(mode) = proxy.mode().await {
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let g = ui.global::<SupergfxPageData>();
g.set_current_mode(mode.clone().into());
// Update selection index
let model = g.get_supported_modes();
for (i, m) in model.iter().enumerate() {
if m == mode.as_str() {
g.set_selected_index(i as i32);
break;
}
}
});
}
// No signal monitoring implemented as supergfxctl state changes usually require user action/logout
});
}

View File

@@ -40,10 +40,13 @@ pub fn setup_system_page(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
ui.global::<SystemPageData>().set_platform_profile(-1); ui.global::<SystemPageData>().set_platform_profile(-1);
ui.global::<SystemPageData>().set_panel_overdrive(-1); ui.global::<SystemPageData>().set_panel_overdrive(-1);
ui.global::<SystemPageData>().set_boot_sound(-1); ui.global::<SystemPageData>().set_boot_sound(-1);
ui.global::<SystemPageData>().set_kbd_leds_awake(-1);
ui.global::<SystemPageData>().set_kbd_leds_sleep(-1);
ui.global::<SystemPageData>().set_kbd_leds_boot(-1);
ui.global::<SystemPageData>().set_kbd_leds_shutdown(-1);
ui.global::<SystemPageData>().set_screen_auto_brightness(-1); ui.global::<SystemPageData>().set_screen_auto_brightness(-1);
ui.global::<SystemPageData>().set_mcu_powersave(-1); ui.global::<SystemPageData>().set_mcu_powersave(-1);
ui.global::<SystemPageData>().set_mini_led_mode(-1); ui.global::<SystemPageData>().set_mini_led_mode(-1);
ui.global::<SystemPageData>().set_screenpad_brightness(-1);
ui.global::<SystemPageData>().set_ppt_pl1_spl(MINMAX); ui.global::<SystemPageData>().set_ppt_pl1_spl(MINMAX);
ui.global::<SystemPageData>().set_ppt_pl2_sppt(MINMAX); ui.global::<SystemPageData>().set_ppt_pl2_sppt(MINMAX);
ui.global::<SystemPageData>().set_ppt_pl3_fppt(MINMAX); ui.global::<SystemPageData>().set_ppt_pl3_fppt(MINMAX);
@@ -292,7 +295,7 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
log::error!("Failed to create platform proxy: {}", e); log::error!("Failed to create platform proxy: {}", e);
}) })
.unwrap(); .unwrap();
let backlight = BacklightProxy::builder(&conn) let _backlight = BacklightProxy::builder(&conn)
.build() .build()
.await .await
.map_err(|e| { .map_err(|e| {
@@ -393,23 +396,7 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
set_ui_props_async!(handle, platform, SystemPageData, enable_ppt_group); set_ui_props_async!(handle, platform, SystemPageData, enable_ppt_group);
set_ui_props_async!(handle, backlight, SystemPageData, screenpad_brightness); set_ui_props_async!(handle, platform, SystemPageData, enable_ppt_group);
if let Ok(value) = backlight.screenpad_gamma().await {
handle
.upgrade_in_event_loop(move |handle| {
handle
.global::<SystemPageData>()
.set_screenpad_gamma(value.parse().unwrap_or(1.0));
})
.ok();
}
set_ui_props_async!(
handle,
backlight,
SystemPageData,
screenpad_sync_with_primary
);
let platform_copy = platform.clone(); let platform_copy = platform.clone();
handle handle
@@ -532,25 +519,11 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
"Setting Throttle policy on AC failed" "Setting Throttle policy on AC failed"
); );
set_ui_callbacks!(handle,
SystemPageData(as i32),
backlight.screenpad_brightness(as i32),
"Screenpad successfully set to {}",
"Setting screenpad brightness failed"
);
set_ui_callbacks!(handle, set_ui_callbacks!(handle,
SystemPageData(as bool), SystemPageData(as bool),
backlight.screenpad_sync_with_primary(as bool), platform_copy.change_platform_profile_on_battery(.into()),
"Screenpad successfully set to {}", "Throttle policy on battery enabled: {}",
"Setting screenpad brightness failed" "Setting Throttle policy on AC failed"
);
set_ui_callbacks!(handle,
SystemPageData(.parse().unwrap_or(1.0)),
backlight.screenpad_gamma(.to_string().as_str()),
"Screenpad successfully set to {}",
"Setting screenpad brightness failed"
); );
}) })
.ok(); .ok();
@@ -669,6 +642,26 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
setup_callback!(boot_sound, handle, attr, i32); setup_callback!(boot_sound, handle, attr, i32);
setup_external!(boot_sound, i32, handle, attr, value) setup_external!(boot_sound, i32, handle, attr, value)
} }
FirmwareAttribute::KbdLedsAwake => {
init_property!(kbd_leds_awake, handle, value, i32);
setup_callback!(kbd_leds_awake, handle, attr, i32);
setup_external!(kbd_leds_awake, i32, handle, attr, value)
}
FirmwareAttribute::KbdLedsSleep => {
init_property!(kbd_leds_sleep, handle, value, i32);
setup_callback!(kbd_leds_sleep, handle, attr, i32);
setup_external!(kbd_leds_sleep, i32, handle, attr, value)
}
FirmwareAttribute::KbdLedsBoot => {
init_property!(kbd_leds_boot, handle, value, i32);
setup_callback!(kbd_leds_boot, handle, attr, i32);
setup_external!(kbd_leds_boot, i32, handle, attr, value)
}
FirmwareAttribute::KbdLedsShutdown => {
init_property!(kbd_leds_shutdown, handle, value, i32);
setup_callback!(kbd_leds_shutdown, handle, attr, i32);
setup_external!(kbd_leds_shutdown, i32, handle, attr, value)
}
FirmwareAttribute::ScreenAutoBrightness => { FirmwareAttribute::ScreenAutoBrightness => {
init_property!(screen_auto_brightness, handle, value, i32); init_property!(screen_auto_brightness, handle, value, i32);
setup_callback!(screen_auto_brightness, handle, attr, i32); setup_callback!(screen_auto_brightness, handle, attr, i32);

View File

@@ -1,9 +1,12 @@
import { Palette, Button, VerticalBox } from "std-widgets.slint"; import { Button, VerticalBox } from "std-widgets.slint";
import { AppSize } from "globals.slint"; import { AppSize } from "globals.slint";
import { PageSystem, SystemPageData, AttrMinMax } from "pages/system.slint"; import { PageSystem, SystemPageData, AttrMinMax } from "pages/system.slint";
import { SideBar } from "widgets/sidebar.slint"; import { SideBar } from "widgets/sidebar.slint";
import { PageAbout } from "pages/about.slint"; import { PageAbout } from "pages/about.slint";
import { PageFans } from "pages/fans.slint"; import { PageFans } from "pages/fans.slint";
import { PageSlash, SlashPageData } from "pages/slash.slint";
import { PageSupergfx, SupergfxPageData } from "pages/supergfx.slint";
import { PageScreenpad, ScreenpadPageData } from "pages/screenpad.slint";
import { PageAnime, AnimePageData } from "pages/anime.slint"; import { PageAnime, AnimePageData } from "pages/anime.slint";
import { RogItem } from "widgets/common.slint"; import { RogItem } from "widgets/common.slint";
import { PageAura } from "pages/aura.slint"; import { PageAura } from "pages/aura.slint";
@@ -14,8 +17,18 @@ export { FanPageData, FanType, Profile }
import { AuraPageData, AuraDevType, LaptopAuraPower, AuraPowerState, PowerZones, AuraEffect } from "types/aura_types.slint"; import { AuraPageData, AuraDevType, LaptopAuraPower, AuraPowerState, PowerZones, AuraEffect } from "types/aura_types.slint";
export { AuraPageData, AuraDevType, LaptopAuraPower, AuraPowerState, PowerZones, AuraEffect } export { AuraPageData, AuraDevType, LaptopAuraPower, AuraPowerState, PowerZones, AuraEffect }
import { PageAppSettings, AppSettingsPageData } from "pages/app_settings.slint"; import { PageAppSettings, AppSettingsPageData } from "pages/app_settings.slint";
import { StatusBar, SystemStatus } from "widgets/status_bar.slint";
import { TrayTooltip } from "windows/tray_tooltip.slint";
export { TrayTooltip }
export { AppSize, AttrMinMax, SystemPageData, AnimePageData, AppSettingsPageData } import { RogPalette } from "themes/rog_theme.slint";
export { AppSize, AttrMinMax, SystemPageData, AnimePageData, AppSettingsPageData, SystemStatus, SlashPageData, SupergfxPageData, ScreenpadPageData }
export global SomeError {
in property <string> error_message: "";
in property <string> error_help: "";
}
export component MainWindow inherits Window { export component MainWindow inherits Window {
title: "ROG Control"; title: "ROG Control";
@@ -24,93 +37,133 @@ export component MainWindow inherits Window {
default-font-size: 14px; default-font-size: 14px;
default-font-weight: 400; default-font-weight: 400;
icon: @image-url("../data/rog-control-center.png"); icon: @image-url("../data/rog-control-center.png");
in property <[bool]> sidebar_items_avilable: [true, true, true, true, true, true]; in property <[bool]> sidebar_items_avilable: [true, true, true, true, true, true, true, true, true];
private property <bool> show_notif; private property <bool> show_notif;
private property <bool> fade_cover; private property <bool> fade_cover;
private property <bool> toast: false; private property <bool> toast: false;
private property <string> toast_text: "I show when something is waiting"; private property <string> toast_text: "I show when something is waiting";
callback show_toast(string); callback show_toast(string);
callback start_toast_timer();
callback hide_toast();
hide_toast() => {
toast = false;
}
show_toast(text) => { show_toast(text) => {
toast = text != ""; toast = text != "";
toast_text = text; toast_text = text;
if (toast) {
start_toast_timer();
}
} }
callback exit-app(); callback exit-app();
callback show_notification(bool); callback show_notification(bool);
show_notification(yes) => { show_notification(yes) => {
show_notif = yes; show_notif = yes;
fade_cover = yes; fade_cover = yes;
} }
callback external_colour_change(); callback external_colour_change();
external_colour_change() => { external_colour_change() => {
aura.external_colour_change(); aura.external_colour_change();
aura.external_colour_change(); aura.external_colour_change();
} }
min-height: AppSize.height; min-height: AppSize.height;
min-width: AppSize.width; min-width: AppSize.width;
background: Colors.black; background: RogPalette.background;
HorizontalLayout {
padding: 0px;
VerticalLayout {
side-bar := SideBar {
title: @tr("ROG");
model: [
@tr("Menu1" => "System Control"),
@tr("Menu2" => "Keyboard Aura"),
@tr("Menu3" => "AniMe Matrix"),
@tr("Menu4" => "Fan Curves"),
@tr("Menu5" => "App Settings"),
@tr("Menu6" => "About"),
];
available: root.sidebar_items_avilable;
}
Rectangle { VerticalLayout {
max-height: 40px; HorizontalLayout {
width: side-bar.width; padding: 0px;
background: Palette.control-background;
Text { // Left Column: Sidebar + Quit Button
vertical-alignment: center; VerticalLayout {
horizontal-alignment: center; side-bar := SideBar {
text: @tr("Quit App"); title: @tr("ROG");
model: [
@tr("Menu1" => "System Control"),
@tr("Menu2" => "Keyboard Aura"),
@tr("Menu3" => "AniMe Matrix"),
@tr("Menu7" => "Slash Lighting"),
@tr("Menu8" => "Graphics Control"),
@tr("Menu9" => "Screenpad Control"),
@tr("Menu4" => "Fan Curves"),
@tr("Menu5" => "App Settings"),
@tr("Menu6" => "About"),
];
available: root.sidebar_items_avilable;
} }
TouchArea { Rectangle {
clicked => { max-height: 40px;
root.exit-app(); width: side-bar.width;
background: RogPalette.control-background;
Text {
vertical-alignment: center;
horizontal-alignment: center;
text: @tr("Quit App");
color: RogPalette.text-primary;
}
TouchArea {
clicked => {
root.exit-app();
}
} }
} }
} }
}
Rectangle { // Right Column: Content Pages
background: Palette.background; Rectangle {
if(side-bar.current-item == 0): page := PageSystem { background: RogPalette.background;
width: root.width - side-bar.width; if(side-bar.current-item == 0): page := PageSystem {
height: root.height + 12px; width: root.width - side-bar.width;
} height: root.height + 12px;
}
aura := PageAura { aura := PageAura {
width: root.width - side-bar.width; width: root.width - side-bar.width;
visible: side-bar.current-item == 1; visible: side-bar.current-item == 1;
} }
if(side-bar.current-item == 2): PageAnime { if(side-bar.current-item == 2): PageAnime {
width: root.width - side-bar.width; width: root.width - side-bar.width;
} }
fans := PageFans { if(side-bar.current-item == 3): PageSlash {
width: root.width - side-bar.width; width: root.width - side-bar.width;
visible: side-bar.current-item == 3; }
}
if(side-bar.current-item == 4): PageAppSettings { if(side-bar.current-item == 4): PageSupergfx {
width: root.width - side-bar.width; width: root.width - side-bar.width;
} }
if(side-bar.current-item == 5): PageAbout { if(side-bar.current-item == 5): PageScreenpad {
width: root.width - side-bar.width; width: root.width - side-bar.width;
}
fans := PageFans {
width: root.width - side-bar.width;
visible: side-bar.current-item == 6;
}
if(side-bar.current-item == 7): PageAppSettings {
width: root.width - side-bar.width;
}
if(side-bar.current-item == 8): PageAbout {
width: root.width - side-bar.width;
}
} }
} }
// Bottom: Status Bar
StatusBar {}
} }
if fade_cover: Rectangle { if fade_cover: Rectangle {
@@ -118,7 +171,7 @@ export component MainWindow inherits Window {
y: 0px; y: 0px;
width: root.width; width: root.width;
height: root.height; height: root.height;
background: Colors.rgba(25,33,23,20); background: Colors.rgba(0,0,0,0.7);
opacity: 0.7; opacity: 0.7;
TouchArea { TouchArea {
height: 100%; height: 100%;
@@ -133,13 +186,24 @@ export component MainWindow inherits Window {
} }
} }
if toast: Rectangle { // Modern floating toast/snackbar notification
x: 0px; // Shows at the bottom center, non-intrusive
y: 0px; Rectangle {
width: root.width; visible: self.opacity > 0;
height: 32px; opacity: root.toast ? 1 : 0;
opacity: 1.0; animate opacity { duration: 300ms; }
background: Colors.grey;
x: (root.width - 400px) / 2; // Center horizontally
y: root.height - 80px; // Bottom padding
width: 400px;
height: 48px;
border-radius: RogPalette.border-radius;
border-width: 1px;
border-color: RogPalette.accent;
background: RogPalette.control-background;
drop-shadow-blur: 10px;
drop-shadow-color: Colors.black;
TouchArea { TouchArea {
height: 100%; height: 100%;
width: 100%; width: 100%;
@@ -148,13 +212,23 @@ export component MainWindow inherits Window {
} }
} }
Rectangle { HorizontalLayout {
height: 100%; padding-left: 16px;
width: 100%; padding-right: 16px;
background: Palette.control-background; alignment: space-between;
Text { Text {
color: Palette.control-foreground; vertical-alignment: center;
color: RogPalette.text-primary;
text: root.toast_text; text: root.toast_text;
overflow: elide;
}
Text {
vertical-alignment: center;
text: "Dismiss";
color: RogPalette.text-secondary;
font-size: 12px;
} }
} }
} }
@@ -174,13 +248,20 @@ export component MainWindow inherits Window {
} }
} }
// TODO: add properties to display
Rectangle { Rectangle {
height: 100%; height: 100%;
width: 100%; width: 100%;
background: Palette.background; background: RogPalette.control-background;
Text { border-radius: 8px;
text: "Click here to exit";
VerticalLayout {
alignment: center;
Text {
horizontal-alignment: center;
text: "Click here to exit";
color: RogPalette.text-primary;
font-size: 16px;
}
} }
} }
} }
@@ -190,35 +271,45 @@ export component MainWindow inherits Window {
y: 0px; y: 0px;
width: root.width; width: root.width;
height: root.height; height: root.height;
//padding only has effect on layout elements
//padding: 10px;
background: Palette.background; background: RogPalette.background;
border-color: Palette.border; border-color: RogPalette.accent;
border-width: 3px; border-width: 2px;
border-radius: 10px; border-radius: 8px;
VerticalBox { VerticalBox {
RogItem { padding: 20px;
min-height: 50px; spacing: 15px;
max-height: 100px; alignment: center;
Text {
text: "Error";
font-size: 22px;
font-weight: 700;
color: RogPalette.accent;
horizontal-alignment: center;
}
Rectangle {
background: RogPalette.control-background;
border-radius: 8px;
min-height: 60px;
Text { Text {
text <=> SomeError.error_message; text <=> SomeError.error_message;
font-size: 18px; font-size: 16px;
color: RogPalette.text-primary;
horizontal-alignment: center;
vertical-alignment: center;
} }
} }
Text { Text {
text <=> SomeError.error_help; text <=> SomeError.error_help;
horizontal-alignment: TextHorizontalAlignment.center; color: RogPalette.text-secondary;
vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: center;
vertical-alignment: center;
} }
} }
} }
} }
export global SomeError {
in property <string> error_message: "";
in property <string> error_help: "";
}

View File

@@ -1,66 +1,128 @@
import { AboutSlint, VerticalBox, HorizontalBox } from "std-widgets.slint"; import { VerticalBox, HorizontalBox, ScrollView } from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
export component PageAbout inherits VerticalLayout { export component PageAbout inherits Rectangle {
padding: 10px; background: RogPalette.background;
spacing: 10px;
Text {
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
text: "A UI for asusctl made with slint";
font-size: 22px;
}
HorizontalBox { ScrollView {
alignment: LayoutAlignment.center;
VerticalBox { VerticalBox {
alignment: LayoutAlignment.center; padding: 30px;
spacing: 20px;
alignment: center;
// Title
Text { Text {
wrap: TextWrap.word-wrap; horizontal-alignment: center;
text: "You will require a kernel built with my work from here: https://github.com/flukejones/linux"; text: "ROG Control Center";
font-size: 28px;
font-weight: 800;
color: RogPalette.accent;
} }
Text { Text {
vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: center;
horizontal-alignment: TextHorizontalAlignment.center; text: "A modern UI for asusctl built with Slint";
text: "Todo:"; font-size: 16px;
font-size: 22px; color: RogPalette.text-secondary;
} }
Text { // Version info
text: "- [ ] Theme the widgets"; Rectangle {
height: 60px;
background: RogPalette.control-background;
border-radius: 8px;
border-width: 1px;
border-color: RogPalette.control-border;
HorizontalBox {
padding: 15px;
alignment: center;
Text {
text: "Version 6.3.0";
font-size: 14px;
color: RogPalette.text-primary;
}
Text {
text: " | ";
color: RogPalette.text-secondary;
}
Text {
text: "Requires kernel 6.10+";
font-size: 14px;
color: RogPalette.text-secondary;
}
}
} }
Text { // Features section
text: "- [ ] Add a cpu/gpu temp/fan speed info bar"; Rectangle {
background: RogPalette.control-background;
border-radius: 8px;
border-width: 1px;
border-color: RogPalette.control-border;
VerticalBox {
padding: 20px;
spacing: 12px;
Text {
text: "Features";
font-size: 18px;
font-weight: 700;
color: RogPalette.accent;
}
// Completed features
Text { text: "[x] ROG-themed dark UI"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] System status bar (CPU/GPU temps & fan speeds)"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] Power profile management"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] Aura RGB keyboard lighting"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] AniMe Matrix display"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] Slash LED control"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] Supergfx graphics switching"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] Screenpad brightness & gamma"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] Custom fan curves"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] Desktop notifications (KDE OSD)"; color: RogPalette.text-primary; font-size: 13px; }
Text { text: "[x] System tray integration"; color: RogPalette.text-primary; font-size: 13px; }
// Pending features
Rectangle { height: 10px; }
Text { text: "Planned:"; font-size: 14px; font-weight: 600; color: RogPalette.text-secondary; }
Text { text: "[ ] ROG Ally specific settings"; color: RogPalette.text-secondary; font-size: 13px; }
Text { text: "[ ] Advanced Aura zone editing"; color: RogPalette.text-secondary; font-size: 13px; }
}
} }
Text { // Credits
text: "- [ ] Include fan speeds, temps in a bottom bar"; Rectangle {
} background: RogPalette.control-background;
border-radius: 8px;
border-width: 1px;
border-color: RogPalette.control-border;
Text { VerticalBox {
text: "- [ ] Slash control"; padding: 20px;
} spacing: 8px;
Text { Text {
text: "- [ ] Supergfx control"; text: "Credits";
} font-size: 18px;
font-weight: 700;
color: RogPalette.accent;
}
Text { Text {
text: "- [ ] Screenpad controls"; text: "asusctl & asusd by Luke Jones";
} font-size: 13px;
color: RogPalette.text-primary;
Text { }
text: "- [ ] ROG Ally specific settings"; Text {
text: "UI built with Slint";
font-size: 13px;
color: RogPalette.text-secondary;
}
}
} }
} }
} }
Text {
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
text: "Work in progress";
font-size: 22px;
}
} }

View File

@@ -1,5 +1,6 @@
import { SystemDropdown, SystemToggle } from "../widgets/common.slint"; import { SystemDropdown, SystemToggle } from "../widgets/common.slint";
import { Palette, GroupBox, VerticalBox, Button, HorizontalBox } from "std-widgets.slint"; import { Palette, GroupBox, VerticalBox, Button, HorizontalBox } from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
export global AnimePageData { export global AnimePageData {
in-out property <[string]> brightness_names: [ in-out property <[string]> brightness_names: [
@@ -109,7 +110,7 @@ export component PageAnime inherits Rectangle {
if root.show_fade_cover: Rectangle { if root.show_fade_cover: Rectangle {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: Palette.background; background: RogPalette.background;
opacity: 0.8; opacity: 0.8;
TouchArea { TouchArea {
height: 100%; height: 100%;
@@ -142,7 +143,7 @@ export component PageAnime inherits Rectangle {
alignment: LayoutAlignment.start; alignment: LayoutAlignment.start;
Text { Text {
font-size: 18px; font-size: 18px;
color: Palette.control-foreground; color: RogPalette.text-primary;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
text: @tr("Set which builtin animations are played"); text: @tr("Set which builtin animations are played");
} }
@@ -216,7 +217,7 @@ export component PageAnime inherits Rectangle {
alignment: LayoutAlignment.start; alignment: LayoutAlignment.start;
Text { Text {
font-size: 18px; font-size: 18px;
color: Palette.control-foreground; color: RogPalette.text-primary;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
text: @tr("Advanced Display Settings"); text: @tr("Advanced Display Settings");
} }

View File

@@ -1,5 +1,6 @@
import { Palette } from "std-widgets.slint"; import { VerticalBox, ScrollView, HorizontalBox, Button } from "std-widgets.slint";
import { SystemToggle } from "../widgets/common.slint"; import { SystemToggle, RogItem } from "../widgets/common.slint";
import { RogPalette } from "../themes/rog_theme.slint";
export global AppSettingsPageData { export global AppSettingsPageData {
in-out property <bool> run_in_background; in-out property <bool> run_in_background;
@@ -8,56 +9,134 @@ export global AppSettingsPageData {
callback set_startup_in_background(bool); callback set_startup_in_background(bool);
in-out property <bool> enable_tray_icon; in-out property <bool> enable_tray_icon;
callback set_enable_tray_icon(bool); callback set_enable_tray_icon(bool);
in-out property <bool> enable_dgpu_notifications;
callback set_enable_dgpu_notifications(bool); // Master notification toggle
in-out property <bool> notifications_enabled;
callback set_notifications_enabled(bool);
// Granular notification toggles
in-out property <bool> notify_gfx_switch;
callback set_notify_gfx_switch(bool);
in-out property <bool> notify_gfx_status;
callback set_notify_gfx_status(bool);
in-out property <bool> notify_platform_profile;
callback set_notify_platform_profile(bool);
} }
export component PageAppSettings inherits VerticalLayout { export component PageAppSettings inherits Rectangle {
Rectangle { background: RogPalette.background;
clip: true;
// TODO: slow with border-radius ScrollView {
//padding only has effect on layout elements VerticalBox {
//padding: 8px; padding: 20px;
spacing: 20px;
alignment: start;
// height: parent.height - infobar.height - mainview.padding - self.padding * 2; // General Section
// TODO: border-radius: 8px; VerticalBox {
mainview := VerticalLayout { spacing: 10px;
padding: 10px; padding: 0px;
spacing: 10px;
SystemToggle { Rectangle {
text: @tr("Run in background after closing"); height: 30px;
checked <=> AppSettingsPageData.run_in_background; background: RogPalette.control-background;
toggled => { border-radius: 4px;
AppSettingsPageData.set_run_in_background(AppSettingsPageData.run_in_background) border-width: 1px;
border-color: RogPalette.control-border;
Text {
x: 10px;
vertical-alignment: center;
text: "General Settings";
color: RogPalette.accent;
font-weight: 700;
}
}
SystemToggle {
text: @tr("Run in background after closing");
checked <=> AppSettingsPageData.run_in_background;
toggled => {
AppSettingsPageData.set_run_in_background(AppSettingsPageData.run_in_background)
}
}
SystemToggle {
text: @tr("Start app in background (UI closed)");
checked <=> AppSettingsPageData.startup_in_background;
toggled => {
AppSettingsPageData.set_startup_in_background(AppSettingsPageData.startup_in_background)
}
}
SystemToggle {
text: @tr("Enable system tray icon");
checked <=> AppSettingsPageData.enable_tray_icon;
toggled => {
AppSettingsPageData.set_enable_tray_icon(AppSettingsPageData.enable_tray_icon)
}
} }
} }
SystemToggle { // Notifications Section
text: @tr("Start app in background (UI closed)"); VerticalBox {
checked <=> AppSettingsPageData.startup_in_background; spacing: 10px;
toggled => { padding: 0px;
AppSettingsPageData.set_startup_in_background(AppSettingsPageData.startup_in_background)
}
}
SystemToggle { Rectangle {
text: @tr("Enable system tray icon"); height: 30px;
checked <=> AppSettingsPageData.enable_tray_icon; background: RogPalette.control-background;
toggled => { border-radius: 4px;
AppSettingsPageData.set_enable_tray_icon(AppSettingsPageData.enable_tray_icon) border-width: 1px;
} border-color: RogPalette.control-border;
}
SystemToggle { Text {
text: @tr("Enable dGPU notifications"); x: 10px;
checked <=> AppSettingsPageData.enable_dgpu_notifications; vertical-alignment: center;
toggled => { text: "Notifications";
AppSettingsPageData.set_enable_dgpu_notifications(AppSettingsPageData.enable_dgpu_notifications) color: RogPalette.accent;
font-weight: 700;
}
}
SystemToggle {
text: @tr("Enable Notifications");
checked <=> AppSettingsPageData.notifications_enabled;
toggled => {
AppSettingsPageData.set_notifications_enabled(AppSettingsPageData.notifications_enabled)
}
}
// Sub-toggles container
VerticalBox {
padding-left: 30px; // Indent
spacing: 10px;
visible: AppSettingsPageData.notifications_enabled;
SystemToggle {
text: @tr("Notify on Graphics Switch");
checked <=> AppSettingsPageData.notify_gfx_switch;
toggled => {
AppSettingsPageData.set_notify_gfx_switch(AppSettingsPageData.notify_gfx_switch)
}
}
SystemToggle {
text: @tr("Notify on GPU Status Change");
checked <=> AppSettingsPageData.notify_gfx_status;
toggled => {
AppSettingsPageData.set_notify_gfx_status(AppSettingsPageData.notify_gfx_status)
}
}
SystemToggle {
text: @tr("Notify on Power Profile Change");
checked <=> AppSettingsPageData.notify_platform_profile;
toggled => {
AppSettingsPageData.set_notify_platform_profile(AppSettingsPageData.notify_platform_profile)
}
}
} }
}
Text {
text: "WIP: some features like notifications are not complete";
} }
} }
} }

View File

@@ -1,5 +1,6 @@
import { SystemDropdown, RogItem, SystemToggle, SystemToggleVert } from "../widgets/common.slint"; import { SystemDropdown, RogItem, SystemToggle, SystemToggleVert } from "../widgets/common.slint";
import { Palette, Button, ComboBox, VerticalBox, GroupBox } from "std-widgets.slint"; import { Button, ComboBox, VerticalBox, GroupBox } from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
import { StyleMetrics, Slider, HorizontalBox, TextEdit, SpinBox, LineEdit, ScrollView } from "std-widgets.slint"; import { StyleMetrics, Slider, HorizontalBox, TextEdit, SpinBox, LineEdit, ScrollView } from "std-widgets.slint";
import { ColourSlider } from "../widgets/colour_picker.slint"; import { ColourSlider } from "../widgets/colour_picker.slint";
import { AuraPageData, AuraDevType, PowerZones, LaptopAuraPower, AuraEffect } from "../types/aura_types.slint"; import { AuraPageData, AuraDevType, PowerZones, LaptopAuraPower, AuraEffect } from "../types/aura_types.slint";
@@ -183,6 +184,57 @@ export component PageAura inherits Rectangle {
} }
} }
// Software Animation Controls (for Static-only keyboards)
if AuraPageData.soft_animation_available: RogItem {
min-height: 100px;
VerticalLayout {
padding: 10px;
spacing: 8px;
Text {
text: @tr("Software Animation (Static-only keyboards)");
font-size: 14px;
font-weight: 600;
color: RogPalette.accent;
}
HorizontalLayout {
spacing: 20px;
VerticalLayout {
Text {
text: @tr("Animation Mode");
color: RogPalette.text-secondary;
}
ComboBox {
current_index <=> AuraPageData.soft_animation_mode;
current_value: AuraPageData.soft_animation_modes[self.current-index];
model <=> AuraPageData.soft_animation_modes;
selected => {
AuraPageData.cb_soft_animation_mode(AuraPageData.soft_animation_mode);
}
}
}
VerticalLayout {
horizontal-stretch: 1;
Text {
text: @tr("Speed: ") + Math.round(AuraPageData.soft_animation_speed) + "ms";
color: RogPalette.text-secondary;
}
Slider {
minimum: 150;
maximum: 1000;
value <=> AuraPageData.soft_animation_speed;
released => {
AuraPageData.cb_soft_animation_speed(Math.round(AuraPageData.soft_animation_speed));
}
}
}
}
}
}
HorizontalLayout { HorizontalLayout {
Button { Button {
text: @tr("Power Settings"); text: @tr("Power Settings");
@@ -195,11 +247,15 @@ export component PageAura inherits Rectangle {
} }
if root.show_fade_cover: Rectangle { if root.show_fade_cover: Rectangle {
background: Palette.background; background: RogPalette.background;
opacity: 0.8; opacity: 0.8;
TouchArea { TouchArea {
height: 100%; height: 100%;
width: 100%; width: 100%;
clicked => {
root.show_fade_cover = false;
root.show_aura_power = false;
}
} }
} }
} }
@@ -266,7 +322,10 @@ export component PageAura inherits Rectangle {
alignment: LayoutAlignment.start; alignment: LayoutAlignment.start;
Text { Text {
text: "TODO: In progress"; text: "LED Power Zones (Legacy)";
font-size: 16px;
font-weight: 600;
color: #ff0033;
} }
for state[idx] in AuraPageData.led_power.states: old_zone := AuraPowerGroupOld { for state[idx] in AuraPageData.led_power.states: old_zone := AuraPowerGroupOld {

View File

@@ -1,7 +1,10 @@
import { Palette, TabWidget, Button, CheckBox } from "std-widgets.slint"; import { Palette, TabWidget, Button, CheckBox, Slider } from "std-widgets.slint";
import { Graph, Node } from "../widgets/graph.slint"; import { Graph, Node } from "../widgets/graph.slint";
import { SystemToggle } from "../widgets/common.slint"; import { SystemToggle } from "../widgets/common.slint";
import { Profile, FanType, FanPageData } from "../types/fan_types.slint"; import { Profile, FanType, FanPageData } from "../types/fan_types.slint";
import { RogPalette } from "../themes/rog_theme.slint";
component FanTab inherits Rectangle { component FanTab inherits Rectangle {
in-out property <bool> enabled: false; in-out property <bool> enabled: false;
@@ -16,10 +19,81 @@ component FanTab inherits Rectangle {
in-out property <[Node]> nodes; in-out property <[Node]> nodes;
VerticalLayout { VerticalLayout {
private property <bool> local_busy:
(root.fan_type == FanType.CPU && FanPageData.is_busy_cpu) ||
(root.fan_type == FanType.GPU && FanPageData.is_busy_gpu) ||
(root.fan_type == FanType.Middle && FanPageData.is_busy_mid);
if FanPageData.show_custom_warning: Rectangle {
background: RogPalette.control-background;
border-radius: 4px;
height: 48px;
HorizontalLayout {
padding: 10px;
Text {
color: #ffd700; // Gold/Yellow
text: @tr("Zero RPM Mode Enabled: Fans will take ~25s to spin down entirely.");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
wrap: word-wrap;
}
}
}
HorizontalLayout { HorizontalLayout {
spacing: 10px;
if root.tab_enabled: Graph { if root.tab_enabled: Graph {
nodes <=> root.nodes; nodes <=> root.nodes;
} }
if root.tab_enabled: VerticalLayout {
width: 40px;
alignment: center;
spacing: 10px;
Button {
text: "+";
height: 40px;
width: 40px;
clicked => {
root.nodes = [
{ x: root.nodes[0].x, y: min(255px, root.nodes[0].y + 13px) },
{ x: root.nodes[1].x, y: min(255px, root.nodes[1].y + 13px) },
{ x: root.nodes[2].x, y: min(255px, root.nodes[2].y + 13px) },
{ x: root.nodes[3].x, y: min(255px, root.nodes[3].y + 13px) },
{ x: root.nodes[4].x, y: min(255px, root.nodes[4].y + 13px) },
{ x: root.nodes[5].x, y: min(255px, root.nodes[5].y + 13px) },
{ x: root.nodes[6].x, y: min(255px, root.nodes[6].y + 13px) },
{ x: root.nodes[7].x, y: min(255px, root.nodes[7].y + 13px) }
];
}
}
Text {
text: "All";
font-size: 10px;
horizontal-alignment: center;
color: white;
}
Button {
text: "-";
height: 40px;
width: 40px;
clicked => {
root.nodes = [
{ x: root.nodes[0].x, y: max(0px, root.nodes[0].y - 13px) },
{ x: root.nodes[1].x, y: max(0px, root.nodes[1].y - 13px) },
{ x: root.nodes[2].x, y: max(0px, root.nodes[2].y - 13px) },
{ x: root.nodes[3].x, y: max(0px, root.nodes[3].y - 13px) },
{ x: root.nodes[4].x, y: max(0px, root.nodes[4].y - 13px) },
{ x: root.nodes[5].x, y: max(0px, root.nodes[5].y - 13px) },
{ x: root.nodes[6].x, y: max(0px, root.nodes[6].y - 13px) },
{ x: root.nodes[7].x, y: max(0px, root.nodes[7].y - 13px) }
];
}
}
}
if !root.tab_enabled: Rectangle { if !root.tab_enabled: Rectangle {
Text { Text {
font-size: 24px; font-size: 24px;
@@ -29,19 +103,20 @@ component FanTab inherits Rectangle {
} }
HorizontalLayout { HorizontalLayout {
spacing: 20px;
alignment: LayoutAlignment.end; alignment: LayoutAlignment.end;
CheckBox { CheckBox {
text: @tr("Enabled"); text: @tr("Enable Manual Control");
checked <=> root.enabled; checked <=> root.enabled;
enabled <=> root.tab_enabled; enabled: root.tab_enabled && !local_busy;
toggled => { toggled => {
root.toggled(); root.toggled();
} }
} }
Button { Button {
text: @tr("Apply"); text: local_busy ? @tr("Applying...") : @tr("Apply Curve");
enabled <=> root.tab_enabled; enabled: root.tab_enabled && root.enabled && !local_busy;
clicked => { clicked => {
root.apply(); root.apply();
} }
@@ -49,7 +124,7 @@ component FanTab inherits Rectangle {
Button { Button {
text: @tr("Cancel"); text: @tr("Cancel");
enabled <=> root.tab_enabled; enabled: root.tab_enabled && !local_busy;
clicked => { clicked => {
root.cancel() root.cancel()
} }
@@ -57,7 +132,7 @@ component FanTab inherits Rectangle {
Button { Button {
text: @tr("Factory Default (all fans)"); text: @tr("Factory Default (all fans)");
enabled <=> root.tab_enabled; enabled: root.tab_enabled && !local_busy;
clicked => { clicked => {
root.default(); root.default();
} }
@@ -86,6 +161,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Balanced); FanPageData.set_profile_default(Profile.Balanced);
} }
cancel => {
FanPageData.cancel(FanType.CPU, Profile.Balanced);
}
} }
} }
@@ -104,6 +182,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Balanced); FanPageData.set_profile_default(Profile.Balanced);
} }
cancel => {
FanPageData.cancel(FanType.Middle, Profile.Balanced);
}
} }
} }
@@ -122,6 +203,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Balanced); FanPageData.set_profile_default(Profile.Balanced);
} }
cancel => {
FanPageData.cancel(FanType.GPU, Profile.Balanced);
}
} }
} }
} }
@@ -145,6 +229,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Performance); FanPageData.set_profile_default(Profile.Performance);
} }
cancel => {
FanPageData.cancel(FanType.CPU, Profile.Performance);
}
} }
} }
@@ -163,6 +250,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Performance); FanPageData.set_profile_default(Profile.Performance);
} }
cancel => {
FanPageData.cancel(FanType.Middle, Profile.Performance);
}
} }
} }
@@ -181,6 +271,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Performance); FanPageData.set_profile_default(Profile.Performance);
} }
cancel => {
FanPageData.cancel(FanType.GPU, Profile.Performance);
}
} }
} }
} }
@@ -204,6 +297,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Quiet); FanPageData.set_profile_default(Profile.Quiet);
} }
cancel => {
FanPageData.cancel(FanType.CPU, Profile.Quiet);
}
} }
} }
@@ -222,6 +318,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Quiet); FanPageData.set_profile_default(Profile.Quiet);
} }
cancel => {
FanPageData.cancel(FanType.Middle, Profile.Quiet);
}
} }
} }
@@ -240,6 +339,9 @@ export component PageFans inherits VerticalLayout {
default => { default => {
FanPageData.set_profile_default(Profile.Quiet); FanPageData.set_profile_default(Profile.Quiet);
} }
cancel => {
FanPageData.cancel(FanType.GPU, Profile.Quiet);
}
} }
} }
} }

View File

@@ -0,0 +1,102 @@
import { Button, VerticalBox, Slider, Switch } from "std-widgets.slint";
import { ScreenpadPageData } from "../types/screenpad_types.slint";
import { RogPalette } from "../themes/rog_theme.slint";
import { RogItem, SystemSlider } from "../widgets/common.slint";
export { ScreenpadPageData }
export component PageScreenpad inherits Rectangle {
background: RogPalette.background;
VerticalBox {
alignment: LayoutAlignment.start;
padding: 20px;
spacing: 20px;
Text {
text: @tr("Screenpad Controls");
font-size: 24px;
font-weight: 700;
color: RogPalette.accent;
}
RogItem {
HorizontalLayout {
padding: 15px;
spacing: 20px;
alignment: LayoutAlignment.space-between;
Text {
text: @tr("Enable Screenpad");
font-size: 16px;
vertical-alignment: TextVerticalAlignment.center;
color: RogPalette.text-primary;
}
Switch {
checked <=> ScreenpadPageData.power;
toggled => {
ScreenpadPageData.cb_power(self.checked);
}
}
}
}
VerticalLayout {
spacing: 15px;
// Brightness Slider
SystemSlider {
enabled: ScreenpadPageData.power;
text: @tr("Brightness");
minimum: 0;
maximum: 255;
value: ScreenpadPageData.brightness;
help_text: ScreenpadPageData.brightness == -1 ? @tr("Not available") : "";
released => {
ScreenpadPageData.cb_brightness(Math.round(self.value));
}
}
// Gamma Slider (New)
SystemSlider {
enabled: ScreenpadPageData.power;
text: @tr("Gamma");
minimum: 0.1;
maximum: 2.5;
value: ScreenpadPageData.gamma;
help_text: @tr("Adjust color intensity");
released => {
ScreenpadPageData.cb_gamma(self.value);
}
}
RogItem {
enabled: ScreenpadPageData.power;
HorizontalLayout {
padding: 15px;
spacing: 20px;
alignment: LayoutAlignment.space-between;
Text {
text: @tr("Sync with Primary Display");
font-size: 16px;
vertical-alignment: TextVerticalAlignment.center;
color: RogPalette.text-primary;
}
Switch {
enabled: ScreenpadPageData.power;
checked <=> ScreenpadPageData.sync_with_primary;
toggled => {
ScreenpadPageData.cb_sync_with_primary(self.checked);
}
}
}
}
}
// Spacer
Rectangle {}
}
}

View File

@@ -0,0 +1,114 @@
import { SystemToggle, SystemSlider, SystemDropdown, RogItem } from "../widgets/common.slint";
import { VerticalBox, ScrollView, GroupBox } from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
import { SlashPageData } from "../types/slash_types.slint";
export { SlashPageData }
export component PageSlash inherits Rectangle {
background: RogPalette.background;
ScrollView {
VerticalBox {
padding: 20px;
spacing: 20px;
alignment: start;
// Header
Rectangle {
height: 40px;
background: RogPalette.control-background;
border-radius: RogPalette.border-radius;
border-width: 1px;
border-color: RogPalette.control-border;
Text {
text: @tr("Slash Lighting Control");
color: RogPalette.accent;
font-size: 18px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
// Main Control
RogItem {
VerticalBox {
SystemToggle {
text: @tr("Enable Slash Lighting");
checked <=> SlashPageData.enabled;
toggled => { SlashPageData.cb_enabled(self.checked); }
}
SystemDropdown {
text: @tr("Lighting Mode");
model <=> SlashPageData.modes;
current_index <=> SlashPageData.mode_index;
current_value: SlashPageData.modes[SlashPageData.mode_index];
selected => {
SlashPageData.cb_mode_index(self.current_index);
}
}
SystemSlider {
title: @tr("Brightness");
text: @tr("Brightness");
value <=> SlashPageData.brightness;
minimum: 0;
maximum: 255;
help_text: "";
released(val) => { SlashPageData.cb_brightness(val); }
}
SystemSlider {
title: @tr("Interval / Speed");
text: @tr("Interval / Speed");
value <=> SlashPageData.interval;
minimum: 0;
maximum: 255;
help_text: "";
released(val) => { SlashPageData.cb_interval(val); }
}
}
}
// Behaviors
GroupBox {
title: @tr("Behavior Settings");
VerticalBox {
SystemToggle {
text: @tr("Show Battery Warning");
checked <=> SlashPageData.show_battery_warning;
toggled => { SlashPageData.cb_show_battery_warning(self.checked); }
}
SystemToggle {
text: @tr("Active on Battery");
checked <=> SlashPageData.show_on_battery;
toggled => { SlashPageData.cb_show_on_battery(self.checked); }
}
SystemToggle {
text: @tr("Active on Boot");
checked <=> SlashPageData.show_on_boot;
toggled => { SlashPageData.cb_show_on_boot(self.checked); }
}
SystemToggle {
text: @tr("Active on Shutdown");
checked <=> SlashPageData.show_on_shutdown;
toggled => { SlashPageData.cb_show_on_shutdown(self.checked); }
}
SystemToggle {
text: @tr("Active on Sleep");
checked <=> SlashPageData.show_on_sleep;
toggled => { SlashPageData.cb_show_on_sleep(self.checked); }
}
SystemToggle {
text: @tr("Active when Lid Closed");
checked <=> SlashPageData.show_on_lid_closed;
toggled => { SlashPageData.cb_show_on_lid_closed(self.checked); }
}
}
}
}
}
}

View File

@@ -0,0 +1,73 @@
import { SystemDropdown, RogItem } from "../widgets/common.slint";
import { VerticalBox, ScrollView, HorizontalBox } from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
import { SupergfxPageData } from "../types/supergfx_types.slint";
export { SupergfxPageData }
export component PageSupergfx inherits Rectangle {
background: RogPalette.background;
ScrollView {
VerticalBox {
padding: 20px;
spacing: 20px;
alignment: start;
// Header
Rectangle {
height: 40px;
background: RogPalette.control-background;
border-radius: RogPalette.border-radius;
border-width: 1px;
border-color: RogPalette.control-border;
Text {
text: @tr("Graphics Control (supergfx)");
color: RogPalette.accent;
font-size: 18px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
RogItem {
HorizontalBox {
Text {
text: @tr("Vendor: ") + SupergfxPageData.vendor;
color: RogPalette.text-secondary;
vertical-alignment: center;
}
}
}
// Main Control
RogItem {
VerticalBox {
Text {
text: @tr("Current Mode: ") + SupergfxPageData.current_mode;
color: RogPalette.text-primary;
}
SystemDropdown {
text: @tr("Graphics Mode");
model <=> SupergfxPageData.supported_modes;
current_index <=> SupergfxPageData.selected_index;
current_value: SupergfxPageData.supported_modes[SupergfxPageData.selected_index];
selected => {
SupergfxPageData.set_mode(self.current_value);
}
}
Text {
text: @tr("Note: Changing modes requires a logout.");
color: RogPalette.text-secondary;
font-size: 12px;
wrap: word-wrap;
}
}
}
}
}
}

View File

@@ -1,5 +1,6 @@
import { SystemSlider, SystemDropdown, SystemToggle, SystemToggleInt, RogItem } from "../widgets/common.slint"; import { SystemSlider, SystemDropdown, SystemToggle, SystemToggleInt, RogItem } from "../widgets/common.slint";
import { Palette, HorizontalBox , VerticalBox, ScrollView, Slider, Button, Switch, ComboBox, GroupBox, StandardButton} from "std-widgets.slint"; import { Palette, HorizontalBox , VerticalBox, ScrollView, Slider, Button, Switch, ComboBox, GroupBox, StandardButton} from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
export struct AttrMinMax { export struct AttrMinMax {
min: int, min: int,
@@ -51,6 +52,14 @@ export global SystemPageData {
callback cb_panel_overdrive(int); callback cb_panel_overdrive(int);
in-out property <int> boot_sound; in-out property <int> boot_sound;
callback cb_boot_sound(int); callback cb_boot_sound(int);
in-out property <int> kbd_leds_awake;
callback cb_kbd_leds_awake(int);
in-out property <int> kbd_leds_sleep;
callback cb_kbd_leds_sleep(int);
in-out property <int> kbd_leds_boot;
callback cb_kbd_leds_boot(int);
in-out property <int> kbd_leds_shutdown;
callback cb_kbd_leds_shutdown(int);
in-out property <int> screen_auto_brightness; in-out property <int> screen_auto_brightness;
callback cb_screen_auto_brightness(int); callback cb_screen_auto_brightness(int);
in-out property <int> mcu_powersave; in-out property <int> mcu_powersave;
@@ -58,13 +67,6 @@ export global SystemPageData {
in-out property <int> mini_led_mode; in-out property <int> mini_led_mode;
callback cb_mini_led_mode(int); callback cb_mini_led_mode(int);
in-out property <float> screenpad_gamma;
callback cb_screenpad_gamma(float);
// percentage
in-out property <int> screenpad_brightness: 50;
callback cb_screenpad_brightness(int);
in-out property <bool> screenpad_sync_with_primary: false;
callback cb_screenpad_sync_with_primary(bool);
in-out property <bool> asus_armoury_loaded: false; in-out property <bool> asus_armoury_loaded: false;
@@ -149,18 +151,22 @@ export component PageSystem inherits Rectangle {
ScrollView { ScrollView {
VerticalLayout { VerticalLayout {
padding: 10px; padding: 10px;
padding-top: 40px;
padding-bottom: 40px;
spacing: 10px; spacing: 10px;
alignment: LayoutAlignment.start; alignment: LayoutAlignment.start;
Rectangle { Rectangle {
background: Palette.alternate-background; background: RogPalette.control-background;
border-color: Palette.accent-background; border-color: RogPalette.control-border;
border-width: 3px; border-width: 1px;
border-radius: 10px; border-radius: 8px;
height: 40px; height: 46px;
Text { Text {
font-size: 18px; font-size: 18px;
color: Palette.control-foreground; color: RogPalette.accent;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
vertical-alignment: TextVerticalAlignment.center;
font-weight: 700;
text: @tr("Power settings"); text: @tr("Power settings");
} }
} }
@@ -199,62 +205,18 @@ export component PageSystem inherits Rectangle {
} }
} }
if SystemPageData.screenpad_brightness != -1: RogItem {
HorizontalLayout {
padding-left: 10px;
padding-right: 20px;
HorizontalLayout {
width: 38%;
alignment: LayoutAlignment.space-between;
padding-right: 15px;
Text {
font-size: 16px;
vertical-alignment: TextVerticalAlignment.center;
color: Palette.control-foreground;
text: @tr("Screenpad brightness");
}
}
HorizontalLayout {
width: 38%;
alignment: LayoutAlignment.stretch;
screen_bright := Slider {
enabled: true;
minimum: 0;
maximum: 100;
value: SystemPageData.screenpad_brightness;
released(value) => {
// SystemPageData.screenpad_brightness = self.value;
SystemPageData.cb_screenpad_brightness(Math.floor(self.value));
}
}
}
HorizontalLayout {
width: 20%;
padding-left: 10px;
alignment: LayoutAlignment.stretch;
Switch {
text: @tr("Sync with primary");
checked <=> SystemPageData.screenpad_sync_with_primary;
toggled => {
SystemPageData.cb_screenpad_sync_with_primary(self.checked);
}
}
}
}
}
Rectangle { Rectangle {
background: Palette.alternate-background; background: RogPalette.control-background;
border-color: Palette.accent-background; border-color: RogPalette.control-border;
border-width: 3px; border-width: 1px;
border-radius: 10px; border-radius: 8px;
height: 40px; height: 46px;
Text { Text {
font-size: 18px; font-size: 18px;
color: Palette.control-foreground; color: RogPalette.accent;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
vertical-alignment: TextVerticalAlignment.center;
font-weight: 700;
text: @tr("Armoury settings"); text: @tr("Armoury settings");
} }
} }
@@ -278,6 +240,44 @@ export component PageSystem inherits Rectangle {
} }
} }
GroupBox {
title: @tr("Keyboard Power Management");
HorizontalLayout {
spacing: 10px;
if SystemPageData.kbd_leds_awake != -1: SystemToggleInt {
text: @tr("Keyboard Awake Effect");
checked_int <=> SystemPageData.kbd_leds_awake;
toggled => {
SystemPageData.cb_kbd_leds_awake(SystemPageData.kbd_leds_awake)
}
}
if SystemPageData.kbd_leds_sleep != -1: SystemToggleInt {
text: @tr("Keyboard Sleep Effect");
checked_int <=> SystemPageData.kbd_leds_sleep;
toggled => {
SystemPageData.cb_kbd_leds_sleep(SystemPageData.kbd_leds_sleep)
}
}
if SystemPageData.kbd_leds_boot != -1: SystemToggleInt {
text: @tr("Keyboard Boot Effect");
checked_int <=> SystemPageData.kbd_leds_boot;
toggled => {
SystemPageData.cb_kbd_leds_boot(SystemPageData.kbd_leds_boot)
}
}
if SystemPageData.kbd_leds_shutdown != -1: SystemToggleInt {
text: @tr("Keyboard Shutdown Effect");
checked_int <=> SystemPageData.kbd_leds_shutdown;
toggled => {
SystemPageData.cb_kbd_leds_shutdown(SystemPageData.kbd_leds_shutdown)
}
}
}
}
HorizontalBox { HorizontalBox {
padding: 0px; padding: 0px;
spacing: 10px; spacing: 10px;
@@ -331,6 +331,7 @@ export component PageSystem inherits Rectangle {
Text { Text {
font-size: 16px; font-size: 16px;
text: @tr("ppt_warning" => "The following settings are not applied until the toggle is enabled."); text: @tr("ppt_warning" => "The following settings are not applied until the toggle is enabled.");
color: RogPalette.text-primary;
} }
} }
@@ -499,7 +500,7 @@ export component PageSystem inherits Rectangle {
if root.show_fade_cover: Rectangle { if root.show_fade_cover: Rectangle {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: Palette.background; background: RogPalette.background;
opacity: 0.9; opacity: 0.9;
TouchArea { TouchArea {
height: 100%; height: 100%;
@@ -532,6 +533,7 @@ export component PageSystem inherits Rectangle {
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
vertical-alignment: TextVerticalAlignment.center; vertical-alignment: TextVerticalAlignment.center;
text: @tr("Energy Performance Preference linked to Throttle Policy"); text: @tr("Energy Performance Preference linked to Throttle Policy");
color: RogPalette.text-primary;
} }
SystemToggle { SystemToggle {
@@ -582,6 +584,7 @@ export component PageSystem inherits Rectangle {
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
vertical-alignment: TextVerticalAlignment.center; vertical-alignment: TextVerticalAlignment.center;
text: @tr("Throttle Policy for power state"); text: @tr("Throttle Policy for power state");
color: RogPalette.text-primary;
} }
HorizontalLayout { HorizontalLayout {

View File

@@ -0,0 +1,13 @@
export global RogPalette {
out property <brush> background: #0a0a0a;
out property <brush> alternate-background: #111111;
out property <brush> control-background: #1e1e1e;
out property <brush> control-border: #333333;
out property <brush> control-border-hover: #555555;
out property <brush> control-border-checked: #ff0033; // ROG Red
out property <brush> text-primary: #ffffff;
out property <brush> text-secondary: #aaaaaa;
out property <brush> accent: #ff0033;
out property <brush> accent-hover: #d60000;
out property <length> border-radius: 4px;
}

View File

@@ -8,6 +8,13 @@ export enum AuraDevType {
AnimeOrSlash, AnimeOrSlash,
} }
// Software animation modes for keyboards that only support Static
export enum SoftAnimationMode {
None,
Rainbow,
ColorCycle,
}
export struct AuraEffect { export struct AuraEffect {
/// The effect type /// The effect type
mode: int, mode: int,
@@ -166,4 +173,16 @@ export global AuraPageData {
}] }]
}; };
callback cb_led_power(LaptopAuraPower); callback cb_led_power(LaptopAuraPower);
// Software animation properties (for Static-only keyboards)
in-out property <[string]> soft_animation_modes: [
@tr("Animation mode" => "None"),
@tr("Animation mode" => "Rainbow"),
@tr("Animation mode" => "Color Cycle"),
];
in-out property <int> soft_animation_mode: 0;
in-out property <float> soft_animation_speed: 200; // ms between updates
in-out property <bool> soft_animation_available: false; // Set true when only Static mode is supported
callback cb_soft_animation_mode(int);
callback cb_soft_animation_speed(int);
} }

View File

@@ -35,314 +35,64 @@ export global FanPageData {
in-out property <bool> quiet_gpu_enabled: true; in-out property <bool> quiet_gpu_enabled: true;
in-out property <bool> quiet_mid_enabled: false; in-out property <bool> quiet_mid_enabled: false;
in-out property <bool> is_busy_cpu: false;
in-out property <bool> is_busy_gpu: false;
in-out property <bool> is_busy_mid: false;
in-out property <bool> show_custom_warning: false;
callback set_fan_data(FanType, Profile, bool, [Node]); callback set_fan_data(FanType, Profile, bool, [Node]);
callback set_profile_default(Profile); callback set_profile_default(Profile);
callback set_is_busy(FanType, bool);
// Last applied cache for Cancel button
in-out property <[Node]> last_applied_cpu_balanced: [];
in-out property <[Node]> last_applied_gpu_balanced: [];
in-out property <[Node]> last_applied_mid_balanced: [];
in-out property <[Node]> last_applied_cpu_performance: [];
in-out property <[Node]> last_applied_gpu_performance: [];
in-out property <[Node]> last_applied_mid_performance: [];
in-out property <[Node]> last_applied_cpu_quiet: [];
in-out property <[Node]> last_applied_gpu_quiet: [];
in-out property <[Node]> last_applied_mid_quiet: [];
callback cancel(FanType, Profile);
in-out property <[Node]> balanced_cpu: [ in-out property <[Node]> balanced_cpu: [
{ { x: 30px, y: 0px }, { x: 40px, y: 0px }, { x: 50px, y: 0px }, { x: 60px, y: 0px },
x: 10px, { x: 70px, y: 25px }, { x: 80px, y: 55px }, { x: 90px, y: 85px }, { x: 98px, y: 100px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
in-out property <[Node]> balanced_mid: [ in-out property <[Node]> balanced_mid: [
{ { x: 30px, y: 0px }, { x: 40px, y: 0px }, { x: 50px, y: 0px }, { x: 60px, y: 0px },
x: 10px, { x: 70px, y: 25px }, { x: 80px, y: 55px }, { x: 90px, y: 85px }, { x: 98px, y: 100px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
in-out property <[Node]> balanced_gpu: [ in-out property <[Node]> balanced_gpu: [
{ { x: 30px, y: 0px }, { x: 40px, y: 0px }, { x: 50px, y: 0px }, { x: 60px, y: 0px },
x: 10px, { x: 70px, y: 25px }, { x: 80px, y: 55px }, { x: 90px, y: 85px }, { x: 98px, y: 100px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
in-out property <[Node]> performance_cpu: [ in-out property <[Node]> performance_cpu: [
{ { x: 30px, y: 0px }, { x: 40px, y: 10px }, { x: 50px, y: 30px }, { x: 60px, y: 50px },
x: 10px, { x: 70px, y: 70px }, { x: 80px, y: 85px }, { x: 90px, y: 95px }, { x: 98px, y: 100px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
in-out property <[Node]> performance_mid: [ in-out property <[Node]> performance_mid: [
{ { x: 30px, y: 0px }, { x: 40px, y: 10px }, { x: 50px, y: 30px }, { x: 60px, y: 50px },
x: 10px, { x: 70px, y: 70px }, { x: 80px, y: 85px }, { x: 90px, y: 95px }, { x: 98px, y: 100px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
in-out property <[Node]> performance_gpu: [ in-out property <[Node]> performance_gpu: [
{ { x: 30px, y: 0px }, { x: 40px, y: 10px }, { x: 50px, y: 30px }, { x: 60px, y: 50px },
x: 10px, { x: 70px, y: 70px }, { x: 80px, y: 85px }, { x: 90px, y: 95px }, { x: 98px, y: 100px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
in-out property <[Node]> quiet_cpu: [ in-out property <[Node]> quiet_cpu: [
{ { x: 30px, y: 0px }, { x: 40px, y: 0px }, { x: 50px, y: 0px }, { x: 60px, y: 0px },
x: 10px, { x: 70px, y: 20px }, { x: 80px, y: 40px }, { x: 90px, y: 70px }, { x: 98px, y: 90px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
in-out property <[Node]> quiet_mid: [ in-out property <[Node]> quiet_mid: [
{ { x: 30px, y: 0px }, { x: 40px, y: 0px }, { x: 50px, y: 0px }, { x: 60px, y: 0px },
x: 10px, { x: 70px, y: 20px }, { x: 80px, y: 40px }, { x: 90px, y: 70px }, { x: 98px, y: 90px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
in-out property <[Node]> quiet_gpu: [ in-out property <[Node]> quiet_gpu: [
{ { x: 30px, y: 0px }, { x: 40px, y: 0px }, { x: 50px, y: 0px }, { x: 60px, y: 0px },
x: 10px, { x: 70px, y: 20px }, { x: 80px, y: 40px }, { x: 90px, y: 70px }, { x: 98px, y: 90px },
y: 10px,
},
{
x: 40px,
y: 30px,
},
{
x: 50px,
y: 50px,
},
{
x: 55px,
y: 50px,
},
{
x: 60px,
y: 60px,
},
{
x: 65px,
y: 70px,
},
{
x: 70px,
y: 80px,
},
{
x: 90px,
y: 100px,
},
]; ];
function set_fan(profile: Profile, fan: FanType, data: [Node]) { function set_fan(profile: Profile, fan: FanType, data: [Node]) {

View File

@@ -0,0 +1,13 @@
import { RogPalette } from "../themes/rog_theme.slint";
export global ScreenpadPageData {
in-out property <int> brightness: -1;
in-out property <float> gamma: 1.0;
in-out property <bool> sync_with_primary: false;
in-out property <bool> power: true;
callback cb_brightness(int);
callback cb_gamma(float);
callback cb_sync_with_primary(bool);
callback cb_power(bool);
}

View File

@@ -0,0 +1,49 @@
export global SlashPageData {
in-out property <bool> enabled;
callback cb_enabled(bool);
in-out property <float> brightness;
callback cb_brightness(float);
in-out property <float> interval;
callback cb_interval(float);
in-out property <[string]> modes: [
@tr("Static"),
@tr("Bounce"),
@tr("Slash"),
@tr("Loading"),
@tr("BitStream"),
@tr("Transmission"),
@tr("Flow"),
@tr("Flux"),
@tr("Phantom"),
@tr("Spectrum"),
@tr("Hazard"),
@tr("Interfacing"),
@tr("Ramp"),
@tr("GameOver"),
@tr("Start"),
@tr("Buzzer"),
];
in-out property <int> mode_index;
callback cb_mode_index(int);
in-out property <bool> show_battery_warning;
callback cb_show_battery_warning(bool);
in-out property <bool> show_on_battery;
callback cb_show_on_battery(bool);
in-out property <bool> show_on_boot;
callback cb_show_on_boot(bool);
in-out property <bool> show_on_shutdown;
callback cb_show_on_shutdown(bool);
in-out property <bool> show_on_sleep;
callback cb_show_on_sleep(bool);
in-out property <bool> show_on_lid_closed;
callback cb_show_on_lid_closed(bool);
}

View File

@@ -0,0 +1,10 @@
export global SupergfxPageData {
in-out property <string> current_mode: "Hybrid";
in-out property <[string]> supported_modes: ["Hybrid", "Integrated"];
in-out property <int> selected_index: 0;
in-out property <string> vendor: "Unknown";
callback set_mode(string);
callback refresh();
}

View File

@@ -1,12 +1,14 @@
import { Palette, VerticalBox, HorizontalBox, GroupBox } from "std-widgets.slint"; import { VerticalBox, HorizontalBox, GroupBox } from "std-widgets.slint";
import { SystemToggleVert, SystemDropdown } from "common.slint"; import { SystemToggleVert, SystemDropdown } from "common.slint";
import { PowerZones } from "../types/aura_types.slint"; import { PowerZones } from "../types/aura_types.slint";
import { RogPalette } from "../themes/rog_theme.slint";
export component AuraPowerGroup inherits Rectangle { export component AuraPowerGroup inherits Rectangle {
min-width: row.min-width; min-width: row.min-width;
border-radius: 20px; border-radius: 8px;
background: Palette.alternate-background; background: RogPalette.control-background;
opacity: 0.9; border-width: 1px;
border-color: RogPalette.control-border;
in-out property <string> group-title; in-out property <string> group-title;
in-out property <bool> boot_checked; in-out property <bool> boot_checked;
in-out property <bool> awake_checked; in-out property <bool> awake_checked;
@@ -20,7 +22,7 @@ export component AuraPowerGroup inherits Rectangle {
spacing: 10px; spacing: 10px;
Text { Text {
font-size: 18px; font-size: 18px;
color: Palette.alternate-foreground; color: RogPalette.text-primary;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
text <=> root.group-title; text <=> root.group-title;
} }
@@ -72,9 +74,10 @@ export component AuraPowerGroup inherits Rectangle {
export component AuraPowerGroupOld inherits Rectangle { export component AuraPowerGroupOld inherits Rectangle {
min-width: row.min-width; min-width: row.min-width;
border-radius: 20px; border-radius: 8px;
background: Palette.alternate-background; background: RogPalette.control-background;
opacity: 0.9; border-width: 1px;
border-color: RogPalette.control-border;
in-out property <int> current_zone; in-out property <int> current_zone;
in-out property <[int]> zones; in-out property <[int]> zones;
in-out property <[string]> zone_strings; in-out property <[string]> zone_strings;
@@ -90,7 +93,7 @@ export component AuraPowerGroupOld inherits Rectangle {
spacing: 10px; spacing: 10px;
Text { Text {
font-size: 18px; font-size: 18px;
color: Palette.alternate-foreground; color: RogPalette.text-primary;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
text <=> root.group-title; text <=> root.group-title;
} }

View File

@@ -1,12 +1,15 @@
import { Palette, VerticalBox , StandardButton, Button, HorizontalBox, ComboBox, Switch, Slider} from "std-widgets.slint"; import { VerticalBox , StandardButton, Button, HorizontalBox, ComboBox, Switch, Slider} from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
export component RogItem inherits Rectangle { export component RogItem inherits Rectangle {
background: Palette.control-background; in property <bool> enabled: true;
border-color: Palette.border; background: root.enabled ? RogPalette.control-background : RogPalette.control-background.darker(0.5);
border-width: 3px; border-color: root.enabled ? RogPalette.control-border : RogPalette.control-border.darker(0.3);
border-radius: 10px; border-width: 1px; // Thinner border for modern look
border-radius: RogPalette.border-radius;
min-height: 48px; min-height: 48px;
max-height: 56px; max-height: 56px;
opacity: root.enabled ? 1.0 : 0.6;
} }
export component SystemSlider inherits RogItem { export component SystemSlider inherits RogItem {
@@ -18,7 +21,6 @@ export component SystemSlider inherits RogItem {
callback released(float); callback released(float);
in property <string> help_text; in property <string> help_text;
in property <bool> enabled: true;
in property <bool> has_reset: false; in property <bool> has_reset: false;
callback cb_do_reset(); callback cb_do_reset();
@@ -32,7 +34,7 @@ export component SystemSlider inherits RogItem {
Text { Text {
font-size: 16px; font-size: 16px;
vertical-alignment: TextVerticalAlignment.center; vertical-alignment: TextVerticalAlignment.center;
color: Palette.control-foreground; color: RogPalette.text-primary;
text: root.text; text: root.text;
} }
@@ -40,7 +42,7 @@ export component SystemSlider inherits RogItem {
font-size: 16px; font-size: 16px;
horizontal-alignment: TextHorizontalAlignment.right; horizontal-alignment: TextHorizontalAlignment.right;
vertical-alignment: TextVerticalAlignment.center; vertical-alignment: TextVerticalAlignment.center;
color: Palette.control-foreground; color: RogPalette.accent;
text: "\{Math.round(root.value)}"; text: "\{Math.round(root.value)}";
} }
} }
@@ -64,10 +66,11 @@ export component SystemSlider inherits RogItem {
y: help.y - self.height + help.height - 10px; y: help.y - self.height + help.height - 10px;
Rectangle { Rectangle {
drop-shadow-blur: 10px; drop-shadow-blur: 10px;
drop-shadow-color: black; drop-shadow-color: Colors.black;
border-radius: 10px; border-radius: RogPalette.border-radius;
border-color: Palette.accent-background; border-width: 1px;
background: Palette.background; border-color: RogPalette.accent;
background: RogPalette.control-background;
Dialog { Dialog {
title: root.title; title: root.title;
VerticalBox { VerticalBox {
@@ -77,12 +80,12 @@ export component SystemSlider inherits RogItem {
wrap: TextWrap.word-wrap; wrap: TextWrap.word-wrap;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
text: root.title; text: root.title;
color: RogPalette.text-primary;
} }
Rectangle { Rectangle {
height: 1px; height: 1px;
border-color: black; background: RogPalette.control-border;
border-width: 1px;
} }
Text { Text {
@@ -90,6 +93,7 @@ export component SystemSlider inherits RogItem {
font-size: 16px; font-size: 16px;
wrap: TextWrap.word-wrap; wrap: TextWrap.word-wrap;
text: root.help_text; text: root.help_text;
color: RogPalette.text-secondary;
} }
} }
@@ -114,16 +118,18 @@ export component SystemSlider inherits RogItem {
y: reset.y - self.height + reset.height; y: reset.y - self.height + reset.height;
Rectangle { Rectangle {
drop-shadow-blur: 10px; drop-shadow-blur: 10px;
drop-shadow-color: black; drop-shadow-color: Colors.black;
border-radius: 10px; border-radius: RogPalette.border-radius;
border-color: Palette.accent-background; border-width: 1px;
background: Palette.background; border-color: RogPalette.accent;
background: RogPalette.control-background;
Dialog { Dialog {
Text { Text {
max-width: 420px; max-width: 420px;
font-size: 16px; font-size: 16px;
wrap: TextWrap.word-wrap; wrap: TextWrap.word-wrap;
text: @tr("confirm_reset" => "Are you sure you want to reset this?"); text: @tr("confirm_reset" => "Are you sure you want to reset this?");
color: RogPalette.text-primary;
} }
StandardButton { StandardButton {
@@ -164,7 +170,7 @@ export component SystemToggle inherits RogItem {
Text { Text {
font-size: 16px; font-size: 16px;
vertical-alignment: TextVerticalAlignment.center; vertical-alignment: TextVerticalAlignment.center;
color: Palette.control-foreground; color: RogPalette.text-primary;
text: root.text; text: root.text;
} }
} }
@@ -195,7 +201,7 @@ export component SystemToggleInt inherits RogItem {
Text { Text {
font-size: 16px; font-size: 16px;
vertical-alignment: TextVerticalAlignment.center; vertical-alignment: TextVerticalAlignment.center;
color: Palette.control-foreground; color: RogPalette.text-primary;
text: root.text; text: root.text;
} }
} }
@@ -226,7 +232,7 @@ export component SystemToggleVert inherits RogItem {
font-size: 16px; font-size: 16px;
vertical-alignment: TextVerticalAlignment.bottom; vertical-alignment: TextVerticalAlignment.bottom;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
color: Palette.control-foreground; color: RogPalette.text-primary;
text: root.text; text: root.text;
} }
@@ -256,7 +262,7 @@ export component SystemDropdown inherits RogItem {
Text { Text {
font-size: 16px; font-size: 16px;
vertical-alignment: TextVerticalAlignment.center; vertical-alignment: TextVerticalAlignment.center;
color: Palette.control-foreground; color: RogPalette.text-primary;
text: root.text; text: root.text;
} }
} }
@@ -288,9 +294,9 @@ export component PopupNotification {
height: root.height; height: root.height;
// TODO: add properties to display // TODO: add properties to display
Rectangle { Rectangle {
border-width: 2px; border-width: 1px;
border-color: Palette.accent-background; border-color: RogPalette.accent;
background: Palette.background; background: RogPalette.background;
// TODO: drop shadows slow // TODO: drop shadows slow
// drop-shadow-offset-x: 7px; // drop-shadow-offset-x: 7px;
// drop-shadow-offset-y: 7px; // drop-shadow-offset-y: 7px;
@@ -302,14 +308,14 @@ export component PopupNotification {
alignment: start; alignment: start;
Text { Text {
text: heading; text: heading;
color: Palette.control-foreground; color: RogPalette.text-primary;
font-size: 32px; font-size: 32px;
font-weight: 900; font-weight: 900;
} }
Text { Text {
text: content; text: content;
color: Palette.control-foreground; color: RogPalette.text-secondary;
font-size: 18px; font-size: 18px;
} }
} }

View File

@@ -1,4 +1,5 @@
import { Palette } from "std-widgets.slint"; import { Palette } from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
export struct Node { x: length, y: length} export struct Node { x: length, y: length}
@@ -44,7 +45,7 @@ export component Graph inherits Rectangle {
for n in 11: Path { for n in 11: Path {
viewbox-width: self.width / 1px; viewbox-width: self.width / 1px;
viewbox-height: self.height / 1px; viewbox-height: self.height / 1px;
stroke: Palette.alternate-foreground.darker(200%); stroke: RogPalette.control-border;
stroke-width: 1px; stroke-width: 1px;
MoveTo { MoveTo {
x: scale_x_to_graph(n * 10px) / 1px; x: scale_x_to_graph(n * 10px) / 1px;
@@ -60,7 +61,7 @@ export component Graph inherits Rectangle {
} }
for n in 11: Text { for n in 11: Text {
color: Palette.accent-background; color: RogPalette.text-secondary;
font-size <=> root.axis_font_size; font-size <=> root.axis_font_size;
text: "\{n * 10}c"; text: "\{n * 10}c";
x: scale_x_to_graph(n * 10px) - self.width / 3; x: scale_x_to_graph(n * 10px) - self.width / 3;
@@ -70,7 +71,7 @@ export component Graph inherits Rectangle {
for n in 11: Path { for n in 11: Path {
viewbox-width: self.width / 1px; viewbox-width: self.width / 1px;
viewbox-height: self.height / 1px; viewbox-height: self.height / 1px;
stroke: Palette.alternate-foreground.darker(200%); stroke: RogPalette.control-border;
stroke-width: 1px; stroke-width: 1px;
MoveTo { MoveTo {
x: 0; x: 0;
@@ -86,7 +87,7 @@ export component Graph inherits Rectangle {
} }
for n in 11: Text { for n in 11: Text {
color: Palette.accent-background; color: RogPalette.text-secondary;
font-size <=> root.axis_font_size; font-size <=> root.axis_font_size;
text: "\{n * 10}%"; text: "\{n * 10}%";
x: - self.width; x: - self.width;
@@ -97,7 +98,7 @@ export component Graph inherits Rectangle {
if idx + 1 != nodes.length: Path { if idx + 1 != nodes.length: Path {
viewbox-width: self.width / 1px; viewbox-width: self.width / 1px;
viewbox-height: self.height / 1px; viewbox-height: self.height / 1px;
stroke: Palette.control-foreground; stroke: RogPalette.accent;
stroke-width: 2px; stroke-width: 2px;
MoveTo { MoveTo {
x: scale_x_to_graph(nodes[idx].x) / 1px; x: scale_x_to_graph(nodes[idx].x) / 1px;
@@ -114,19 +115,19 @@ export component Graph inherits Rectangle {
for n[idx] in nodes: Rectangle { for n[idx] in nodes: Rectangle {
states [ states [
pressed when touch.pressed: { pressed when touch.pressed: {
point.background: Palette.selection-background; point.background: RogPalette.accent;
tip.background: Palette.selection-background; tip.background: RogPalette.accent;
tip.opacity: 1.0; tip.opacity: 1.0;
} }
hover when touch.has-hover: { hover when touch.has-hover: {
point.background: Palette.accent-background; point.background: RogPalette.accent;
tip.background: Palette.accent-background; tip.background: RogPalette.accent;
tip.opacity: 1.0; tip.opacity: 1.0;
} }
] ]
// //
point := Rectangle { point := Rectangle {
background: Palette.control-foreground; background: RogPalette.text-primary;
x: scale_x_to_graph(n.x) - self.width / 2; x: scale_x_to_graph(n.x) - self.width / 2;
y: graph.height - scale_y_to_graph(n.y) - self.height / 2; y: graph.height - scale_y_to_graph(n.y) - self.height / 2;
width: 18px; width: 18px;
@@ -142,10 +143,14 @@ export component Graph inherits Rectangle {
} else if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) < nodes[idx - 1].x { } else if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) < nodes[idx - 1].x {
n.x = nodes[idx - 1].x + pad; n.x = nodes[idx - 1].x + pad;
} }
// Y-Axis: Monotonic Non-Decreasing
if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) > nodes[idx + 1].y { if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) > nodes[idx + 1].y {
n.y = nodes[idx + 1].y - pad; n.y = nodes[idx + 1].y; // Allow equality
} else if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) < nodes[idx - 1].y { } else if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) < nodes[idx - 1].y {
n.y = nodes[idx - 1].y + pad; n.y = nodes[idx - 1].y; // Allow equality
} else if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) < 0.0 {
n.y = 0px;
} }
} else if idx == 0 { } else if idx == 0 {
if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) < 0.0 { if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) < 0.0 {
@@ -153,10 +158,12 @@ export component Graph inherits Rectangle {
} else if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) > nodes[idx + 1].x { } else if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) > nodes[idx + 1].x {
n.x = nodes[idx + 1].x - pad; n.x = nodes[idx + 1].x - pad;
} }
// Y-Axis: <= Next Point
if n.y - scale_y_to_node(self.mouse-y - self.pressed-y) < 0.0 { if n.y - scale_y_to_node(self.mouse-y - self.pressed-y) < 0.0 {
n.y = 1px; n.y = 0px; // Allow 0 RPM
} else if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) > nodes[idx + 1].y { } else if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) > nodes[idx + 1].y {
n.y = nodes[idx + 1].y - pad; n.y = nodes[idx + 1].y; // Allow equality
} }
} else if idx == nodes.length - 1 { } else if idx == nodes.length - 1 {
if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) > scale_x_to_node(graph.width) { if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) > scale_x_to_node(graph.width) {
@@ -164,10 +171,14 @@ export component Graph inherits Rectangle {
} else if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) < nodes[idx - 1].x { } else if n.x + scale_x_to_node(self.mouse-x - self.pressed-x) < nodes[idx - 1].x {
n.x = nodes[idx - 1].x + pad; n.x = nodes[idx - 1].x + pad;
} }
// Y-Axis: >= Previous Point
if n.y - scale_y_to_node(self.mouse-y - self.pressed-y) > scale_y_to_node(graph.height) { if n.y - scale_y_to_node(self.mouse-y - self.pressed-y) > scale_y_to_node(graph.height) {
n.y = scale_y_to_node(graph.height - 1px); n.y = scale_y_to_node(graph.height);
} else if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) < nodes[idx - 1].y { } else if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) < nodes[idx - 1].y {
n.y = nodes[idx - 1].y + pad; n.y = nodes[idx - 1].y; // Allow equality
} else if n.y + scale_y_to_node(self.height - self.mouse-y - self.pressed-y) < 0.0 {
n.y = 0px;
} }
} }
} }
@@ -189,7 +200,7 @@ export component Graph inherits Rectangle {
} }
tip := Rectangle { tip := Rectangle {
background: Palette.control-foreground; background: RogPalette.control-background;
opacity: 0.3; opacity: 0.3;
x: final_x_pos(); x: final_x_pos();
y: final_y_pos(); y: final_y_pos();
@@ -224,7 +235,7 @@ export component Graph inherits Rectangle {
} }
// //
label := Text { label := Text {
color: Palette.accent-foreground; color: RogPalette.text-primary;
font-size: 16px; font-size: 16px;
text: "\{Math.floor(n.x / 1px)}c, \{fan_pct()}%"; text: "\{Math.floor(n.x / 1px)}c, \{fan_pct()}%";
} }

View File

@@ -1,7 +1,8 @@
// Copyright © SixtyFPS GmbH <info@slint.dev> // Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { Palette, HorizontalBox, VerticalBox } from "std-widgets.slint"; import { HorizontalBox, VerticalBox } from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
component SideBarItem inherits Rectangle { component SideBarItem inherits Rectangle {
// padding only has effect on layout elements // padding only has effect on layout elements
@@ -28,12 +29,21 @@ component SideBarItem inherits Rectangle {
] ]
state := Rectangle { state := Rectangle {
opacity: 0; opacity: 0;
border-width: 2px; border-width: 0px; // Modern look: no full border, maybe just a left bar?
border-radius: 10px; // Or keep the ROG style border
border-color: Palette.accent-background; border-color: RogPalette.accent;
background: Palette.alternate-background; background: root.selected ? RogPalette.control-background : RogPalette.alternate-background;
// Add a red indicator line on the left for selected items
Rectangle {
x: 0;
width: 4px;
height: 100%;
background: root.selected ? RogPalette.accent : Colors.transparent;
}
animate opacity { duration: 150ms; } animate opacity { duration: 150ms; }
animate border-width { duration: 150ms; } // animate border-width { duration: 150ms; }
height: l.preferred-height; height: l.preferred-height;
} }
@@ -41,9 +51,10 @@ component SideBarItem inherits Rectangle {
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
spacing: 0px; spacing: 0px;
label := Text { label := Text {
color: Palette.foreground; color: root.selected ? RogPalette.accent : RogPalette.text-primary;
vertical-alignment: center; vertical-alignment: center;
font-size: 14px; font-size: 14px;
font-weight: root.selected ? 700 : 400;
} }
} }
@@ -66,10 +77,10 @@ export component SideBar inherits Rectangle {
accessible-role: tab; accessible-role: tab;
accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-item; accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-item;
Rectangle { Rectangle {
border-width: 2px; border-width: 0px;
border-color: Palette.accent-background; // border-color: RogPalette.accent;
border-radius: 0px; border-radius: 0px;
background: Palette.background.darker(0.2); background: RogPalette.alternate-background; // Darker sidebar
fs := FocusScope { fs := FocusScope {
key-pressed(event) => { key-pressed(event) => {
if (event.text == "\n") { if (event.text == "\n") {
@@ -104,12 +115,19 @@ export component SideBar inherits Rectangle {
spacing: 4px; spacing: 4px;
alignment: start; alignment: start;
label := Text { label := Text {
font-size: 16px; font-size: 24px; // Larger brand text
font-weight: 800;
horizontal-alignment: center; horizontal-alignment: center;
color: RogPalette.accent; // ROG Red brand text
}
// Spacer after brand text
Rectangle {
height: 20px;
} }
navigation := VerticalLayout { navigation := VerticalLayout {
spacing: -6px; spacing: 4px; // Spacing between items
alignment: start; alignment: start;
vertical-stretch: 0; vertical-stretch: 0;
for item[index] in root.model: SideBarItem { for item[index] in root.model: SideBarItem {

View File

@@ -0,0 +1,58 @@
import { RogPalette } from "../themes/rog_theme.slint";
export global SystemStatus {
in property <int> cpu_temp: 0;
in property <int> gpu_temp: 0;
in property <int> cpu_fan: 0;
in property <int> gpu_fan: 0;
in property <string> power_w: "--";
in property <string> power_avg_w: "--";
}
component StatusItem inherits Rectangle {
in property <string> label;
in property <string> value;
in property <string> unit;
HorizontalLayout {
spacing: 5px;
Text { text: label; color: RogPalette.text-secondary; font-weight: 700; vertical-alignment: center; }
Text { text: value; color: RogPalette.text-primary; vertical-alignment: center; }
Text { text: unit; color: RogPalette.text-secondary; font-size: 12px; vertical-alignment: center; }
}
}
export component StatusBar inherits Rectangle {
background: RogPalette.control-background;
height: 30px;
// Simulated top border
Rectangle {
y: 0px;
x: 0px;
width: parent.width;
height: 1px;
background: RogPalette.control-border;
}
HorizontalLayout {
padding-left: 20px;
padding-right: 20px;
spacing: 20px;
alignment: space-between;
HorizontalLayout {
spacing: 20px;
StatusItem { label: "CPU"; value: SystemStatus.cpu_temp; unit: "°C"; }
StatusItem { label: "GPU"; value: SystemStatus.gpu_temp; unit: "°C"; }
}
HorizontalLayout {
spacing: 20px;
StatusItem { label: "PWR"; value: SystemStatus.power_w; unit: "W"; }
StatusItem { label: "AVG"; value: SystemStatus.power_avg_w; unit: "W"; }
StatusItem { label: "CPU Fan"; value: SystemStatus.cpu_fan; unit: "RPM"; }
StatusItem { label: "GPU Fan"; value: SystemStatus.gpu_fan; unit: "RPM"; }
}
}
}

View File

@@ -0,0 +1,76 @@
import { RogPalette } from "../themes/rog_theme.slint";
import { SystemStatus } from "../widgets/status_bar.slint";
component StatusItem inherits Rectangle {
in property <string> label;
in property <string> value;
in property <string> unit;
HorizontalLayout {
spacing: 8px;
Text {
text: label;
color: RogPalette.text-secondary;
font-weight: 700;
vertical-alignment: center;
font-size: 13px;
}
Text {
text: value;
color: RogPalette.text-primary;
vertical-alignment: center;
font-size: 14px;
}
Text {
text: unit;
color: RogPalette.text-secondary;
font-size: 11px;
vertical-alignment: center;
}
}
}
export component TrayTooltip inherits Window {
always-on-top: true;
no-frame: true;
background: transparent;
width: 280px;
height: 160px;
Rectangle {
background: RogPalette.control-background;
border-radius: 8px;
border-width: 1px;
border-color: RogPalette.control-border;
drop-shadow-blur: 10px;
drop-shadow-color: rgba(0,0,0,0.5);
VerticalLayout {
padding: 15px;
spacing: 12px;
Text {
text: "System Statistics";
color: RogPalette.accent;
font-size: 16px;
font-weight: 800;
}
GridLayout {
spacing: 15px;
Row {
StatusItem { label: "CPU"; value: SystemStatus.cpu_temp; unit: "°C"; }
StatusItem { label: "GPU"; value: SystemStatus.gpu_temp; unit: "°C"; }
}
Row {
StatusItem { label: "FAN"; value: SystemStatus.cpu_fan; unit: "RPM"; }
StatusItem { label: "GPU"; value: SystemStatus.gpu_fan; unit: "RPM"; }
}
Row {
StatusItem { label: "PWR"; value: SystemStatus.power_w; unit: "W"; }
StatusItem { label: "AVG"; value: SystemStatus.power_avg_w; unit: "W"; }
}
}
}
}
}

View File

@@ -286,6 +286,10 @@ define_attribute_getters!(
dgpu_tgp, dgpu_tgp,
charge_mode, charge_mode,
boot_sound, boot_sound,
kbd_leds_awake,
kbd_leds_sleep,
kbd_leds_boot,
kbd_leds_shutdown,
mcu_powersave, mcu_powersave,
panel_od, panel_od,
panel_hd_mode, panel_hd_mode,
@@ -342,6 +346,10 @@ pub enum FirmwareAttribute {
PptEnabled = 24, PptEnabled = 24,
None = 25, None = 25,
ScreenAutoBrightness = 26, ScreenAutoBrightness = 26,
KbdLedsAwake = 27,
KbdLedsSleep = 28,
KbdLedsBoot = 29,
KbdLedsShutdown = 30,
} }
impl FirmwareAttribute { impl FirmwareAttribute {
@@ -383,9 +391,13 @@ impl From<&str> for FirmwareAttribute {
"nv_dynamic_boost" => Self::NvDynamicBoost, "nv_dynamic_boost" => Self::NvDynamicBoost,
"nv_temp_target" => Self::NvTempTarget, "nv_temp_target" => Self::NvTempTarget,
"nv_base_tgp" => Self::DgpuBaseTgp, "nv_base_tgp" => Self::DgpuBaseTgp,
"dgpu_tgp" => Self::DgpuTgp, "nv_tgp" => Self::DgpuTgp,
"charge_mode" => Self::ChargeMode, "charge_mode" => Self::ChargeMode,
"boot_sound" => Self::BootSound, "boot_sound" => Self::BootSound,
"kbd_leds_awake" => Self::KbdLedsAwake,
"kbd_leds_sleep" => Self::KbdLedsSleep,
"kbd_leds_boot" => Self::KbdLedsBoot,
"kbd_leds_shutdown" => Self::KbdLedsShutdown,
"mcu_powersave" => Self::McuPowersave, "mcu_powersave" => Self::McuPowersave,
"panel_overdrive" => Self::PanelOverdrive, "panel_overdrive" => Self::PanelOverdrive,
"panel_hd_mode" => Self::PanelHdMode, "panel_hd_mode" => Self::PanelHdMode,
@@ -420,7 +432,7 @@ impl From<FirmwareAttribute> for &str {
FirmwareAttribute::NvDynamicBoost => "nv_dynamic_boost", FirmwareAttribute::NvDynamicBoost => "nv_dynamic_boost",
FirmwareAttribute::NvTempTarget => "nv_temp_target", FirmwareAttribute::NvTempTarget => "nv_temp_target",
FirmwareAttribute::DgpuBaseTgp => "dgpu_base_tgp", FirmwareAttribute::DgpuBaseTgp => "dgpu_base_tgp",
FirmwareAttribute::DgpuTgp => "dgpu_tgp", FirmwareAttribute::DgpuTgp => "nv_tgp",
FirmwareAttribute::ChargeMode => "charge_mode", FirmwareAttribute::ChargeMode => "charge_mode",
FirmwareAttribute::BootSound => "boot_sound", FirmwareAttribute::BootSound => "boot_sound",
FirmwareAttribute::McuPowersave => "mcu_powersave", FirmwareAttribute::McuPowersave => "mcu_powersave",
@@ -431,6 +443,10 @@ impl From<FirmwareAttribute> for &str {
FirmwareAttribute::DgpuDisable => "dgpu_disable", FirmwareAttribute::DgpuDisable => "dgpu_disable",
FirmwareAttribute::GpuMuxMode => "gpu_mux_mode", FirmwareAttribute::GpuMuxMode => "gpu_mux_mode",
FirmwareAttribute::MiniLedMode => "mini_led_mode", FirmwareAttribute::MiniLedMode => "mini_led_mode",
FirmwareAttribute::KbdLedsAwake => "kbd_leds_awake",
FirmwareAttribute::KbdLedsSleep => "kbd_leds_sleep",
FirmwareAttribute::KbdLedsBoot => "kbd_leds_boot",
FirmwareAttribute::KbdLedsShutdown => "kbd_leds_shutdown",
FirmwareAttribute::PendingReboot => "pending_reboot", FirmwareAttribute::PendingReboot => "pending_reboot",
FirmwareAttribute::ScreenAutoBrightness => "screen_auto_brightness", FirmwareAttribute::ScreenAutoBrightness => "screen_auto_brightness",
FirmwareAttribute::None => "none", FirmwareAttribute::None => "none",

View File

@@ -177,6 +177,12 @@ pub enum AuraMode {
DoubleFade = 14, DoubleFade = 14,
} }
#[cfg(feature = "dbus")]
impl zbus::zvariant::Basic for AuraMode {
const SIGNATURE_CHAR: char = 'u';
const SIGNATURE_STR: &'static str = "u";
}
impl AuraMode { impl AuraMode {
pub fn list() -> [String; 15] { pub fn list() -> [String; 15] {
[ [

View File

@@ -12,9 +12,13 @@ use crate::usb::{PROD_ID1, PROD_ID1_STR, PROD_ID2, PROD_ID2_STR};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum SlashType { pub enum SlashType {
GA403, GA403_2024,
GA605, GA403_2025,
GU605, GA605_2024,
GA605_2025,
GU605_2024,
GU605_2025,
G614_2025,
#[default] #[default]
Unsupported, Unsupported,
} }
@@ -22,30 +26,51 @@ pub enum SlashType {
impl SlashType { impl SlashType {
pub const fn prod_id(&self) -> u16 { pub const fn prod_id(&self) -> u16 {
match self { match self {
SlashType::GA403 => PROD_ID1, SlashType::GA403_2025 => PROD_ID2,
SlashType::GA605 => PROD_ID2, SlashType::GA403_2024 => PROD_ID1,
SlashType::GU605 => PROD_ID1, SlashType::GA605_2025 => PROD_ID2,
SlashType::GA605_2024 => PROD_ID2,
SlashType::GU605_2025 => PROD_ID2,
SlashType::GU605_2024 => PROD_ID1,
SlashType::G614_2025 => PROD_ID2,
SlashType::Unsupported => 0, SlashType::Unsupported => 0,
} }
} }
pub const fn prod_id_str(&self) -> &str { pub const fn prod_id_str(&self) -> &str {
match self { match self {
SlashType::GA403 => PROD_ID1_STR, SlashType::GA403_2025 => PROD_ID2_STR,
SlashType::GA605 => PROD_ID2_STR, SlashType::GA403_2024 => PROD_ID1_STR,
SlashType::GU605 => PROD_ID1_STR, SlashType::GA605_2025 => PROD_ID2_STR,
SlashType::GA605_2024 => PROD_ID2_STR,
SlashType::GU605_2025 => PROD_ID2_STR,
SlashType::GU605_2024 => PROD_ID1_STR,
SlashType::G614_2025 => PROD_ID2_STR,
SlashType::Unsupported => "", SlashType::Unsupported => "",
} }
} }
pub fn from_dmi() -> Self { pub fn from_dmi() -> Self {
let board_name = DMIID::new().unwrap_or_default().board_name.to_uppercase(); let board_name = DMIID::new().unwrap_or_default().board_name.to_uppercase();
if board_name.contains("GA403") { if board_name.contains("G614F") {
SlashType::GA403 SlashType::G614_2025
} else if [
"GA403W", "GA403UH", "GA403UM", "GA403UP",
]
.iter()
.any(|s| board_name.contains(s))
{
SlashType::GA403_2025
} else if board_name.contains("GA403") {
SlashType::GA403_2024
} else if board_name.contains("GA605K") {
SlashType::GA605_2025
} else if board_name.contains("GA605") { } else if board_name.contains("GA605") {
SlashType::GA605 SlashType::GA605_2024
} else if board_name.contains("GU605C") {
SlashType::GU605_2025
} else if board_name.contains("GU605") { } else if board_name.contains("GU605") {
SlashType::GU605 SlashType::GU605_2024
} else { } else {
SlashType::Unsupported SlashType::Unsupported
} }
@@ -56,10 +81,14 @@ impl FromStr for SlashType {
type Err = SlashError; type Err = SlashError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(match s { Ok(match s.to_uppercase().as_str() {
"ga403" | "GA403" => Self::GA403, "GA403_2025" => Self::GA403_2025,
"ga605" | "GA605" => Self::GA605, "GA403_2024" => Self::GA403_2024,
"gu605" | "GU605" => Self::GU605, "GA605_2025" => Self::GA605_2025,
"GA605_2024" => Self::GA605_2024,
"GU605_2025" => Self::GU605_2025,
"GU605_2024" => Self::GU605_2024,
"G614_2025" => Self::G614_2025,
_ => Self::Unsupported, _ => Self::Unsupported,
}) })
} }
@@ -68,6 +97,7 @@ impl FromStr for SlashType {
#[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))] #[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))]
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)] #[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
pub enum SlashMode { pub enum SlashMode {
Static = 0x06,
Bounce = 0x10, Bounce = 0x10,
Slash = 0x12, Slash = 0x12,
Loading = 0x13, Loading = 0x13,
@@ -91,6 +121,7 @@ impl FromStr for SlashMode {
fn from_str(s: &str) -> Result<Self, SlashError> { fn from_str(s: &str) -> Result<Self, SlashError> {
match s { match s {
"Static" => Ok(SlashMode::Static),
"Bounce" => Ok(SlashMode::Bounce), "Bounce" => Ok(SlashMode::Bounce),
"Slash" => Ok(SlashMode::Slash), "Slash" => Ok(SlashMode::Slash),
"Loading" => Ok(SlashMode::Loading), "Loading" => Ok(SlashMode::Loading),
@@ -114,6 +145,7 @@ impl FromStr for SlashMode {
impl Display for SlashMode { impl Display for SlashMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match &self { let str = match &self {
SlashMode::Static => String::from("Static"),
SlashMode::Bounce => String::from("Bounce"), SlashMode::Bounce => String::from("Bounce"),
SlashMode::Slash => String::from("Slash"), SlashMode::Slash => String::from("Slash"),
SlashMode::Loading => String::from("Loading"), SlashMode::Loading => String::from("Loading"),
@@ -135,8 +167,9 @@ impl Display for SlashMode {
} }
impl SlashMode { impl SlashMode {
pub fn list() -> [String; 15] { pub fn list() -> [String; 16] {
[ [
SlashMode::Static.to_string(),
SlashMode::Bounce.to_string(), SlashMode::Bounce.to_string(),
SlashMode::Slash.to_string(), SlashMode::Slash.to_string(),
SlashMode::Loading.to_string(), SlashMode::Loading.to_string(),

View File

@@ -37,14 +37,26 @@ pub fn get_slash_type() -> SlashType {
let dmi = DMIID::new() let dmi = DMIID::new()
.map_err(|_| SlashError::NoDevice) .map_err(|_| SlashError::NoDevice)
.unwrap_or_default(); .unwrap_or_default();
let board_name = dmi.board_name; let board_name = dmi.board_name.to_uppercase();
if board_name.contains("G614F") {
if board_name.contains("GA403") { SlashType::G614_2025
SlashType::GA403 } else if [
"GA403W", "GA403UH", "GA403UM", "GA403UP",
]
.iter()
.any(|s| board_name.contains(s))
{
SlashType::GA403_2025
} else if board_name.contains("GA403") {
SlashType::GA403_2024
} else if board_name.contains("GA605K") {
SlashType::GA605_2025
} else if board_name.contains("GA605") { } else if board_name.contains("GA605") {
SlashType::GA605 SlashType::GA605_2024
} else if board_name.contains("GU605C") {
SlashType::GU605_2025
} else if board_name.contains("GU605") { } else if board_name.contains("GU605") {
SlashType::GU605 SlashType::GU605_2024
} else { } else {
SlashType::Unsupported SlashType::Unsupported
} }
@@ -52,9 +64,13 @@ pub fn get_slash_type() -> SlashType {
pub const fn report_id(slash_type: SlashType) -> u8 { pub const fn report_id(slash_type: SlashType) -> u8 {
match slash_type { match slash_type {
SlashType::GA403 => REPORT_ID_193B, SlashType::GA403_2025 => REPORT_ID_19B6,
SlashType::GA605 => REPORT_ID_19B6, SlashType::GA403_2024 => REPORT_ID_193B,
SlashType::GU605 => REPORT_ID_193B, SlashType::GA605_2025 => REPORT_ID_19B6,
SlashType::GA605_2024 => REPORT_ID_19B6,
SlashType::GU605_2025 => REPORT_ID_19B6,
SlashType::GU605_2024 => REPORT_ID_193B,
SlashType::G614_2025 => REPORT_ID_19B6,
SlashType::Unsupported => REPORT_ID_19B6, SlashType::Unsupported => REPORT_ID_19B6,
} }
} }

View File

@@ -43,10 +43,12 @@ impl AniMatrix {
vertical: 2, vertical: 2,
horizontal: 5, horizontal: 5,
}, },
AnimeType::GA402 | AnimeType::G635L | AnimeType::Unsupported => LedShape { AnimeType::GA402 | AnimeType::G635L | AnimeType::G835L | AnimeType::Unsupported => {
vertical: 2, LedShape {
horizontal: 5, vertical: 2,
}, horizontal: 5,
}
}
AnimeType::GU604 => LedShape { AnimeType::GU604 => LedShape {
vertical: 2, vertical: 2,
horizontal: 5, horizontal: 5,
@@ -56,7 +58,9 @@ impl AniMatrix {
// Do a hard mapping of each (derived from wireshardk captures) // Do a hard mapping of each (derived from wireshardk captures)
let rows = match model { let rows = match model {
AnimeType::GA401 => GA401.to_vec(), AnimeType::GA401 => GA401.to_vec(),
AnimeType::GA402 | AnimeType::G635L | AnimeType::Unsupported => GA402.to_vec(), AnimeType::GA402 | AnimeType::G635L | AnimeType::G835L | AnimeType::Unsupported => {
GA402.to_vec()
}
AnimeType::GU604 => GU604.to_vec(), AnimeType::GU604 => GU604.to_vec(),
}; };