Compare commits
262 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd7e748c88 | ||
|
|
f222eef6b7 | ||
|
|
e6f3aeb851 | ||
|
|
22605e57cc | ||
|
|
02fb7addf4 | ||
|
|
bdbb403a0e | ||
|
|
7a8bede92f | ||
|
|
a71a40b509 | ||
|
|
42fc5a5392 | ||
|
|
5017a0ea9b | ||
|
|
05f7b0060f | ||
|
|
1043da5328 | ||
|
|
959ad35afa | ||
|
|
1e10255d01 | ||
|
|
c72693bc53 | ||
|
|
2297aad5e5 | ||
|
|
f39c0db680 | ||
|
|
23353c77f3 | ||
|
|
fee1486db6 | ||
|
|
39c4253b24 | ||
|
|
84e924f46a | ||
|
|
43364398b4 | ||
|
|
3f09f221f4 | ||
|
|
4ed922154b | ||
|
|
c0e36295b7 | ||
|
|
ef04549c8e | ||
|
|
a117c300d5 | ||
|
|
6956f7dffc | ||
|
|
fe49913550 | ||
|
|
89ddade2a3 | ||
|
|
bcb3d53ade | ||
|
|
9ab9028d79 | ||
|
|
8de6447562 | ||
|
|
2512cea19d | ||
|
|
eabe78af71 | ||
|
|
16c4ee609e | ||
|
|
37d586c5de | ||
|
|
e66847c263 | ||
|
|
a2c8a226a4 | ||
|
|
f3f6fadfe2 | ||
|
|
ff76c356c5 | ||
|
|
a22808aff3 | ||
|
|
f39fd6dfbb | ||
|
|
95598f2a76 | ||
|
|
535e104ccc | ||
|
|
522cee42e5 | ||
|
|
51656dc13f | ||
|
|
abd412b5d5 | ||
|
|
4d8c05cd92 | ||
|
|
f2ca8081af | ||
|
|
7539011be5 | ||
|
|
5117427143 | ||
|
|
e95986b830 | ||
|
|
5311972345 | ||
|
|
967295fba7 | ||
|
|
5403c5fb4f | ||
|
|
65986c3114 | ||
|
|
13a90b00f3 | ||
|
|
2ee7fc9910 | ||
|
|
a0a0efabbb | ||
|
|
9a50278b98 | ||
|
|
9519a35e32 | ||
|
|
578d5fd541 | ||
|
|
642bc5dda1 | ||
|
|
88274abdb5 | ||
|
|
aea65f5c5f | ||
|
|
edfbfde13b | ||
|
|
dcc676d60a | ||
|
|
561f61116c | ||
|
|
6a4594466b | ||
|
|
af216ee08c | ||
|
|
e493113450 | ||
|
|
74e1d5bdc4 | ||
|
|
5c0ad3e590 | ||
|
|
6e872ecab9 | ||
|
|
46a4cde77f | ||
|
|
fc7c444107 | ||
|
|
822438e0d2 | ||
|
|
de43a37e9e | ||
|
|
d4b2d2f403 | ||
|
|
6e33eab136 | ||
|
|
1e4bc85fee | ||
|
|
31fff75f08 | ||
|
|
f0620154c8 | ||
|
|
711aa1e4be | ||
|
|
854f2d75b3 | ||
|
|
a85e2f6130 | ||
|
|
bac2ba6f09 | ||
|
|
47e5270f9c | ||
|
|
68cbf09e9f | ||
|
|
9f18c88153 | ||
|
|
c6caafdcb7 | ||
|
|
b22a3e1a59 | ||
|
|
b6934bbf63 | ||
|
|
3cd6eb13a9 | ||
|
|
272be2aaad | ||
|
|
99dd6ce77f | ||
|
|
fc14455da4 | ||
|
|
26a52dae23 | ||
|
|
75864d33a6 | ||
|
|
63a97b6665 | ||
|
|
21a37a3bb0 | ||
|
|
de586b5368 | ||
|
|
ba40c3f739 | ||
|
|
31eff037a2 | ||
|
|
630dee0b2a | ||
|
|
9110f06ed5 | ||
|
|
2b0eceaa9d | ||
|
|
c96e1babe5 | ||
|
|
9388cbde5d | ||
|
|
e739cddd6a | ||
|
|
8cee6e0fc4 | ||
|
|
bcf516afeb | ||
|
|
b0e3e81b7f | ||
|
|
ce6a1215a3 | ||
|
|
f54c1dc7d0 | ||
|
|
43aaae8d47 | ||
|
|
ca463a2944 | ||
|
|
20e22589dc | ||
|
|
38d047cb8a | ||
|
|
1d977199f3 | ||
|
|
5041019d77 | ||
|
|
aa3835d3b3 | ||
|
|
678505811d | ||
|
|
a925cbaed5 | ||
|
|
7d2201d873 | ||
|
|
c0c1608d44 | ||
|
|
7bc6c83a04 | ||
|
|
3f0df82f2d | ||
|
|
328ff0251b | ||
|
|
c52582a413 | ||
|
|
3aa6eee306 | ||
|
|
7d47faba0e | ||
|
|
f6fb477898 | ||
|
|
8963960d4b | ||
|
|
ef973f676b | ||
|
|
812f9ea30e | ||
|
|
ff843b1241 | ||
|
|
59e7af149d | ||
|
|
6f14c85287 | ||
|
|
ac0dec4dbf | ||
|
|
e3d192412e | ||
|
|
9fadb6db30 | ||
|
|
f8a1b71866 | ||
|
|
c0a55acba7 | ||
|
|
4f232de634 | ||
|
|
d351ebdaa0 | ||
|
|
ab195e1d84 | ||
|
|
7041d77256 | ||
|
|
99dd052d54 | ||
|
|
de9942609e | ||
|
|
b939a9d331 | ||
|
|
0a565a7a5c | ||
|
|
bfaa478a4a | ||
|
|
f895a5623e | ||
|
|
b7c869bd64 | ||
|
|
5f677bc3b9 | ||
|
|
ccfadc2fcb | ||
|
|
c6cc304a42 | ||
|
|
3d41a7978a | ||
|
|
43fc467d58 | ||
|
|
6aba60f604 | ||
|
|
a13dedf500 | ||
|
|
cb490f23e9 | ||
|
|
7dc8a743b9 | ||
|
|
52f3b5a7bf | ||
|
|
e89e7ca10f | ||
|
|
2431dd9e93 | ||
|
|
453d3091c1 | ||
|
|
8db37491f3 | ||
|
|
cf915b9e00 | ||
|
|
326ca37847 | ||
|
|
498e604531 | ||
|
|
60b7f3be69 | ||
|
|
6ceb5cf939 | ||
|
|
0ed97db4c1 | ||
|
|
8fcd05c2bb | ||
|
|
6de4590f27 | ||
|
|
b097fd4da9 | ||
|
|
2a8e05707d | ||
|
|
a54e112978 | ||
|
|
5785eb981d | ||
|
|
49234af08b | ||
|
|
8f5717def8 | ||
|
|
9e55c0b2ca | ||
|
|
dd6ee91364 | ||
|
|
efbb838ca1 | ||
|
|
daf7d39d41 | ||
|
|
28b1194924 | ||
|
|
73d95ca187 | ||
|
|
00b7c6482f | ||
|
|
29c26e8c89 | ||
|
|
fb124dd228 | ||
|
|
0599be02dc | ||
|
|
81a88263a9 | ||
|
|
cea1fd2540 | ||
|
|
f2ced3bc7c | ||
|
|
dc2a05894b | ||
|
|
9ee962ad09 | ||
|
|
eb7ef0af4f | ||
|
|
dc51120c27 | ||
|
|
fc4c2c4346 | ||
|
|
3f6be037c1 | ||
|
|
0a80c97f02 | ||
|
|
88abafc728 | ||
|
|
ac880a0363 | ||
|
|
baebd51d99 | ||
|
|
226620eb53 | ||
|
|
1b34079d14 | ||
|
|
8deeffcdad | ||
|
|
b7e45d7305 | ||
|
|
d9077db234 | ||
|
|
439b006342 | ||
|
|
ffa74d52e5 | ||
|
|
6ccdd703e6 | ||
|
|
bb910344b8 | ||
|
|
b9c4ff9ca7 | ||
|
|
62a18d4e57 | ||
|
|
f520e381a9 | ||
|
|
1dd543ddf3 | ||
|
|
a7ef63bd8a | ||
|
|
db43c0f2a4 | ||
|
|
f0e5bb4ad1 | ||
|
|
36bba75c50 | ||
|
|
b2dc610c0b | ||
|
|
42d0eb0aba | ||
|
|
c14768182c | ||
|
|
ef7e2135bf | ||
|
|
2b58e259de | ||
|
|
ba03e8feb8 | ||
|
|
7771c6b8da | ||
|
|
04c9285ee6 | ||
|
|
bf4141e4b8 | ||
|
|
233315f668 | ||
|
|
8332fb12f1 | ||
|
|
594e69f9b7 | ||
|
|
0aac0ce495 | ||
|
|
e24b4858a4 | ||
|
|
cf2b459e48 | ||
|
|
895179fdad | ||
|
|
fe3e8792eb | ||
|
|
1916641e2e | ||
|
|
3ea0737be9 | ||
|
|
c67373a830 | ||
|
|
41cbf4d353 | ||
|
|
7a4c14f7b8 | ||
|
|
f52a4d464a | ||
|
|
aa71592a31 | ||
|
|
dc6e8f8dcb | ||
|
|
1a4836246f | ||
|
|
ab80b0742f | ||
|
|
6926aeed20 | ||
|
|
f95e42e4b9 | ||
|
|
82bee6b86e | ||
|
|
bd9bc8bcff | ||
|
|
8a6d364304 | ||
|
|
64d99a3e05 | ||
|
|
59f54b76f6 | ||
|
|
6f36d91281 | ||
|
|
e9f1fa01fc | ||
|
|
0d3a5d266b | ||
|
|
cc28cee8bd |
7
.gitignore
vendored
@@ -1,2 +1,7 @@
|
||||
/target
|
||||
vendor.tar.xz
|
||||
vendor.tar.xz
|
||||
cargo-config
|
||||
.idea
|
||||
vendor-*
|
||||
vendor_*
|
||||
.vscode-ctags
|
||||
|
||||
@@ -10,6 +10,7 @@ stages:
|
||||
test:
|
||||
script:
|
||||
- cargo check #+nightly check --features "clippy"
|
||||
- cargo test
|
||||
|
||||
build:
|
||||
only:
|
||||
|
||||
217
CHANGELOG.md
@@ -4,7 +4,220 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
## [Unreleased ]
|
||||
|
||||
## [4.3.0] - 2022-07-21
|
||||
### Added
|
||||
- Clear command for anime `asusctl anime --clear` will clear the display
|
||||
- Re-added support for LED power states on `0x1866` type keyboards
|
||||
### Changed
|
||||
- Make rog-anime more error tolerent. Remove various asserts and return errors instead
|
||||
- Return error if a pixel-gif is larger than the anime-display dimensions
|
||||
- Both Anime and Aura dbus interfaces are changed a little
|
||||
- Aura power has changed, all power related settings are now in one method
|
||||
- Anime methods will now return an error (if errored)
|
||||
- /org/asuslinux/Led renamed to /org/asuslinux/Aura
|
||||
|
||||
## [4.2.1] - 2022-07-18
|
||||
### Added
|
||||
- Add panel overdrive support (autodetects if supported)
|
||||
- Add detection of dgpu_disable and egpu_enable for diagnostic
|
||||
### Changed
|
||||
- Fixed save and restore of multizone LED settings
|
||||
- Create defaults for multizone
|
||||
|
||||
## [4.2.0] - 2022-07-16
|
||||
### Added
|
||||
- Support for GA402 Anime Matrix display (Author: I-Al-Istannen & Luke Jones)
|
||||
- Support for power-config of all LED zones. See `asusctrl led-power --help` (Author: Luke Jones, With much help from: @MNS26)
|
||||
- Full support for multizone LED <logo, keyboard, lightbar> (Author: Luke Jones, With much help from: @MNS26)
|
||||
- Add ability to load extra data from `/etc/asusd/asusd-user-ledmodes.toml` for LED support if file exits
|
||||
- Support for G513IM LED modes
|
||||
- Support for GX703HS LED modes
|
||||
### Changed
|
||||
- Dbus interface for Aura config has been changed, all power control is done with `SetLedsEnabled` and `SetLedsDisabled`
|
||||
- Data for anime-matrix now requires passing the laptop model as enum
|
||||
- Extra unit tests for anime stuff to help verify things
|
||||
|
||||
### Added
|
||||
- Support for GA503R LED modes
|
||||
### Changed
|
||||
- Refactor LED and AniMe tasks
|
||||
- Reload keyboard brightness on resume from sleep/hiber
|
||||
|
||||
## [4.1.1] - 2022-06-21
|
||||
### Changed
|
||||
- Fixes to anime matrix system thread cancelation
|
||||
|
||||
## [4.1.0] - 2022-06-20
|
||||
### Changed
|
||||
- Huge refactor to use zbus 2.2 + zvariant 3.0 in system-daemon.
|
||||
- Daemons with tasks now use `smol` for async ops.
|
||||
- Fixes to fan-curve settings from CLI (Author: Armas Span)
|
||||
- Add brightness to anime zbus notification
|
||||
- Adjust how threads in AniMe matrix controller work
|
||||
- Use proper power-state packet for keyboard LED's (Author: Martin Piffault)
|
||||
### Added
|
||||
- Support for GA402R LED modes
|
||||
- Support for GU502LV LED modes
|
||||
- Support for G512 LED modes
|
||||
- Support for G513IC LED modes (Author: dada513)
|
||||
- Support for G513QM LED modes (Author: Martin Piffault)
|
||||
- Add side-LED toggle support (Author: Martin Piffault)
|
||||
- Support reloading keyboard mode on wake (from sleep/hiber)
|
||||
- Support reloading charge-level on wake (from sleep/hiber)
|
||||
- Support running AniMe animation blocks on wake/sleep and boot/shutdown events
|
||||
|
||||
# [4.0.7] - 2021-12-19
|
||||
### Changed
|
||||
- Fix incorrect power-profile validation
|
||||
- Update asusd-ledmodes.toml to support Asus Rog Strix G15 G513QE (@LordVicky)
|
||||
- Update patch notes and links
|
||||
|
||||
# [4.0.6] - 2021-11-01
|
||||
### Changed
|
||||
- Fix CLI for bios toggles
|
||||
### Added
|
||||
- Extra commands for AniMe: pixel-image, gif, pixel-gif
|
||||
|
||||
# [4.0.5] - 2021-10-27
|
||||
### Changed
|
||||
- Convert fan curve percentage to 0-255 expected by kernel driver only if '%' char is used, otherwise the expected range for fan power is 0-255
|
||||
- Use correct error in daemon for invalid charging limit
|
||||
- Enforce charging limit values in range 20-100
|
||||
### Added
|
||||
- LED modes for G513QR
|
||||
|
||||
# [4.0.4] - 2021-10-02
|
||||
### Changed
|
||||
- Add missing Profile commands
|
||||
- Spawn tasks on individual threads to prevent blocking
|
||||
- Don't force fan-curve default on reload
|
||||
- Begin obsoleting the graphics switch command in favour of supergfxctl
|
||||
- Slim down the notification daemon to pure ASUS notifications
|
||||
|
||||
# [4.0.3] - 2021-09-16
|
||||
### Changed
|
||||
- Don't show fan-curve warning if fan-curve available
|
||||
- Add G713QR to Strix led-modes
|
||||
- Fix part of CLI fan-curve control
|
||||
|
||||
# [4.0.2] - 2021-09-14
|
||||
### Changed
|
||||
- Backup old configs to *-old if parse fails
|
||||
- Prevent some types of crashes related to unpatched kernels
|
||||
- Add better help for graphics errors
|
||||
- Add better help for asusctl general errors
|
||||
- Implement fan-curve dbus API
|
||||
- Implement partial fan-curve control via CLI tool
|
||||
+ Set fan curve for profile + fan gpu/cpu
|
||||
|
||||
# [4.0.1] - 2021-09-11
|
||||
### Changed
|
||||
- Fix asusd-ledmodes.toml
|
||||
|
||||
# [4.0.0] - 2021-09-10
|
||||
### Added
|
||||
- AniMe:
|
||||
+ Support 8bit RGB, RGBA, 16bit Greyscalw, RGB, RGBA
|
||||
+ add `AsusImage` type for slanted-template pixel-perfect images
|
||||
+ `BREAKING:` plain `Image` with time period is changed and old anime configs break as a result (sorry)
|
||||
- LED:
|
||||
+ By popular request LED prev/next cycle is added
|
||||
+ Add led modes for GX551Q
|
||||
### BREAKING CHANGES
|
||||
- Graphics control:
|
||||
+ graphics control is pulled out of asusd and moved to new package; https://gitlab.com/asus-linux/supergfxctl
|
||||
- Proflies:
|
||||
+ profiles now depend on power-profile-daemon plus kernel patches for support of platform_profile
|
||||
- if your system supports fan-curves you will also require upcoming kernel patches for this
|
||||
+ profiles are now moved to a new file
|
||||
+ fan-curves are only partially completed due to this release needing to be done sooner
|
||||
|
||||
# [3.7.2] - 2021-08-02
|
||||
### Added
|
||||
- Enable multizone support on Strix 513IH
|
||||
- Add G513QY ledmodes
|
||||
### Changed
|
||||
- Fix missing CLI command help for some supported options
|
||||
- Fix incorrectly selecting profile by name, where the active profile was being copied to the selected profile
|
||||
- Add `asusd` version back to `asusctl -v` report
|
||||
- Fix various clippy warnings
|
||||
|
||||
# [3.7.1] - 2021-06-11
|
||||
### Changed
|
||||
- Refine graphics mode switching:
|
||||
+ Disallow switching to compute or vfio mode unless existing mode is "Integrated"
|
||||
|
||||
# [3.7.0] - 2021-06-06
|
||||
### Changed
|
||||
- Set PM to auto for Nvidia always
|
||||
- Extra info output for gfx dev scan
|
||||
- Extra info in log for G-Sync to help prevent user confusion around gfx switching
|
||||
- Add GA503Q led modes
|
||||
- Added ability to fade in/out gifs and images for anime. This does break anime configs. See manual for details.
|
||||
- Added task to CtrlLed to set the keyboard LED brightness on wake from suspend
|
||||
+ requires a kernel patch which will be upstreamed and in fedora rog kernel
|
||||
- Make gfx change from nvidia to vfio/compute also force-change to integrated _then_
|
||||
to requested mode
|
||||
- Fix invalid gfx status when switching from some modes
|
||||
- Fix copy over of serde skipped config values on config reload
|
||||
|
||||
# [3.6.1] - 2021-05-25
|
||||
### Changed
|
||||
- Bugfix: write correct fan modes for profiles
|
||||
- Bugfix: apply created profiles
|
||||
|
||||
# [3.6.1] - 2021-05-25
|
||||
### Changed
|
||||
- Bugfix for cycling through profiles
|
||||
|
||||
# [3.6.0] - 2021-05-24
|
||||
### Changed
|
||||
- Add GX550L led modes
|
||||
- Don't save compute/vfio modes. Option in config for this is removed.
|
||||
- Store a temporary non-serialised option in config for if compute/vfio is active
|
||||
for informational purposes only (will not apply on boot)
|
||||
- Save state for LEDs enabled + sleep animation enabled
|
||||
- Save state for AnimMe enabled + boot animation enabled
|
||||
- Add extra config options and dbus methods
|
||||
- Add power state signals for anime and led
|
||||
- Refactor to use channels for dbus signal handler send/recv
|
||||
- Split out profiles independant parts to a rog-profiles crate
|
||||
- Cleanup dependencies
|
||||
- Fix some dbus Supported issues
|
||||
|
||||
# [3.5.2] - 2021-05-15
|
||||
### Changed
|
||||
- Bugfix: prevent the hang on compute/integrated mode change
|
||||
|
||||
# [3.5.1] - 2021-04-25
|
||||
### Changed
|
||||
+ Anime:
|
||||
- Fix using multiple configs
|
||||
|
||||
# [3.5.0] - 2021-04-25
|
||||
### Changed
|
||||
+ Keyboard:
|
||||
- Split out all aura functionality that isn't dependent on the daemon in to a
|
||||
new crate `rog-aura` (incomplete)
|
||||
- Keyboard LED control now includes:
|
||||
+ Enable/disable LED's while laptop is awake
|
||||
+ Enable/disable LED animation while laptop is suspended and AC plugged in
|
||||
- Properly reload the last used keyboard mode on boot
|
||||
+ Graphics:
|
||||
- Correctly enable compute mode for nvidia plus no-reboot or logout if switching
|
||||
from vfio/integrated/compute.
|
||||
- Add asusd config option to not save compute/vfio mode switch.
|
||||
+ Anime:
|
||||
- Enable basic multiple user anime configs (asusd-user must still be restarted)
|
||||
+ Profiles:
|
||||
- Enable dbus methods for freq min/max, fan curve, fan preset, CPU turbo enable.
|
||||
These options will apply to the active profile if no profile name is specified.
|
||||
|
||||
# [3.4.1] - 2021-04-11
|
||||
### Changed
|
||||
- Fix anime init sequence
|
||||
|
||||
# [3.4.0] - 2021-04-11
|
||||
### Changed
|
||||
@@ -12,7 +225,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Use enum to show power states, and catch missing pci path for nvidia.
|
||||
- Partial user-daemon for anime/per-key done, `asusd-user`. Includes asusd-user systemd unit.
|
||||
- user-daemon provides dbus emthods to insert anime actions, remove from index, set leds on/off
|
||||
+ COnfig file is stored in `~/.config/rog/rog-user.cfg`
|
||||
+ Config file is stored in `~/.config/rog/rog-user.cfg`
|
||||
- AniMe display parts split out to individual crate in preparation for publishing
|
||||
on crates.io
|
||||
|
||||
|
||||
1335
Cargo.lock
generated
@@ -1,8 +1,10 @@
|
||||
[workspace]
|
||||
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-types", "rog-dbus", "rog-anime"]
|
||||
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-supported", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
# thin = 57s, asusd = 9.0M
|
||||
# fat = 72s, asusd = 6.4M
|
||||
lto = "fat"
|
||||
debug = false
|
||||
opt-level = 3
|
||||
panic = "abort"
|
||||
|
||||
359
MANUAL.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# asusctrl manual
|
||||
|
||||
`asusd` is a utility for Linux to control many aspects of various ASUS laptops
|
||||
but can also be used with non-asus laptops with reduced features.
|
||||
|
||||
## Programs Available
|
||||
|
||||
- `asusd`: The main system daemon. It is autostarted by a udev rule and systemd unit.
|
||||
- `asusd-user`: The user level daemon. Currently will run an anime sequence, with RGB keyboard sequences soon.
|
||||
- `asusctl`: The CLI for interacting with the system daemon
|
||||
- `asus-notify`: A notification daemon with a user systemd unit that can be enabled.
|
||||
|
||||
## `asusd`
|
||||
|
||||
`asusd` is the main system-level daemon which will control/load/save various settings in a safe way for the user, along with exposing a *safe* dbus interface for these interactions. This section covers only the daemon plus the various configuration file options.
|
||||
|
||||
The functionality that `asusd` exposes is:
|
||||
|
||||
- anime control
|
||||
- led keyboard control (aura)
|
||||
- charge limiting
|
||||
- bios/efivar control
|
||||
- power profile switching
|
||||
- fan curves (if supported, this is auto-detected)
|
||||
|
||||
each of these will be detailed in sections.
|
||||
|
||||
### AniMe control
|
||||
|
||||
Controller for the fancy AniMe matrix display on the lid of some machines. This controller is a work in progress.
|
||||
|
||||
#### Config options
|
||||
|
||||
If you have an AniMe device a few system-level config options are enabled for you in `/etc/asusd/anime.conf`;
|
||||
|
||||
1. `"system": [],`: currently unused, is intended to be a default continuous sequence in future versions
|
||||
2. `"boot": [],`: a sequence that plays on system boot (when asusd is loaded)
|
||||
3. `"wake": [],`: a sequence that plays when waking from suspend
|
||||
4. `"shutdown": [],`: a sequence that plays when shutdown begins
|
||||
5. `"brightness": <FLOAT>`: global brightness control, where `<FLOAT> is 0.0-1.0
|
||||
|
||||
Some default examples are provided but are minimal. The full range of configuration options will be covered in another section of this manual.
|
||||
|
||||
### Led keyboard control
|
||||
|
||||
The LED controller (e.g, aura) enables setting many of the factory modes available if a laptop supports them. It also enables per-key RGB settings but this is a WIP and will likely be similar to how AniMe sequences can be created.
|
||||
|
||||
#### Supported laptops
|
||||
|
||||
Models GA401, GA502, GU502 support LED brightness change only (no RGB). However the GA401Q model can actually use three modes; static, breathe, and pulse, plus also use red to control the LED brightness intensity.
|
||||
|
||||
All models that have any form of LED mode control need to be enabled via the config file at `/etc/asusd/asusd-ledmodes.toml`. Unfortunately ASUS doesn't provide any easy way to find all the supported modes for all laptops (not even through Armory Crate and its various files, that progrma downloads only the required settings for the laptop it runs on) so each model must be added as needed.
|
||||
|
||||
#### Config options
|
||||
|
||||
The defaults are located at `/etc/asusd/asusd-ledmodes.toml`, and on `asusd` start it creates `/etc/asusd/aura.conf` whcih stores the per-mode settings. If you edit the defaults file you must remove `/etc/asusd/aura.conf` and restart `asusd.service` with `systemctl restart asusd`.
|
||||
|
||||
##### /etc/asusd/asusd-ledmodes.toml
|
||||
|
||||
Example:
|
||||
```toml
|
||||
[[led_data]]
|
||||
prod_family = "Strix"
|
||||
board_names = ["GL504G"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4", "Logo", "BarLeft", "BarRight"]
|
||||
per_key = false
|
||||
```
|
||||
|
||||
1. `prod_family`: you can find this in `journalctl -b -u asusd`, or `cat /sys/class/dmi/id/product_name`. It should be copied as written. There can be multiple `led-data` groups of the same `prod_family` with differing `board_names`.
|
||||
2. `board_names`: is an array of board names in this product family. Find this in the journal as above or by `cat /sys/class/dmi/id/board_name`.
|
||||
3. `standard` are the factory preset modes, the names should corrospond to Armory Crate names
|
||||
4. `multizone`: some models have 4 to 7 zones of LED control as shown in the example. If the laptop has no zones then an empty array will suffice.
|
||||
5. `per_key`: enable per-key RGB effects. The keyboard must support this or it has no effect.
|
||||
|
||||
##### /etc/asusd/aura.conf
|
||||
|
||||
This file can be manually edited if desired, but the `asusctl` CLI tool, or dbus methods are the preferred method. Any manual changes to this file mean that the `asusd.service` will need to be restarted, or you need to cycle between modes to force a reload.
|
||||
|
||||
### Charge control
|
||||
|
||||
Almost all modern ASUS laptops have charging limit control now. This can be controlled in `/etc/asusd/asusd.conf`.
|
||||
|
||||
```json
|
||||
"bat_charge_limit": 80,
|
||||
```
|
||||
where the number is a percentage.
|
||||
|
||||
### Bios control
|
||||
|
||||
Some options that you find in Armory Crate are available under this controller, so far there is:
|
||||
|
||||
- POST sound: this is the sound you here on bios boot post
|
||||
- G-Sync: this controls if the dGPU (Nvidia) is the *only* GPU, making it the main GPU and disabling the iGPU
|
||||
|
||||
These options are not written to the config file as they are stored in efivars. The only way to change these is to use the exposed safe dbus methods, or use the `asusctl` CLI tool.
|
||||
|
||||
### Profiles
|
||||
|
||||
asusctl can support setting a power profile via platform_profile drivers. This requires [power-profiles-daemon](https://gitlab.freedesktop.org/hadess/power-profiles-daemon) v0.10.0 minimum. It also requires the kernel patch for platform_profile support to be applied form [here](https://lkml.org/lkml/2021/8/18/1022) - this patch is merged to 5.15 kernel upstream.
|
||||
|
||||
A common use of asusctl is to bind the `fn+f5` (fan) key to `asusctl profile -n` to cycle through the 3 profiles:
|
||||
1. Balanced
|
||||
2. Performance
|
||||
3. Quiet
|
||||
|
||||
#### Fan curves
|
||||
|
||||
Fan curve support requires a laptop that supports it (this is detected automatically) and the kernel patch from [here](https://lkml.org/lkml/2021/10/23/250) which is accepted for the 5.17 kernel release .
|
||||
|
||||
The fan curve format can be of varying formats:
|
||||
|
||||
- `30c:0%,40c:5%,50c:10%,60c:20%,70c:35%,80c:55%,90c:65%,100c:65%"`
|
||||
- `30:0,40:5,50:10,60:20,70:35,80:55,90:65,100:65"`
|
||||
- `30 0,40 5,50 10,60 20,70 35,80 55,90 65,100 65"`
|
||||
- `30 0 40 5 50 10 60 20 70 35 80 55 90 65 100 65"`
|
||||
|
||||
the order must always be the same "temperature:percentage", lowest from left to rigth being highest.
|
||||
|
||||
The config file is located at `/etc/asusd/profile.conf` and is self-descriptive. On first run it is populated with the system EC defaults.
|
||||
|
||||
### Support controller
|
||||
|
||||
There is one more controller; the support controller. The sole pupose of this controller is to querie all the other controllers for information about their support level for the host laptop. Returns a json string.
|
||||
|
||||
## asusd-user
|
||||
|
||||
`asusd-user` is a usermode daemon. The intended purpose is to provide a method for users to run there own custom per-key keyboard effects and modes, AniMe sequences, and possibly their own profiles - all without overwriting the *base* system config. As such some parts of the system daemon will migrate to the user daemon over time with the expectation that the Linux system runs both.
|
||||
|
||||
As of now only AniMe is active in this with configuration in `~/.config/rog/`. On first run defaults are created that are intended to work as examples.
|
||||
|
||||
The main config is `~/.config/rog/rog-user.cfg`
|
||||
|
||||
#### Config options: AniMe
|
||||
|
||||
`~/.config/rog/rog-user.cfg` contains a setting `"active_anime": "<FILENAME>"` where `<FILENAME>` is the name of the AniMe config to use, located in the same directory and without the file postfix, e.g, `"active_anime": "anime-doom"`
|
||||
|
||||
An AniMe config itself is a file with contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "<FILENAME>",
|
||||
"anime": []
|
||||
}
|
||||
```
|
||||
|
||||
`<FILENAME>` is used as a reference internally. `"anime": []` is an array of sequences (WIP).
|
||||
|
||||
##### "anime" array options
|
||||
|
||||
Each object in the array can be one of:
|
||||
|
||||
1. AsusAnimation
|
||||
2. ImageAnimation
|
||||
3. Image
|
||||
4. Pause
|
||||
|
||||
##### AsusAnimation
|
||||
|
||||
`AsusAnimation` is specifically for running the gif files that Armory Crate comes with. `asusctl` includes all of these in `/usr/share/asusd/anime/asus/`
|
||||
```json
|
||||
"AsusAnimation": {
|
||||
"file": "<FILE_PATH>",
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
```
|
||||
|
||||
##### AsusImage
|
||||
|
||||
Virtually the same as `AsusAnimation` but for png files, typically created in the same "slanted" style using a template (`diagonal-template.png`) as the ASUS gifs for pixel perfection.
|
||||
|
||||
```json
|
||||
"AsusImage": {
|
||||
"file": "<FILE_PATH>",
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
```
|
||||
|
||||
##### ImageAnimation
|
||||
|
||||
`ImageAnimation` can play *any* gif of any size.
|
||||
|
||||
```json
|
||||
"ImageAnimation": {
|
||||
"file": "<FILE_PATH>",
|
||||
"scale": <FLOAT>,
|
||||
"angle": <FLOAT>,
|
||||
"translation": [
|
||||
<FLOAT>,
|
||||
<FLOAT>
|
||||
],
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
##### Image
|
||||
|
||||
`Image` currently requires 8bit greyscale png. It will be able to use most in future.
|
||||
|
||||
```json
|
||||
{
|
||||
"Image": {
|
||||
"file": "<FILE_PATH>",
|
||||
"scale": <FLOAT>,
|
||||
"angle": <FLOAT>,
|
||||
"translation": [
|
||||
<FLOAT>,
|
||||
<FLOAT>
|
||||
],
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
##### Pause
|
||||
|
||||
A `Pause` is handy for after an `Image` to hold the `Image` on the AniMe for a period.
|
||||
|
||||
```json
|
||||
{
|
||||
"Pause": {
|
||||
"secs": <INT>,
|
||||
"nanos": <INT>
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
##### Options for objects
|
||||
|
||||
**<FILE_PATH>**
|
||||
|
||||
Must be full path: `"/usr/share/asusd/anime/asus/gaming/Controller.gif"` or `/home/luke/Downloads/random.gif`.
|
||||
|
||||
**<FLOAT>**
|
||||
|
||||
A number from 0.0-1.0.
|
||||
- `brightness`: If it is brightness it is combined with the system daemon global brightness
|
||||
- `scale`: 1.0 is the original size with lower number shrinking, larger growing
|
||||
- `angle`: Rotation angle in radians
|
||||
- `translation`: Shift the image X -/+, and y -/+
|
||||
|
||||
**<TIME>**
|
||||
|
||||
Time is the length of time to run the gif for:
|
||||
```json
|
||||
"time": {
|
||||
"Time": {
|
||||
"secs": 5,
|
||||
"nanos": 0
|
||||
}
|
||||
},
|
||||
```
|
||||
A cycle is how many gif loops to run:
|
||||
```json
|
||||
"time": {
|
||||
"Cycles": 2
|
||||
},
|
||||
```
|
||||
`Infinite` means that this gif will never end:
|
||||
```json
|
||||
"time": "Infinite",
|
||||
```
|
||||
`Fade` allows an image or gif to fade in and out, and remain at max brightness to n time:
|
||||
```json
|
||||
"time": {
|
||||
"Fade": {
|
||||
"fade_in": {
|
||||
"secs": 2,
|
||||
"nanos": 0
|
||||
},
|
||||
"show_for": {
|
||||
"secs": 1,
|
||||
"nanos": 0
|
||||
},
|
||||
"fade_out": {
|
||||
"secs": 2,
|
||||
"nanos": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
```
|
||||
`show_for` can be `null`, if it is `null` then the `show_for` becomes `gif_time_length - fade_in - fade_out`.
|
||||
This is period for which the gif or image will be max brightness (as set).
|
||||
|
||||
**<INT>**
|
||||
|
||||
A plain non-float integer.
|
||||
|
||||
## asusctl
|
||||
|
||||
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. I can be used in any place a terminal app can be used.
|
||||
|
||||
This program will query `asusd` for the `Support` level of the laptop and show or hide options according to this support level.
|
||||
|
||||
Most commands are self-explanatory.
|
||||
|
||||
### CLI Usage and help
|
||||
|
||||
Commands are given by:
|
||||
|
||||
```
|
||||
asusctl <option> <command> <command-options>
|
||||
```
|
||||
|
||||
Help is available through:
|
||||
|
||||
```
|
||||
asusctl --help
|
||||
asusctl <command> --help
|
||||
```
|
||||
|
||||
Some commands may have subcommands:
|
||||
|
||||
```
|
||||
asusctl <command> <subcommand> --help
|
||||
```
|
||||
|
||||
### Keybinds
|
||||
|
||||
To switch to next/previous Aura modes you will need to bind both the aura keys (if available) to one of:
|
||||
**Next**
|
||||
```
|
||||
asusctl led-mode -n
|
||||
```
|
||||
**Previous**
|
||||
```
|
||||
asusctl led-mode -p
|
||||
```
|
||||
|
||||
To switch Fan/Thermal profiles you need to bind the Fn+F5 key to `asusctl profile -n`.
|
||||
|
||||
## User NOTIFICATIONS via dbus
|
||||
|
||||
If you have a notifications handler set up, or are using KDE or Gnome then you
|
||||
can enable the user service to get basic notifications when something changes.
|
||||
|
||||
```
|
||||
systemctl --user enable asus-notify.service
|
||||
systemctl --user start asus-notify.service
|
||||
```
|
||||
|
||||
# License & Trademarks
|
||||
|
||||
Mozilla Public License 2 (MPL-2.0)
|
||||
|
||||
---
|
||||
|
||||
ASUS and ROG Trademark is either a US registered trademark or trademark of ASUSTeK Computer Inc. in the United States and/or other countries.
|
||||
|
||||
Reference to any ASUS products, services, processes, or other information and/or use of ASUS Trademarks does not constitute or imply endorsement, sponsorship, or recommendation thereof by ASUS.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
25
Makefile
@@ -16,8 +16,6 @@ BIN_D := asusd
|
||||
BIN_U := asusd-user
|
||||
BIN_N := asus-notify
|
||||
LEDCFG := asusd-ledmodes.toml
|
||||
X11CFG := 90-nvidia-screen-G05.conf
|
||||
PMRULES := 90-asusd-nvidia-pm.rules
|
||||
|
||||
SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs')
|
||||
|
||||
@@ -45,35 +43,48 @@ install:
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_D)" "$(DESTDIR)$(bindir)/$(BIN_D)"
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_U)" "$(DESTDIR)$(bindir)/$(BIN_U)"
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_N)" "$(DESTDIR)$(bindir)/$(BIN_N)"
|
||||
$(INSTALL_DATA) "./data/$(PMRULES)" "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)"
|
||||
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).rules" "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
|
||||
$(INSTALL_DATA) "./data/$(LEDCFG)" "$(DESTDIR)/etc/asusd/$(LEDCFG)"
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).conf" "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
|
||||
$(INSTALL_DATA) "./data/$(X11CFG)" "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)"
|
||||
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).service" "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
|
||||
$(INSTALL_DATA) "./data/$(BIN_N).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
|
||||
$(INSTALL_DATA) "./data/$(BIN_U).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_U).service"
|
||||
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_yellow.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_green.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_red.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
|
||||
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-compute.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-compute.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-hybrid.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-hybrid.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-integrated.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-integrated.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-nvidia.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-nvidia.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-vfio.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-vfio.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/notification-reboot.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/notification-reboot.svg"
|
||||
|
||||
$(INSTALL_DATA) "./data/_asusctl" "$(DESTDIR)$(zshcpl)/_asusctl"
|
||||
$(INSTALL_DATA) "./data/completions/asusctl.fish" "$(DESTDIR)$(datarootdir)/fish/vendor_completions.d/asusctl.fish"
|
||||
cd data && find "./anime" -type f -exec install -Dm 755 "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
|
||||
cd rog-anime/data && find "./anime" -type f -exec install -Dm 755 "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
|
||||
|
||||
uninstall:
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_C)"
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_D)"
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_N)"
|
||||
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)"
|
||||
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
|
||||
rm -f "$(DESTDIR)/etc/asusd/$(LEDCFG)"
|
||||
rm -f "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
|
||||
rm -f "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)"
|
||||
rm -f "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
|
||||
rm -r "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-compute.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-hybrid.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-integrated.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-nvidia.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-vfio.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/notification-reboot.svg"
|
||||
rm -f "$(DESTDIR)$(zshcpl)/_asusctl"
|
||||
rm -f "$(DESTDIR)$(datarootdir)/fish/vendor_completions.d/asusctl.fish"
|
||||
rm -rf "$(DESTDIR)$(datarootdir)/asusd"
|
||||
|
||||
212
README.md
@@ -1,34 +1,33 @@
|
||||
# ASUS NB Ctrl
|
||||
# `asusctl` for ASUS ROG
|
||||
|
||||
[](https://www.paypal.com/donate/?hosted_button_id=4V2DEPS7K6APC) - [Asus Linux Website](https://asus-linux.org/)
|
||||
|
||||
`asusd` is a utility for Linux to control many aspects of various ASUS laptops
|
||||
but can also be used with non-asus laptops with reduced features.
|
||||
|
||||
## Kernel support
|
||||
|
||||
**The minimum supported kernel version is 5.15**
|
||||
|
||||
Fan curve control on laptops with this feature require [this patch](https://lkml.org/lkml/2021/10/23/250) which has been merged for 5.17 upstream.
|
||||
|
||||
## Goals
|
||||
|
||||
1. To provide an interface for rootless control of some system functions most users wish to control such as fan speeds, keyboard LEDs, graphics modes.
|
||||
2. Enable third-party apps to use the above with dbus methods
|
||||
3. To make the above as easy as possible for new users
|
||||
4. Respect the users resources: be small, light, and fast
|
||||
|
||||
Point 3 means that the list of supported distros is very narrow - fedora is explicitly
|
||||
supported, while Ubuntu and openSUSE are level-2 support. All other distros are *not*
|
||||
supported (while asusd might still run fine on them). For best support use fedora 32+ Workstation.
|
||||
|
||||
**NOTICE:**
|
||||
1. The following is *not* required for 5.11 kernel versions, as this version includes all the required patches.
|
||||
2. 2021 hardware has a new keyboard prod_id and the patch is included in 5.12+
|
||||
|
||||
'hid-asus-rog` DKMS module from [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/).
|
||||
|
||||
The module enables the following in kernel:
|
||||
|
||||
- Initialising the keyboard
|
||||
- All hotkeys (FN+Key combos)
|
||||
Point 4? asusd currently uses a tiny fraction of cpu time, and less than 1Mb of ram, the way
|
||||
a system-level daemon should.
|
||||
|
||||
## Discord
|
||||
|
||||
[Discord server link](https://discord.gg/ngbdKabAnP)
|
||||
[Discord server link](https://discord.gg/4ZKGd7Un5t)
|
||||
|
||||
## SUPPORTED LAPTOPS
|
||||
|
||||
@@ -51,122 +50,35 @@ will probably suffer another rename once it becomes generic enough to do so.
|
||||
- [X] Per-key LED setting
|
||||
- [X] Fancy LED modes (See examples) (currently being reworked)
|
||||
- [X] Saving settings for reload
|
||||
- [X] Logging - required for journalctl
|
||||
- [X] AniMatrix display on G14 models that include it (currently being reworked)
|
||||
- [X] AniMatrix display on G14 models that include it
|
||||
- [X] Set battery charge limit (with kernel supporting this)
|
||||
- [X] Fan curve control on G14 + G15 thanks to @Yarn1
|
||||
- [X] Graphics mode switching between iGPU, dGPU, on-demand, and vfio (for VM pass-through)
|
||||
+ [X] Requires only a logout/login
|
||||
- [X] Fan curve control on G14 + G15. Requires kernel patch (should reach 5.15 kernel)
|
||||
- [X] Toggle bios setting for boot/POST sound
|
||||
- [X] Toggle bios setting for "dedicated gfx" mode on supported laptops (g-sync)
|
||||
|
||||
# FUNCTIONS
|
||||
|
||||
## Graphics switching
|
||||
|
||||
`asusd` can switch graphics modes between:
|
||||
- `integrated`, uses the iGPU only and force-disables the dGPU
|
||||
- `hybrid`, enables Nvidia prime-offload mode
|
||||
- `nvidia`, uses the Nvidia gpu only
|
||||
- `vfio`, binds the Nvidia gpu to vfio for VM pass-through
|
||||
|
||||
**Rebootless note:** You must edit `/etc/default/grub` to remove `nvidia-drm.modeset=1`
|
||||
from the line `GRUB_CMDLINE_LINUX=` and then recreate your grub config. In fedora
|
||||
you can do this with `sudo grub2-mkconfig -o /etc/grub2.cfg` - other distro may be
|
||||
similar but with a different config location.
|
||||
|
||||
This can be disabled in the config with `"manage_gfx": false,`. Additionally there
|
||||
is an extra setting for laptops capable of g-sync dedicated gfx mode to enable the
|
||||
graphics switching to switch on dedicated gfx for "nvidia" mode.
|
||||
|
||||
This switcher conflicts with other gpu switchers like optimus-manager, suse-prime
|
||||
or ubuntu-prime, system76-power, and bbswitch. If you have issues with `asusd`
|
||||
always defaulting to `integrated` mode on boot then you will need to check for
|
||||
stray configs blocking nvidia modules from loading in:
|
||||
- `/etc/modprobe.d/`
|
||||
- `/usr/lib/modprope.d/`
|
||||
|
||||
**VFIO NOTE:** The vfio modules *must not* be compiled into the kernel, they need
|
||||
to be separate modules. If you don't plan to use vfio mode then you can ignore this
|
||||
otherwise you may need a custom built kernel.
|
||||
|
||||
To enable vfio switching you need to edit `/etc/asusd/asusd.conf` and change `"gfx_vfio_enable": false,` to true.
|
||||
|
||||
### Power management udev rule
|
||||
|
||||
If you have installed the Nvidia driver manually you will require the
|
||||
`data/90-asusd-nvidia-pm.rules` udev rule to be installed in `/etc/udev/rules.d/`.
|
||||
|
||||
### fedora and openSUSE
|
||||
|
||||
You *may* need a file `/etc/dracut.conf.d/90-nvidia-dracut-G05.conf` installed
|
||||
to stop dracut including the nvidia modules in the ramdisk if you manually
|
||||
installed the nvidia drivers.
|
||||
|
||||
```
|
||||
# filename /etc/dracut.conf.d/90-nvidia-dracut-G05.conf
|
||||
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
|
||||
# the ramdisk on updates, and to ensure the power-management udev rules run
|
||||
# on module load
|
||||
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
|
||||
```
|
||||
|
||||
and run `dracut -f` after creating it.
|
||||
|
||||
## KEYBOARD BACKLIGHT MODES
|
||||
|
||||
Models GA401, GA502, GU502 support LED brightness change only (no RGB).
|
||||
|
||||
If you model isn't getting the correct led modes, you can edit the file
|
||||
`/etc/asusd/asusd-ledmodes.toml`, the LED Mode numbers are as follows:
|
||||
|
||||
- Static
|
||||
- Breathe
|
||||
- Strobe
|
||||
- Rainbow
|
||||
- Star
|
||||
- Rain
|
||||
- Highlight
|
||||
- Laser
|
||||
- Ripple
|
||||
- Pulse
|
||||
- Comet
|
||||
- Flash
|
||||
|
||||
use `cat /sys/class/dmi/id/product_name` to get details about your laptop. You
|
||||
must restart the `asusd.service` after editing.
|
||||
|
||||
# Keybinds
|
||||
|
||||
To switch to next/previous Aura modes you will need to bind both the aura keys (if available) to one of:
|
||||
**Next**
|
||||
```
|
||||
asusctl led-mode -n
|
||||
```
|
||||
**Previous**
|
||||
```
|
||||
asusctl led-mode -p
|
||||
```
|
||||
|
||||
To switch Fan/Thermal profiles you need to bind the Fn+F5 key to `asusctl profile -n`.
|
||||
|
||||
# BUILDING
|
||||
|
||||
Requirements are rust >= 1.40 installed from rustup.io if the distro provided version is too old, and `make`.
|
||||
Requirements are rust >= 1.57 installed from rustup.io if the distro provided version is too old, and `make`.
|
||||
|
||||
**Ubuntu*:** `apt install libclang-dev libudev-dev`
|
||||
**Ubuntu (unsuported):**
|
||||
|
||||
**fedora:** `dnf install clang-devel systemd-devel`
|
||||
apt install libclang-dev libudev-dev
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
make
|
||||
sudo make install
|
||||
|
||||
**fedora:**
|
||||
|
||||
dnf install clang-devel systemd-devel cargo
|
||||
make
|
||||
sudo make install
|
||||
|
||||
## Installing
|
||||
- Fedora copr = https://copr.fedorainfracloud.org/coprs/lukenukem/asus-linux/
|
||||
- openSUSE = https://download.opensuse.org/repositories/home:/luke_nukem:/asus/
|
||||
- Ubuntu = not supported due to packaging woes, but you can build and install on your own.
|
||||
|
||||
Packaging and auto-builds are available [here](https://build.opensuse.org/package/show/home:luke_nukem:asus/asus-nb-ctrl)
|
||||
|
||||
Download repositories are available [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/)
|
||||
|
||||
---
|
||||
|
||||
Run `make` then `sudo make install` then reboot.
|
||||
=======
|
||||
|
||||
The default init method is to use the udev rule, this ensures that the service is
|
||||
started when the device is initialised and ready.
|
||||
@@ -179,70 +91,26 @@ $ systemctl daemon-reload && systemctl restart asusd
|
||||
|
||||
You may also need to activate the service for debian install. If running Pop!_OS, I suggest disabling `system76-power` gnome-shell extension and systemd service.
|
||||
|
||||
If you would like to run this daemon on another non-ASUS laptop you can. You'll
|
||||
have all features available except the LED and AniMe control (further controllers
|
||||
can be added on request). You will need to install the alternative service from
|
||||
`data/asusd-alt.service`.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
Run `sudo make uninstall` in the source repo, and remove `/etc/asusd/`.
|
||||
|
||||
# USAGE
|
||||
|
||||
Commands are given by:
|
||||
|
||||
```
|
||||
asusctl <option> <command> <command-options>
|
||||
```
|
||||
|
||||
Help is available through:
|
||||
|
||||
```
|
||||
asusctl --help
|
||||
asusctl <command> --help
|
||||
```
|
||||
|
||||
Some commands may have subcommands:
|
||||
|
||||
```
|
||||
asusctl <command> <subcommand> --help
|
||||
```
|
||||
|
||||
## User NOTIFICATIONS via dbus
|
||||
|
||||
If you have a notifications handler set up, or are using KDE or Gnome then you
|
||||
can enable the user service to get basic notifications when something changes.
|
||||
|
||||
```
|
||||
systemctl --user enable asus-notify.service
|
||||
systemctl --user start asus-notify.service
|
||||
```
|
||||
|
||||
# OTHER
|
||||
|
||||
## AniMe input
|
||||
|
||||
You will want to look at what MeuMeu has done with [https://github.com/Meumeu/ZephyrusBling/](https://github.com/Meumeu/ZephyrusBling/)
|
||||
|
||||
## Supporting more laptops
|
||||
|
||||
Please file a support request.
|
||||
|
||||
## Notes:
|
||||
|
||||
- If charge limit or fan modes are not working, then you may require a kernel newer than 5.6.10.
|
||||
- AniMe device check is performed on start, if your device has one it will be detected.
|
||||
- GA14/GA401 and GA15/GA502/GU502, You will need kernel [patches](https://lab.retarded.farm/zappel/asus-rog-zephyrus-g14/-/tree/master/kernel_patches), these are on their way to the kernel upstream.
|
||||
- On fedora manually installed Nvidia driver requires a dracut config as follows:
|
||||
```
|
||||
# filename/etc/dracut.conf.d/90-nvidia-dracut-G05.conf
|
||||
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
|
||||
# the ramdisk on updates, and to ensure the power-management udev rules run
|
||||
# on module load
|
||||
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
|
||||
```
|
||||
|
||||
# License
|
||||
# License & Trademarks
|
||||
|
||||
Mozilla Public License 2 (MPL-2.0)
|
||||
|
||||
---
|
||||
|
||||
ASUS and ROG Trademark is either a US registered trademark or trademark of ASUSTeK Computer Inc. in the United States and/or other countries.
|
||||
|
||||
Reference to any ASUS products, services, processes, or other information and/or use of ASUS Trademarks does not constitute or imply endorsement, sponsorship, or recommendation thereof by ASUS.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
[package]
|
||||
name = "asus-notify"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
zbus = "^2.2"
|
||||
# serialisation
|
||||
serde_json = "^1.0"
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
daemon = { path = "../daemon" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_supported = { path = "../rog-supported" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
smol = "^1.2"
|
||||
|
||||
[dependencies.notify-rust]
|
||||
version = "^4.3"
|
||||
default-features = false
|
||||
features = ["z"]
|
||||
features = ["z"]
|
||||
|
||||
@@ -1,109 +1,145 @@
|
||||
use notify_rust::{Hint, Notification, NotificationHandle};
|
||||
use rog_dbus::{DbusProxies, Signals};
|
||||
use rog_types::profile::Profile;
|
||||
use std::error::Error;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use rog_aura::AuraEffect;
|
||||
use rog_dbus::{
|
||||
zbus_charge::ChargeProxy, zbus_led::LedProxy, zbus_profile::ProfileProxy,
|
||||
zbus_rogbios::RogBiosProxy,
|
||||
};
|
||||
use rog_profiles::Profile;
|
||||
use smol::{future, Executor};
|
||||
use std::{
|
||||
error::Error,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
|
||||
const NOTIF_HEADER: &str = "ROG Control";
|
||||
|
||||
macro_rules! notify {
|
||||
($notifier:ident, $last_notif:ident, $data:expr) => {
|
||||
if let Some(notif) = $last_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
if let Ok(x) = $notifier($data) {
|
||||
$last_notif.replace(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! base_notification {
|
||||
($body:expr) => {
|
||||
Notification::new()
|
||||
.summary(NOTIF_HEADER)
|
||||
.body($body)
|
||||
.timeout(2000)
|
||||
.show()
|
||||
};
|
||||
}
|
||||
|
||||
type SharedHandle = Arc<Mutex<Option<NotificationHandle>>>;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("asus-notify version {}", env!("CARGO_PKG_VERSION"));
|
||||
println!(" daemon version {}", daemon::VERSION);
|
||||
println!(" rog-dbus version {}", rog_dbus::VERSION);
|
||||
|
||||
let (proxies, conn) = DbusProxies::new()?;
|
||||
let signals = Signals::new(&proxies)?;
|
||||
let last_notification: SharedHandle = Arc::new(Mutex::new(None));
|
||||
|
||||
let mut last_profile_notif: Option<NotificationHandle> = None;
|
||||
let mut last_led_notif: Option<NotificationHandle> = None;
|
||||
let mut last_gfx_notif: Option<NotificationHandle> = None;
|
||||
let mut last_chrg_notif: Option<NotificationHandle> = None;
|
||||
|
||||
let recv = proxies.setup_recv(conn);
|
||||
let mut err_count = 0;
|
||||
loop {
|
||||
sleep(Duration::from_millis(100));
|
||||
if let Err(err) = recv.next_signal() {
|
||||
if err_count < 3 {
|
||||
err_count += 1;
|
||||
println!("{}", err);
|
||||
}
|
||||
if err_count == 3 {
|
||||
err_count += 1;
|
||||
println!("Max error count reached. Spooling silently.");
|
||||
}
|
||||
sleep(Duration::from_millis(2000));
|
||||
continue;
|
||||
}
|
||||
err_count = 0;
|
||||
|
||||
if let Ok(mut lock) = signals.gfx_vendor.lock() {
|
||||
if let Some(vendor) = lock.take() {
|
||||
if let Some(notif) = last_gfx_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_notif(&format!(
|
||||
"Graphics mode changed to {}",
|
||||
<&str>::from(vendor)
|
||||
))?;
|
||||
last_gfx_notif = Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut lock) = signals.charge.lock() {
|
||||
if let Some(limit) = lock.take() {
|
||||
if let Some(notif) = last_chrg_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_notif(&format!("Battery charge limit changed to {}", limit))?;
|
||||
last_chrg_notif = Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut lock) = signals.profile.lock() {
|
||||
if let Some(profile) = lock.take() {
|
||||
if let Some(notif) = last_profile_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
if let Ok(profile) = serde_json::from_str(&profile) {
|
||||
let profile: Profile = profile;
|
||||
if let Ok(name) = proxies.profile().active_profile_name() {
|
||||
let x = do_thermal_notif(&profile, &name)?;
|
||||
last_profile_notif = Some(x);
|
||||
let executor = Executor::new();
|
||||
// BIOS notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = RogBiosProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_post_boot_sound().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_post_sound_notif, lock, &out.sound());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
if let Ok(mut lock) = signals.led_mode.lock() {
|
||||
if let Some(ledmode) = lock.take() {
|
||||
if let Some(notif) = last_led_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_notif(&format!(
|
||||
"Keyboard LED mode changed to {}",
|
||||
ledmode.mode_name()
|
||||
))?;
|
||||
last_led_notif = Some(x);
|
||||
}
|
||||
}
|
||||
// Charge notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = ChargeProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_charge().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_charge_notif, lock, &out.limit);
|
||||
}
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
// Profile notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = ProfileProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_profile().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_thermal_notif, lock, &out.profile);
|
||||
}
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
// LED notif
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = LedProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_led().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = last_notification.try_lock() {
|
||||
notify!(do_led_notif, lock, &out.data);
|
||||
}
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
loop {
|
||||
smol::block_on(executor.tick());
|
||||
}
|
||||
}
|
||||
|
||||
fn do_thermal_notif(profile: &Profile, label: &str) -> Result<NotificationHandle, Box<dyn Error>> {
|
||||
let fan = profile.fan_preset;
|
||||
let turbo = if profile.turbo { "enabled" } else { "disabled" };
|
||||
let icon = match fan {
|
||||
0 => "asus_notif_yellow",
|
||||
1 => "asus_notif_red",
|
||||
2 => "asus_notif_green",
|
||||
_ => "asus_notif_red",
|
||||
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
|
||||
let icon = match profile {
|
||||
Profile::Balanced => "asus_notif_yellow",
|
||||
Profile::Performance => "asus_notif_red",
|
||||
Profile::Quiet => "asus_notif_green",
|
||||
};
|
||||
let profile: &str = (*profile).into();
|
||||
let x = Notification::new()
|
||||
.summary("ASUS ROG")
|
||||
.body(&format!(
|
||||
"Thermal profile changed to {}, turbo {}",
|
||||
label.to_uppercase(),
|
||||
turbo
|
||||
"Thermal profile changed to {}",
|
||||
profile.to_uppercase(),
|
||||
))
|
||||
.hint(Hint::Resident(true))
|
||||
.timeout(2000)
|
||||
@@ -114,11 +150,17 @@ fn do_thermal_notif(profile: &Profile, label: &str) -> Result<NotificationHandle
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
fn do_notif(body: &str) -> Result<NotificationHandle, Box<dyn Error>> {
|
||||
let x = Notification::new()
|
||||
.summary("ASUS ROG")
|
||||
.body(body)
|
||||
.timeout(2000)
|
||||
.show()?;
|
||||
Ok(x)
|
||||
fn do_led_notif(ledmode: &AuraEffect) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!(
|
||||
"Keyboard LED mode changed to {}",
|
||||
ledmode.mode_name()
|
||||
))
|
||||
}
|
||||
|
||||
fn do_charge_notif(limit: &u8) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!("Battery charge limit changed to {}", limit))
|
||||
}
|
||||
|
||||
fn do_post_sound_notif(on: &bool) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!("BIOS Post sound {}", on))
|
||||
}
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
[package]
|
||||
name = "asusctl"
|
||||
version = "3.4.0"
|
||||
version = "4.3.0"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# serialisation
|
||||
serde_json = "^1.0"
|
||||
zbus = "^2.2"
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_supported = { path = "../rog-supported" }
|
||||
daemon = { path = "../daemon" }
|
||||
gumdrop = "^0.8"
|
||||
yansi-term = "^0.1"
|
||||
toml = "^0.5.8"
|
||||
|
||||
sysfs-class = "^0.1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tinybmp = "^0.2.3"
|
||||
glam = "0.14.0"
|
||||
tinybmp = "^0.3.3"
|
||||
glam = "0.20.5"
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
gif = "^0.11.2"
|
||||
gif = "^0.11.2"
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
use std::{env, error::Error, path::Path, process::exit};
|
||||
|
||||
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
|
||||
use rog_dbus::AuraDbusClient;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
if args.len() != 3 {
|
||||
println!("Usage: <filepath> <brightness>");
|
||||
println!("e.g, asusctl/examples/doom_large.png 0.8");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
let matrix =
|
||||
AnimeDiagonal::from_png(Path::new(&args[1]), None, args[2].parse::<f32>().unwrap())?;
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
use rog_anime::AnimeDataBuffer;
|
||||
use rog_dbus::AuraDbusClient;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
|
||||
fn main() {
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
let mut matrix = AnimeDataBuffer::new();
|
||||
matrix.get_mut()[1] = 100; // start = 1
|
||||
for n in matrix.get_mut()[2..32].iter_mut() {
|
||||
*n = 250;
|
||||
}
|
||||
matrix.get_mut()[32] = 100; // end
|
||||
matrix.get_mut()[34] = 100; // start x = 0
|
||||
matrix.get_mut()[66] = 100; // end
|
||||
matrix.get_mut()[69] = 100; // start x = 1
|
||||
matrix.get_mut()[101] = 100; // end
|
||||
matrix.get_mut()[102] = 100; // start
|
||||
matrix.get_mut()[134] = 100; // end
|
||||
matrix.get_mut()[137] = 100; // start
|
||||
matrix.get_mut()[169] = 100; // end
|
||||
matrix.get_mut()[170] = 100; // start
|
||||
matrix.get_mut()[202] = 100; // end
|
||||
matrix.get_mut()[204] = 100; // start
|
||||
matrix.get_mut()[236] = 100; // end
|
||||
matrix.get_mut()[237] = 100; // start
|
||||
matrix.get_mut()[268] = 100; // end
|
||||
matrix.get_mut()[270] = 100; // start
|
||||
matrix.get_mut()[301] = 100; // end
|
||||
matrix.get_mut()[302] = 100; // start
|
||||
matrix.get_mut()[332] = 100; // end
|
||||
matrix.get_mut()[334] = 100; // start
|
||||
matrix.get_mut()[364] = 100; // end
|
||||
matrix.get_mut()[365] = 100; // start
|
||||
matrix.get_mut()[394] = 100; // end
|
||||
matrix.get_mut()[396] = 100; // start
|
||||
matrix.get_mut()[425] = 100; // end
|
||||
matrix.get_mut()[426] = 100; // start
|
||||
matrix.get_mut()[454] = 100; // end
|
||||
matrix.get_mut()[456] = 100; // start
|
||||
matrix.get_mut()[484] = 100; // end
|
||||
matrix.get_mut()[485] = 100; // start
|
||||
matrix.get_mut()[512] = 100; // end
|
||||
matrix.get_mut()[514] = 100; // start
|
||||
matrix.get_mut()[541] = 100; // end
|
||||
matrix.get_mut()[542] = 100; // start
|
||||
matrix.get_mut()[568] = 100; // end
|
||||
matrix.get_mut()[570] = 100; // start
|
||||
matrix.get_mut()[596] = 100; // end
|
||||
matrix.get_mut()[597] = 100; // start
|
||||
matrix.get_mut()[622] = 100; // end
|
||||
matrix.get_mut()[624] = 100; // start
|
||||
matrix.get_mut()[649] = 100; // end
|
||||
matrix.get_mut()[650] = 100; // start
|
||||
matrix.get_mut()[674] = 100; // end
|
||||
matrix.get_mut()[676] = 100; // start
|
||||
matrix.get_mut()[700] = 100; // end
|
||||
matrix.get_mut()[701] = 100; // start
|
||||
matrix.get_mut()[724] = 100; // end
|
||||
matrix.get_mut()[726] = 100; // start
|
||||
matrix.get_mut()[749] = 100; // end
|
||||
matrix.get_mut()[750] = 100; // start
|
||||
matrix.get_mut()[772] = 100; // end
|
||||
matrix.get_mut()[774] = 100; // start
|
||||
matrix.get_mut()[796] = 100; // end
|
||||
matrix.get_mut()[797] = 100; // start
|
||||
matrix.get_mut()[818] = 100; // end
|
||||
matrix.get_mut()[820] = 100; // start
|
||||
matrix.get_mut()[841] = 100; // end
|
||||
matrix.get_mut()[842] = 100; // start
|
||||
matrix.get_mut()[862] = 100; // end
|
||||
matrix.get_mut()[864] = 100; // start
|
||||
matrix.get_mut()[884] = 100; // end
|
||||
matrix.get_mut()[885] = 100; // start
|
||||
matrix.get_mut()[904] = 100; // end
|
||||
matrix.get_mut()[906] = 100; // start
|
||||
matrix.get_mut()[925] = 100; // end
|
||||
matrix.get_mut()[926] = 100; // start
|
||||
matrix.get_mut()[944] = 100; // end
|
||||
matrix.get_mut()[946] = 100; // start
|
||||
matrix.get_mut()[964] = 100; // end
|
||||
matrix.get_mut()[965] = 100; // start
|
||||
matrix.get_mut()[982] = 100; // end
|
||||
matrix.get_mut()[984] = 100; // start
|
||||
matrix.get_mut()[1001] = 100; // end
|
||||
matrix.get_mut()[1002] = 100; // start
|
||||
matrix.get_mut()[1018] = 100; // end
|
||||
matrix.get_mut()[1020] = 100; // start
|
||||
matrix.get_mut()[1036] = 100; // end
|
||||
matrix.get_mut()[1037] = 100; // start
|
||||
matrix.get_mut()[1052] = 100; // end
|
||||
matrix.get_mut()[1054] = 100; // start
|
||||
matrix.get_mut()[1069] = 100; // end
|
||||
matrix.get_mut()[1070] = 100; // start
|
||||
matrix.get_mut()[1084] = 100; // end
|
||||
matrix.get_mut()[1086] = 100; // start
|
||||
matrix.get_mut()[1100] = 100; // end
|
||||
matrix.get_mut()[1101] = 100; // start
|
||||
matrix.get_mut()[1114] = 100; // end
|
||||
matrix.get_mut()[1116] = 100; // start
|
||||
matrix.get_mut()[1129] = 100; // end
|
||||
matrix.get_mut()[1130] = 100; // start
|
||||
matrix.get_mut()[1142] = 100; // end
|
||||
matrix.get_mut()[1144] = 100; // start
|
||||
matrix.get_mut()[1156] = 100; // end
|
||||
matrix.get_mut()[1157] = 100; // start
|
||||
matrix.get_mut()[1168] = 100; // end
|
||||
matrix.get_mut()[1170] = 100; // start
|
||||
matrix.get_mut()[1181] = 100; // end
|
||||
matrix.get_mut()[1182] = 100; // start
|
||||
matrix.get_mut()[1192] = 100; // end
|
||||
matrix.get_mut()[1194] = 100; // start
|
||||
matrix.get_mut()[1204] = 100; // end
|
||||
matrix.get_mut()[1205] = 100; // start
|
||||
matrix.get_mut()[1214] = 100; // end
|
||||
matrix.get_mut()[1216] = 100; // start
|
||||
matrix.get_mut()[1225] = 100; // end
|
||||
matrix.get_mut()[1226] = 100; // start
|
||||
matrix.get_mut()[1234] = 100; // end
|
||||
matrix.get_mut()[1236] = 100; // start
|
||||
for n in matrix.get_mut()[1237..1244].iter_mut() {
|
||||
*n = 250;
|
||||
}
|
||||
matrix.get_mut()[1244] = 100; // end
|
||||
println!("{:?}", &matrix);
|
||||
|
||||
client.proxies().anime().write(matrix).unwrap();
|
||||
}
|
||||
32
asusctl/examples/anime-diag-png.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::{env, error::Error, path::Path, process::exit};
|
||||
|
||||
use rog_anime::{usb::get_anime_type, AnimeDiagonal, AnimeType};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
if args.len() != 3 {
|
||||
println!("Usage: <filepath> <brightness>");
|
||||
println!("e.g, asusctl/examples/doom_large.png 0.8");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
let matrix = AnimeDiagonal::from_png(
|
||||
Path::new(&args[1]),
|
||||
None,
|
||||
args[2].parse::<f32>().unwrap(),
|
||||
AnimeType::GA401,
|
||||
)?;
|
||||
|
||||
let anime_type = get_anime_type()?;
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(matrix.into_data_buffer(anime_type)?)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
|
||||
use rog_dbus::AuraDbusClient;
|
||||
use rog_anime::{usb::get_anime_type, AnimeDiagonal, AnimeType};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
@@ -9,10 +9,10 @@ use rog_dbus::AuraDbusClient;
|
||||
// 74w x 36h diagonal used by the windows app
|
||||
|
||||
fn main() {
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
for step in (2..50).rev() {
|
||||
let mut matrix = AnimeDiagonal::new(None);
|
||||
let mut matrix = AnimeDiagonal::new(AnimeType::GA401, None);
|
||||
for c in (0..60).into_iter().step_by(step) {
|
||||
for i in matrix.get_mut().iter_mut() {
|
||||
i[c] = 50;
|
||||
@@ -25,8 +25,12 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let m = <AnimeDataBuffer>::from(&matrix);
|
||||
client.proxies().anime().write(m).unwrap();
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(matrix.into_data_buffer(anime_type).unwrap())
|
||||
.unwrap();
|
||||
sleep(Duration::from_millis(300));
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::{env, path::Path, thread::sleep};
|
||||
|
||||
use rog_anime::{ActionData, AnimeAction, Sequences};
|
||||
use rog_dbus::AuraDbusClient;
|
||||
use rog_anime::{usb::get_anime_type, ActionData, ActionLoader, Sequences};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() {
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
if args.len() != 3 {
|
||||
@@ -14,10 +14,11 @@ fn main() {
|
||||
|
||||
let path = Path::new(&args[1]);
|
||||
let brightness = args[2].parse::<f32>().unwrap();
|
||||
let mut seq = Sequences::new();
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
let mut seq = Sequences::new(anime_type);
|
||||
seq.insert(
|
||||
0,
|
||||
&AnimeAction::AsusAnimation {
|
||||
&ActionLoader::AsusAnimation {
|
||||
file: path.into(),
|
||||
time: rog_anime::AnimTime::Infinite,
|
||||
brightness,
|
||||
@@ -1,5 +1,6 @@
|
||||
use rog_anime::{AnimeDataBuffer, AnimeGrid};
|
||||
use rog_dbus::AuraDbusClient;
|
||||
use rog_anime::{usb::get_anime_type, AnimeDataBuffer, AnimeGrid};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
@@ -7,8 +8,9 @@ use rog_dbus::AuraDbusClient;
|
||||
// 74w x 36h diagonal used by the windows app
|
||||
|
||||
fn main() {
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
let mut matrix = AnimeGrid::new(None);
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
let mut matrix = AnimeGrid::new(anime_type);
|
||||
let tmp = matrix.get_mut();
|
||||
|
||||
let mut i = 0;
|
||||
@@ -38,7 +40,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let matrix = <AnimeDataBuffer>::from(matrix);
|
||||
let matrix = <AnimeDataBuffer>::try_from(matrix).unwrap();
|
||||
|
||||
client.proxies().anime().write(matrix).unwrap();
|
||||
}
|
||||
130
asusctl/examples/anime-outline.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use rog_anime::{usb::get_anime_type, AnimeDataBuffer};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
let mut matrix = AnimeDataBuffer::new(anime_type);
|
||||
matrix.data_mut()[1] = 100; // start = 1
|
||||
for n in matrix.data_mut()[2..32].iter_mut() {
|
||||
*n = 250;
|
||||
}
|
||||
matrix.data_mut()[32] = 100; // end
|
||||
matrix.data_mut()[34] = 100; // start x = 0
|
||||
matrix.data_mut()[66] = 100; // end
|
||||
matrix.data_mut()[69] = 100; // start x = 1
|
||||
matrix.data_mut()[101] = 100; // end
|
||||
matrix.data_mut()[102] = 100; // start
|
||||
matrix.data_mut()[134] = 100; // end
|
||||
matrix.data_mut()[137] = 100; // start
|
||||
matrix.data_mut()[169] = 100; // end
|
||||
matrix.data_mut()[170] = 100; // start
|
||||
matrix.data_mut()[202] = 100; // end
|
||||
matrix.data_mut()[204] = 100; // start
|
||||
matrix.data_mut()[236] = 100; // end
|
||||
matrix.data_mut()[237] = 100; // start
|
||||
matrix.data_mut()[268] = 100; // end
|
||||
matrix.data_mut()[270] = 100; // start
|
||||
matrix.data_mut()[301] = 100; // end
|
||||
matrix.data_mut()[302] = 100; // start
|
||||
matrix.data_mut()[332] = 100; // end
|
||||
matrix.data_mut()[334] = 100; // start
|
||||
matrix.data_mut()[364] = 100; // end
|
||||
matrix.data_mut()[365] = 100; // start
|
||||
matrix.data_mut()[394] = 100; // end
|
||||
matrix.data_mut()[396] = 100; // start
|
||||
matrix.data_mut()[425] = 100; // end
|
||||
matrix.data_mut()[426] = 100; // start
|
||||
matrix.data_mut()[454] = 100; // end
|
||||
matrix.data_mut()[456] = 100; // start
|
||||
matrix.data_mut()[484] = 100; // end
|
||||
matrix.data_mut()[485] = 100; // start
|
||||
matrix.data_mut()[512] = 100; // end
|
||||
matrix.data_mut()[514] = 100; // start
|
||||
matrix.data_mut()[541] = 100; // end
|
||||
matrix.data_mut()[542] = 100; // start
|
||||
matrix.data_mut()[568] = 100; // end
|
||||
matrix.data_mut()[570] = 100; // start
|
||||
matrix.data_mut()[596] = 100; // end
|
||||
matrix.data_mut()[597] = 100; // start
|
||||
matrix.data_mut()[622] = 100; // end
|
||||
matrix.data_mut()[624] = 100; // start
|
||||
matrix.data_mut()[649] = 100; // end
|
||||
matrix.data_mut()[650] = 100; // start
|
||||
matrix.data_mut()[674] = 100; // end
|
||||
matrix.data_mut()[676] = 100; // start
|
||||
matrix.data_mut()[700] = 100; // end
|
||||
matrix.data_mut()[701] = 100; // start
|
||||
matrix.data_mut()[724] = 100; // end
|
||||
matrix.data_mut()[726] = 100; // start
|
||||
matrix.data_mut()[749] = 100; // end
|
||||
matrix.data_mut()[750] = 100; // start
|
||||
matrix.data_mut()[772] = 100; // end
|
||||
matrix.data_mut()[774] = 100; // start
|
||||
matrix.data_mut()[796] = 100; // end
|
||||
matrix.data_mut()[797] = 100; // start
|
||||
matrix.data_mut()[818] = 100; // end
|
||||
matrix.data_mut()[820] = 100; // start
|
||||
matrix.data_mut()[841] = 100; // end
|
||||
matrix.data_mut()[842] = 100; // start
|
||||
matrix.data_mut()[862] = 100; // end
|
||||
matrix.data_mut()[864] = 100; // start
|
||||
matrix.data_mut()[884] = 100; // end
|
||||
matrix.data_mut()[885] = 100; // start
|
||||
matrix.data_mut()[904] = 100; // end
|
||||
matrix.data_mut()[906] = 100; // start
|
||||
matrix.data_mut()[925] = 100; // end
|
||||
matrix.data_mut()[926] = 100; // start
|
||||
matrix.data_mut()[944] = 100; // end
|
||||
matrix.data_mut()[946] = 100; // start
|
||||
matrix.data_mut()[964] = 100; // end
|
||||
matrix.data_mut()[965] = 100; // start
|
||||
matrix.data_mut()[982] = 100; // end
|
||||
matrix.data_mut()[984] = 100; // start
|
||||
matrix.data_mut()[1001] = 100; // end
|
||||
matrix.data_mut()[1002] = 100; // start
|
||||
matrix.data_mut()[1018] = 100; // end
|
||||
matrix.data_mut()[1020] = 100; // start
|
||||
matrix.data_mut()[1036] = 100; // end
|
||||
matrix.data_mut()[1037] = 100; // start
|
||||
matrix.data_mut()[1052] = 100; // end
|
||||
matrix.data_mut()[1054] = 100; // start
|
||||
matrix.data_mut()[1069] = 100; // end
|
||||
matrix.data_mut()[1070] = 100; // start
|
||||
matrix.data_mut()[1084] = 100; // end
|
||||
matrix.data_mut()[1086] = 100; // start
|
||||
matrix.data_mut()[1100] = 100; // end
|
||||
matrix.data_mut()[1101] = 100; // start
|
||||
matrix.data_mut()[1114] = 100; // end
|
||||
matrix.data_mut()[1116] = 100; // start
|
||||
matrix.data_mut()[1129] = 100; // end
|
||||
matrix.data_mut()[1130] = 100; // start
|
||||
matrix.data_mut()[1142] = 100; // end
|
||||
matrix.data_mut()[1144] = 100; // start
|
||||
matrix.data_mut()[1156] = 100; // end
|
||||
matrix.data_mut()[1157] = 100; // start
|
||||
matrix.data_mut()[1168] = 100; // end
|
||||
matrix.data_mut()[1170] = 100; // start
|
||||
matrix.data_mut()[1181] = 100; // end
|
||||
matrix.data_mut()[1182] = 100; // start
|
||||
matrix.data_mut()[1192] = 100; // end
|
||||
matrix.data_mut()[1194] = 100; // start
|
||||
matrix.data_mut()[1204] = 100; // end
|
||||
matrix.data_mut()[1205] = 100; // start
|
||||
matrix.data_mut()[1214] = 100; // end
|
||||
matrix.data_mut()[1216] = 100; // start
|
||||
matrix.data_mut()[1225] = 100; // end
|
||||
matrix.data_mut()[1226] = 100; // start
|
||||
matrix.data_mut()[1234] = 100; // end
|
||||
matrix.data_mut()[1236] = 100; // start
|
||||
for n in matrix.data_mut()[1237..1244].iter_mut() {
|
||||
*n = 250;
|
||||
}
|
||||
matrix.data_mut()[1244] = 100; // end
|
||||
println!("{:?}", &matrix);
|
||||
|
||||
client.proxies().anime().write(matrix).unwrap();
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::{env, error::Error, path::Path, process::exit};
|
||||
|
||||
use rog_anime::{
|
||||
usb::get_anime_type,
|
||||
AnimeDataBuffer, {AnimeImage, Vec2},
|
||||
};
|
||||
use rog_dbus::AuraDbusClient;
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
if args.len() != 7 {
|
||||
@@ -15,6 +17,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
let anime_type = get_anime_type()?;
|
||||
let matrix = AnimeImage::from_png(
|
||||
Path::new(&args[1]),
|
||||
args[2].parse::<f32>().unwrap(),
|
||||
@@ -24,12 +27,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
args[5].parse::<f32>().unwrap(),
|
||||
),
|
||||
args[6].parse::<f32>().unwrap(),
|
||||
anime_type,
|
||||
)?;
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))
|
||||
.write(<AnimeDataBuffer>::try_from(&matrix)?)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
@@ -1,14 +1,16 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::{
|
||||
env, error::Error, f32::consts::PI, path::Path, process::exit, thread::sleep, time::Duration,
|
||||
};
|
||||
|
||||
use rog_anime::{
|
||||
usb::get_anime_type,
|
||||
AnimeDataBuffer, {AnimeImage, Vec2},
|
||||
};
|
||||
use rog_dbus::AuraDbusClient;
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
if args.len() != 7 {
|
||||
@@ -17,6 +19,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
let anime_type = get_anime_type()?;
|
||||
let mut matrix = AnimeImage::from_png(
|
||||
Path::new(&args[1]),
|
||||
args[2].parse::<f32>().unwrap(),
|
||||
@@ -26,6 +29,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
args[5].parse::<f32>().unwrap(),
|
||||
),
|
||||
args[6].parse::<f32>().unwrap(),
|
||||
anime_type,
|
||||
)?;
|
||||
|
||||
loop {
|
||||
@@ -38,7 +42,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))
|
||||
.write(<AnimeDataBuffer>::try_from(&matrix)?)
|
||||
.unwrap();
|
||||
sleep(Duration::from_micros(500));
|
||||
}
|
||||
@@ -1,54 +1,4 @@
|
||||
use gumdrop::Options;
|
||||
use rog_types::error::AuraError;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum AnimeStatusValue {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
impl FromStr for AnimeStatusValue {
|
||||
type Err = AuraError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
match s.as_str() {
|
||||
"on" => Ok(AnimeStatusValue::On),
|
||||
"off" => Ok(AnimeStatusValue::Off),
|
||||
_ => {
|
||||
print!("Invalid argument, must be one of: on, off");
|
||||
Err(AuraError::ParseAnime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<AnimeStatusValue> for bool {
|
||||
fn from(value: AnimeStatusValue) -> Self {
|
||||
match value {
|
||||
AnimeStatusValue::On => true,
|
||||
AnimeStatusValue::Off => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeLeds {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(
|
||||
no_long,
|
||||
required,
|
||||
short = "b",
|
||||
meta = "",
|
||||
help = "set all leds brightness value"
|
||||
)]
|
||||
led_brightness: u8,
|
||||
}
|
||||
impl AnimeLeds {
|
||||
pub fn led_brightness(&self) -> u8 {
|
||||
self.led_brightness
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeCommand {
|
||||
@@ -56,21 +6,32 @@ pub struct AnimeCommand {
|
||||
pub help: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "turn on/off the panel (accept/reject write requests)"
|
||||
help = "enable/disable the panel LEDs (does not erase last image)"
|
||||
)]
|
||||
pub turn: Option<AnimeStatusValue>,
|
||||
#[options(meta = "", help = "turn on/off the panel at boot (with Asus effect)")]
|
||||
pub boot: Option<AnimeStatusValue>,
|
||||
pub enable: Option<bool>,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "enable/disable system animations (boot/sleep/shutdown)"
|
||||
)]
|
||||
pub boot_enable: Option<bool>,
|
||||
#[options(meta = "", help = "set global AniMe brightness value")]
|
||||
pub brightness: Option<f32>,
|
||||
#[options(help = "clear the display")]
|
||||
pub clear: bool,
|
||||
#[options(command)]
|
||||
pub command: Option<AnimeActions>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub enum AnimeActions {
|
||||
#[options(help = "change all leds brightness")]
|
||||
Leds(AnimeLeds),
|
||||
#[options(help = "display an 8bit greyscale png")]
|
||||
#[options(help = "display a PNG image")]
|
||||
Image(AnimeImage),
|
||||
#[options(help = "display a diagonal/pixel-perfect PNG")]
|
||||
PixelImage(AnimeImageDiagonal),
|
||||
#[options(help = "display an animated GIF")]
|
||||
Gif(AnimeGif),
|
||||
#[options(help = "display an animated diagonal/pixel-perfect GIF")]
|
||||
PixelGif(AnimeGifDiagonal),
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
@@ -90,3 +51,53 @@ pub struct AnimeImage {
|
||||
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")]
|
||||
pub bright: f32,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeImageDiagonal {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "full path to the png to display")]
|
||||
pub path: String,
|
||||
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")]
|
||||
pub bright: f32,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeGif {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "full path to the png to display")]
|
||||
pub path: String,
|
||||
#[options(meta = "", default = "1.0", help = "scale 1.0 == normal")]
|
||||
pub scale: f32,
|
||||
#[options(meta = "", default = "0.0", help = "x position (float)")]
|
||||
pub x_pos: f32,
|
||||
#[options(meta = "", default = "0.0", help = "y position (float)")]
|
||||
pub y_pos: f32,
|
||||
#[options(meta = "", default = "0.0", help = "the angle in radians")]
|
||||
pub angle: f32,
|
||||
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")]
|
||||
pub bright: f32,
|
||||
#[options(
|
||||
meta = "",
|
||||
default = "1",
|
||||
help = "how many loops to play - 0 is infinite"
|
||||
)]
|
||||
pub loops: u32,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeGifDiagonal {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "full path to the png to display")]
|
||||
pub path: String,
|
||||
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")]
|
||||
pub bright: f32,
|
||||
#[options(
|
||||
meta = "",
|
||||
default = "1",
|
||||
help = "how many loops to play - 0 is infinite"
|
||||
)]
|
||||
pub loops: u32,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,71 @@
|
||||
use gumdrop::Options;
|
||||
use rog_types::{
|
||||
aura_modes::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed},
|
||||
error::AuraError,
|
||||
};
|
||||
use rog_aura::{error::Error, AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct LedPowerCommand1 {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "Control if LEDs enabled while awake <true/false>")]
|
||||
pub awake: Option<bool>,
|
||||
#[options(meta = "", help = "Use with awake option <true/false>")]
|
||||
pub keyboard: Option<bool>,
|
||||
#[options(meta = "", help = "Use with awake option <true/false>")]
|
||||
pub lightbar: Option<bool>,
|
||||
#[options(meta = "", help = "Control boot animations <true/false>")]
|
||||
pub boot: Option<bool>,
|
||||
#[options(meta = "", help = "Control suspend animations <true/false>")]
|
||||
pub sleep: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct LedPowerCommand2 {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(command)]
|
||||
pub command: Option<SetAuraEnabled>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub enum SetAuraEnabled {
|
||||
/// Applies to both old and new models
|
||||
#[options(help = "set <keyboard, logo, lightbar> to enabled while device is awake")]
|
||||
Awake(AuraEnabled),
|
||||
#[options(help = "set <keyboard, logo, lightbar> to enabled while the device is booting")]
|
||||
Boot(AuraEnabled),
|
||||
#[options(help = "set <keyboard, logo, lightbar> to animate while the device is suspended")]
|
||||
Sleep(AuraEnabled),
|
||||
#[options(help = "set <keyboard, logo, lightbar> to animate while the device is shutdown")]
|
||||
Shutdown(AuraEnabled),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Options)]
|
||||
pub struct AuraEnabled {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "<true/false>")]
|
||||
pub keyboard: Option<bool>,
|
||||
#[options(meta = "", help = "<true/false>")]
|
||||
pub logo: Option<bool>,
|
||||
#[options(meta = "", help = "<true/false>")]
|
||||
pub lightbar: Option<bool>,
|
||||
}
|
||||
|
||||
// impl FromStr for AuraEnabled {
|
||||
// type Err = Error;
|
||||
|
||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// let s = s.to_lowercase();
|
||||
// dbg!(s);
|
||||
// Ok(Self {
|
||||
// help: false,
|
||||
// keyboard: None,
|
||||
// logo: None,
|
||||
// lightbar: None,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct LedBrightness {
|
||||
level: Option<u32>,
|
||||
@@ -19,7 +80,7 @@ impl LedBrightness {
|
||||
}
|
||||
}
|
||||
impl FromStr for LedBrightness {
|
||||
type Err = AuraError;
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
@@ -30,7 +91,7 @@ impl FromStr for LedBrightness {
|
||||
"high" => Ok(LedBrightness { level: Some(0x03) }),
|
||||
_ => {
|
||||
print!("Invalid argument, must be one of: off, low, med, high");
|
||||
Err(AuraError::ParseBrightness)
|
||||
Err(Error::ParseBrightness)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +114,14 @@ pub struct SingleSpeed {
|
||||
help: bool,
|
||||
#[options(no_long, meta = "WORD", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Options, Default)]
|
||||
pub struct SingleSpeedDirection {
|
||||
#[options(help = "print help message")]
|
||||
@@ -62,6 +130,12 @@ pub struct SingleSpeedDirection {
|
||||
pub direction: Direction,
|
||||
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Options)]
|
||||
@@ -70,6 +144,12 @@ pub struct SingleColour {
|
||||
help: bool,
|
||||
#[options(no_long, meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour: Colour,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Options)]
|
||||
@@ -80,6 +160,12 @@ pub struct SingleColourSpeed {
|
||||
pub colour: Colour,
|
||||
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Options, Default)]
|
||||
@@ -92,10 +178,16 @@ pub struct TwoColourSpeed {
|
||||
pub colour2: Colour,
|
||||
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Options)]
|
||||
pub struct MultiColour {
|
||||
pub struct MultiZone {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(short = "a", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
@@ -127,6 +219,8 @@ pub struct MultiColourSpeed {
|
||||
/// Byte value for setting the built-in mode.
|
||||
///
|
||||
/// Enum corresponds to the required integer value
|
||||
///
|
||||
// NOTE: The option names here must match those in rog-aura crate
|
||||
#[derive(Options)]
|
||||
pub enum SetAuraBuiltin {
|
||||
#[options(help = "set a single static colour")]
|
||||
@@ -138,7 +232,7 @@ pub enum SetAuraBuiltin {
|
||||
#[options(help = "rainbow cycling in one of four directions")]
|
||||
Rainbow(SingleSpeedDirection),
|
||||
#[options(help = "rain pattern mimicking raindrops")]
|
||||
Star(TwoColourSpeed),
|
||||
Stars(TwoColourSpeed),
|
||||
#[options(help = "rain pattern of three preset colours")]
|
||||
Rain(SingleSpeed),
|
||||
#[options(help = "pressed keys are highlighted to fade")]
|
||||
@@ -153,10 +247,6 @@ pub enum SetAuraBuiltin {
|
||||
Comet(SingleColour),
|
||||
#[options(help = "set a wide vertical line zooming from left")]
|
||||
Flash(SingleColour),
|
||||
#[options(help = "4-zone multi-colour")]
|
||||
MultiStatic(MultiColour),
|
||||
#[options(help = "4-zone multi-colour breathing")]
|
||||
MultiBreathe(MultiColourSpeed),
|
||||
}
|
||||
|
||||
impl Default for SetAuraBuiltin {
|
||||
@@ -169,6 +259,7 @@ impl From<&SingleColour> for AuraEffect {
|
||||
fn from(aura: &SingleColour) -> Self {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -178,6 +269,7 @@ impl From<&SingleSpeed> for AuraEffect {
|
||||
fn from(aura: &SingleSpeed) -> Self {
|
||||
Self {
|
||||
speed: aura.speed,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -188,6 +280,7 @@ impl From<&SingleColourSpeed> for AuraEffect {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
speed: aura.speed,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -198,6 +291,7 @@ impl From<&TwoColourSpeed> for AuraEffect {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
colour2: aura.colour2,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -208,6 +302,7 @@ impl From<&SingleSpeedDirection> for AuraEffect {
|
||||
Self {
|
||||
speed: aura.speed,
|
||||
direction: aura.direction,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -236,7 +331,7 @@ impl From<&SetAuraBuiltin> for AuraEffect {
|
||||
data.mode = AuraModeNum::Rainbow;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Star(x) => {
|
||||
SetAuraBuiltin::Stars(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Star;
|
||||
data
|
||||
@@ -276,55 +371,6 @@ impl From<&SetAuraBuiltin> for AuraEffect {
|
||||
data.mode = AuraModeNum::Flash;
|
||||
data
|
||||
}
|
||||
_ => AuraEffect::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SetAuraBuiltin> for Vec<AuraEffect> {
|
||||
fn from(aura: &SetAuraBuiltin) -> Vec<AuraEffect> {
|
||||
let mut zones = vec![AuraEffect::default(); 4];
|
||||
match aura {
|
||||
SetAuraBuiltin::MultiStatic(data) => {
|
||||
zones[0].mode = AuraModeNum::Static;
|
||||
zones[0].zone = AuraZone::One;
|
||||
zones[0].colour1 = data.colour1;
|
||||
|
||||
zones[1].mode = AuraModeNum::Static;
|
||||
zones[1].zone = AuraZone::Two;
|
||||
zones[1].colour1 = data.colour2;
|
||||
|
||||
zones[2].mode = AuraModeNum::Static;
|
||||
zones[2].zone = AuraZone::Three;
|
||||
zones[2].colour1 = data.colour3;
|
||||
|
||||
zones[3].mode = AuraModeNum::Static;
|
||||
zones[3].zone = AuraZone::Four;
|
||||
zones[3].colour1 = data.colour4;
|
||||
}
|
||||
SetAuraBuiltin::MultiBreathe(data) => {
|
||||
zones[0].mode = AuraModeNum::Breathe;
|
||||
zones[0].zone = AuraZone::One;
|
||||
zones[0].colour1 = data.colour1;
|
||||
zones[0].speed = data.speed;
|
||||
|
||||
zones[1].mode = AuraModeNum::Breathe;
|
||||
zones[1].zone = AuraZone::Two;
|
||||
zones[1].colour1 = data.colour2;
|
||||
zones[1].speed = data.speed;
|
||||
|
||||
zones[2].mode = AuraModeNum::Breathe;
|
||||
zones[2].zone = AuraZone::Three;
|
||||
zones[2].colour1 = data.colour3;
|
||||
zones[2].speed = data.speed;
|
||||
|
||||
zones[3].mode = AuraModeNum::Breathe;
|
||||
zones[3].zone = AuraZone::Four;
|
||||
zones[3].colour1 = data.colour4;
|
||||
zones[3].speed = data.speed;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
zones
|
||||
}
|
||||
}
|
||||
|
||||
97
asusctl/src/cli_opts.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use crate::{
|
||||
anime_cli::AnimeCommand,
|
||||
aura_cli::{LedBrightness, LedPowerCommand1, LedPowerCommand2, SetAuraBuiltin},
|
||||
profiles_cli::{FanCurveCommand, ProfileCommand},
|
||||
};
|
||||
use gumdrop::Options;
|
||||
|
||||
#[derive(Default, Options)]
|
||||
pub struct CliStart {
|
||||
#[options(help_flag, help = "print help message")]
|
||||
pub help: bool,
|
||||
#[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(command)]
|
||||
pub command: Option<CliCommand>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub enum CliCommand {
|
||||
#[options(help = "Set the keyboard lighting from built-in modes")]
|
||||
LedMode(LedModeCommand),
|
||||
#[options(help = "Set the LED power states")]
|
||||
LedPow1(LedPowerCommand1),
|
||||
#[options(help = "Set the LED power states")]
|
||||
LedPow2(LedPowerCommand2),
|
||||
#[options(help = "Set or select platform_profile")]
|
||||
Profile(ProfileCommand),
|
||||
#[options(help = "Set, select, or modify fan curves if supported")]
|
||||
FanCurve(FanCurveCommand),
|
||||
#[options(help = "Set the graphics mode (obsoleted by supergfxctl)")]
|
||||
Graphics(GraphicsCommand),
|
||||
#[options(name = "anime", help = "Manage AniMe Matrix")]
|
||||
Anime(AnimeCommand),
|
||||
#[options(help = "Change bios settings")]
|
||||
Bios(BiosCommand),
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct LedModeCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(help = "switch to next aura mode")]
|
||||
pub next_mode: bool,
|
||||
#[options(help = "switch to previous aura mode")]
|
||||
pub prev_mode: bool,
|
||||
#[options(command)]
|
||||
pub command: Option<SetAuraBuiltin>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct GraphicsCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
}
|
||||
|
||||
#[derive(Options, Debug)]
|
||||
pub struct BiosCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "S",
|
||||
no_long,
|
||||
help = "set bios POST sound: asusctl -p <true/false>"
|
||||
)]
|
||||
pub post_sound_set: Option<bool>,
|
||||
#[options(no_long, short = "s", help = "read bios POST sound")]
|
||||
pub post_sound_get: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "D",
|
||||
no_long,
|
||||
help = "activate dGPU dedicated/G-Sync: asusctl -d <true/false>, reboot required"
|
||||
)]
|
||||
pub dedicated_gfx_set: Option<bool>,
|
||||
#[options(no_long, short = "d", help = "get GPU mode")]
|
||||
pub dedicated_gfx_get: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "O",
|
||||
no_long,
|
||||
help = "Set device panel overdrive <true/false>"
|
||||
)]
|
||||
pub panel_overdrive_set: Option<bool>,
|
||||
#[options(no_long, short = "o", help = "get panel overdrive")]
|
||||
pub panel_overdrive_get: bool,
|
||||
}
|
||||
51
asusctl/src/profiles_cli.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use gumdrop::Options;
|
||||
use rog_profiles::{fan_curve_set::CurveData, FanCurvePU, Profile};
|
||||
|
||||
#[derive(Debug, Clone, Options)]
|
||||
pub struct ProfileCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[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<Profile>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Options)]
|
||||
pub struct FanCurveCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
|
||||
#[options(help = "get enabled fan profiles")]
|
||||
pub get_enabled: bool,
|
||||
#[options(help = "set the active profile's fan curve to default")]
|
||||
pub default: bool,
|
||||
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "profile to modify fan-curve for. Shows data if no options provided"
|
||||
)]
|
||||
pub mod_profile: Option<Profile>,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "enable or disable <true/false> fan curve. `mod-profile` required"
|
||||
)]
|
||||
pub enabled: Option<bool>,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "select fan <cpu/gpu> to modify. `mod-profile` required"
|
||||
)]
|
||||
pub fan: Option<FanCurvePU>,
|
||||
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "data format = 30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%.
|
||||
`--mod-profile` required. If '%' is omitted the fan range is 0-255"
|
||||
)]
|
||||
pub data: Option<CurveData>,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "daemon-user"
|
||||
version = "1.0.0"
|
||||
version = "1.3.0"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
description = "Usermode daemon for user settings, anime, per-key lighting"
|
||||
@@ -21,10 +21,12 @@ serde_derive = "^1.0"
|
||||
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
rog_supported = { path = "../rog-supported" }
|
||||
|
||||
dirs = "3.0.1"
|
||||
dirs = "^4.0"
|
||||
|
||||
zbus = "^1.9.1"
|
||||
zvariant = "^2.6"
|
||||
zvariant_derive = "^2.6"
|
||||
zbus = "^2.2"
|
||||
zvariant = "^3.0"
|
||||
zvariant_derive = "^3.0"
|
||||
|
||||
smol = "^1.2"
|
||||
@@ -1,6 +1,6 @@
|
||||
use rog_anime::{ActionData, AnimTime, AnimeAction, Sequences, Vec2};
|
||||
use rog_dbus::AuraDbusClient;
|
||||
//use crate::dbus::DbusEvents;
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_anime::{ActionData, ActionLoader, AnimTime, Fade, Sequences, Vec2};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
@@ -15,7 +15,45 @@ use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
use zvariant_derive::Type;
|
||||
|
||||
use crate::{error::Error, user_config::UserConfig};
|
||||
use crate::{error::Error, user_config::UserAnimeConfig};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
|
||||
pub struct Timer {
|
||||
type_of: TimeType,
|
||||
/// If time type is Timer then this is milliseonds, otherwise it is animation loop count
|
||||
count: u64,
|
||||
/// Used only for `TimeType::Timer`, milliseonds to fade the image in for
|
||||
fade_in: Option<u64>,
|
||||
/// Used only for `TimeType::Timer`, milliseonds to fade the image out for
|
||||
fade_out: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<Timer> for AnimTime {
|
||||
fn from(time: Timer) -> Self {
|
||||
match time.type_of {
|
||||
TimeType::Timer => {
|
||||
if time.fade_in.is_some() || time.fade_out.is_some() {
|
||||
let fade_in = time
|
||||
.fade_in
|
||||
.map_or(Duration::from_secs(0), Duration::from_millis);
|
||||
let fade_out = time
|
||||
.fade_out
|
||||
.map_or(Duration::from_secs(0), Duration::from_millis);
|
||||
let show_for = if time.count != 0 {
|
||||
Some(Duration::from_millis(time.count))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
AnimTime::Fade(Fade::new(fade_in, show_for, fade_out))
|
||||
} else {
|
||||
AnimTime::Time(Duration::from_millis(time.count))
|
||||
}
|
||||
}
|
||||
TimeType::Count => AnimTime::Count(time.count as u32),
|
||||
TimeType::Infinite => AnimTime::Infinite,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
|
||||
pub enum TimeType {
|
||||
@@ -28,15 +66,15 @@ pub enum TimeType {
|
||||
/// and a zbus server behind `Arc<Mutex<T>>`
|
||||
pub struct CtrlAnimeInner<'a> {
|
||||
sequences: Sequences,
|
||||
client: AuraDbusClient<'a>,
|
||||
do_early_return: &'a AtomicBool,
|
||||
client: RogDbusClientBlocking<'a>,
|
||||
do_early_return: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl<'a> CtrlAnimeInner<'static> {
|
||||
pub fn new(
|
||||
sequences: Sequences,
|
||||
client: AuraDbusClient<'static>,
|
||||
do_early_return: &'static AtomicBool,
|
||||
client: RogDbusClientBlocking<'static>,
|
||||
do_early_return: Arc<AtomicBool>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
sequences,
|
||||
@@ -45,7 +83,7 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
})
|
||||
}
|
||||
/// To be called on each main loop iteration to pump out commands to the anime
|
||||
pub fn run(&self) -> Result<(), Error> {
|
||||
pub fn run(&'a self) -> Result<(), Error> {
|
||||
if self.do_early_return.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -53,39 +91,24 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
for action in self.sequences.iter() {
|
||||
match action {
|
||||
ActionData::Animation(frames) => {
|
||||
let mut count = 0;
|
||||
let start = Instant::now();
|
||||
'animation: loop {
|
||||
for frame in frames.frames() {
|
||||
if self.do_early_return.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
self.client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(frame.frame().clone())
|
||||
.unwrap();
|
||||
if let AnimTime::Time(time) = frames.duration() {
|
||||
if Instant::now().duration_since(start) > time {
|
||||
break 'animation;
|
||||
}
|
||||
}
|
||||
sleep(frame.delay());
|
||||
rog_anime::run_animation(frames, &|output| {
|
||||
if self.do_early_return.load(Ordering::Acquire) {
|
||||
return Ok(true); // Do safe exit
|
||||
}
|
||||
if let AnimTime::Cycles(times) = frames.duration() {
|
||||
count += 1;
|
||||
if count >= times {
|
||||
break 'animation;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(output)
|
||||
.map_err(|e| AnimeError::Dbus(format!("{}", e)))
|
||||
.map(|_| false)
|
||||
})?;
|
||||
}
|
||||
ActionData::Image(image) => {
|
||||
self.client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(image.as_ref().clone())
|
||||
.unwrap();
|
||||
.ok();
|
||||
}
|
||||
ActionData::Pause(duration) => {
|
||||
let start = Instant::now();
|
||||
@@ -111,34 +134,36 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
}
|
||||
|
||||
pub struct CtrlAnime<'a> {
|
||||
config: Arc<Mutex<UserConfig>>,
|
||||
client: AuraDbusClient<'a>,
|
||||
config: Arc<Mutex<UserAnimeConfig>>,
|
||||
client: RogDbusClientBlocking<'a>,
|
||||
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
|
||||
/// Must be the same Atomic as in CtrlAnimeInner
|
||||
inner_early_return: &'a AtomicBool,
|
||||
inner_early_return: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl<'a> CtrlAnime<'static> {
|
||||
pub fn new(
|
||||
config: Arc<Mutex<UserConfig>>,
|
||||
config: Arc<Mutex<UserAnimeConfig>>,
|
||||
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
|
||||
client: AuraDbusClient<'static>,
|
||||
inner_early_return: &'static AtomicBool,
|
||||
client: RogDbusClientBlocking<'static>,
|
||||
inner_early_return: Arc<AtomicBool>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(CtrlAnime {
|
||||
config,
|
||||
inner,
|
||||
client,
|
||||
inner,
|
||||
inner_early_return,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
pub async fn add_to_server(self, server: &mut zbus::Connection) {
|
||||
server
|
||||
.object_server()
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
|
||||
self,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
println!("CtrlAnime: add_to_server {}", err);
|
||||
err
|
||||
@@ -147,7 +172,6 @@ impl<'a> CtrlAnime<'static> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The pattern for a zbus method is:
|
||||
// - Get config lock if required
|
||||
// - Set inner_early_return to stop the inner run loop temporarily
|
||||
@@ -160,18 +184,13 @@ impl CtrlAnime<'static> {
|
||||
&mut self,
|
||||
index: u32,
|
||||
file: String,
|
||||
time: TimeType,
|
||||
count: u32,
|
||||
time: Timer,
|
||||
brightness: f32,
|
||||
) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let time: AnimTime = match time {
|
||||
TimeType::Timer => AnimTime::Time(Duration::from_millis(count as u64)),
|
||||
TimeType::Count => AnimTime::Cycles(count),
|
||||
TimeType::Infinite => AnimTime::Infinite,
|
||||
};
|
||||
let time: AnimTime = time.into();
|
||||
let file = Path::new(&file);
|
||||
let action = AnimeAction::AsusAnimation {
|
||||
let action = ActionLoader::AsusAnimation {
|
||||
file: file.into(),
|
||||
brightness,
|
||||
time,
|
||||
@@ -181,7 +200,10 @@ impl CtrlAnime<'static> {
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller.sequences.insert(index as usize, &action)?;
|
||||
controller
|
||||
.sequences
|
||||
.insert(index as usize, &action)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write()?;
|
||||
@@ -203,19 +225,14 @@ impl CtrlAnime<'static> {
|
||||
scale: f32,
|
||||
angle: f32,
|
||||
xy: (f32, f32),
|
||||
time: TimeType,
|
||||
count: u32,
|
||||
time: Timer,
|
||||
brightness: f32,
|
||||
) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let time: AnimTime = match time {
|
||||
TimeType::Timer => AnimTime::Time(Duration::from_millis(count as u64)),
|
||||
TimeType::Count => AnimTime::Cycles(count),
|
||||
TimeType::Infinite => AnimTime::Infinite,
|
||||
};
|
||||
let time: AnimTime = time.into();
|
||||
let file = Path::new(&file);
|
||||
let translation = Vec2::new(xy.0, xy.1);
|
||||
let action = AnimeAction::ImageAnimation {
|
||||
let action = ActionLoader::ImageAnimation {
|
||||
file: file.into(),
|
||||
scale,
|
||||
angle,
|
||||
@@ -228,7 +245,10 @@ impl CtrlAnime<'static> {
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller.sequences.insert(index as usize, &action)?;
|
||||
controller
|
||||
.sequences
|
||||
.insert(index as usize, &action)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write()?;
|
||||
@@ -243,6 +263,7 @@ impl CtrlAnime<'static> {
|
||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn insert_image(
|
||||
&mut self,
|
||||
index: u32,
|
||||
@@ -250,23 +271,29 @@ impl CtrlAnime<'static> {
|
||||
scale: f32,
|
||||
angle: f32,
|
||||
xy: (f32, f32),
|
||||
time: Timer,
|
||||
brightness: f32,
|
||||
) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let file = Path::new(&file);
|
||||
let action = AnimeAction::Image {
|
||||
let time = time.into();
|
||||
let action = ActionLoader::Image {
|
||||
file: file.into(),
|
||||
scale,
|
||||
angle,
|
||||
translation: Vec2::new(xy.0, xy.1),
|
||||
brightness,
|
||||
time,
|
||||
};
|
||||
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller.sequences.insert(index as usize, &action)?;
|
||||
controller
|
||||
.sequences
|
||||
.insert(index as usize, &action)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write()?;
|
||||
@@ -283,12 +310,15 @@ impl CtrlAnime<'static> {
|
||||
|
||||
pub fn insert_pause(&mut self, index: u32, millis: u64) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let action = AnimeAction::Pause(Duration::from_millis(millis));
|
||||
let action = ActionLoader::Pause(Duration::from_millis(millis));
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller.sequences.insert(index as usize, &action)?;
|
||||
controller
|
||||
.sequences
|
||||
.insert(index as usize, &action)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write()?;
|
||||
@@ -329,13 +359,13 @@ impl CtrlAnime<'static> {
|
||||
pub fn set_state(&mut self, on: bool) -> zbus::fdo::Result<()> {
|
||||
// Operations here need to be in specific order
|
||||
if on {
|
||||
self.client.proxies().anime().toggle_on(on)?;
|
||||
self.client.proxies().anime().set_on_off(on).ok();
|
||||
// Let the inner loop run
|
||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
self.client.proxies().anime().toggle_on(on)?;
|
||||
self.client.proxies().anime().set_on_off(on).ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,67 +1,72 @@
|
||||
use rog_dbus::AuraDbusClient;
|
||||
use rog_types::supported::SupportedFunctions;
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use rog_user::{
|
||||
ctrl_anime::{CtrlAnime, CtrlAnimeInner},
|
||||
user_config::*,
|
||||
DBUS_NAME,
|
||||
};
|
||||
use smol::Executor;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use zbus::{fdo, Connection};
|
||||
use zbus::Connection;
|
||||
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
/// The anime loop needs an atomic to make it exit early if required
|
||||
static ANIME_INNER_EARLY_RETURN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!(" rog-dbus version {}", rog_dbus::VERSION);
|
||||
println!(" user daemon v{}", rog_user::VERSION);
|
||||
println!(" rog-anime v{}", rog_anime::VERSION);
|
||||
println!(" rog-dbus v{}", rog_dbus::VERSION);
|
||||
println!("rog-supported v{}", rog_supported::VERSION);
|
||||
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
let supported = client.proxies().supported().get_supported_functions()?;
|
||||
let supported = serde_json::from_str::<SupportedFunctions>(&&supported).unwrap();
|
||||
let (client, _) = RogDbusClientBlocking::new()?;
|
||||
let supported = client.proxies().supported().supported_functions()?;
|
||||
|
||||
let mut config = UserConfig::new();
|
||||
config.load_config()?;
|
||||
let anime = config.create_anime()?;
|
||||
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
|
||||
// Create server
|
||||
let connection = Connection::new_session()?;
|
||||
fdo::DBusProxy::new(&connection)?
|
||||
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
|
||||
let mut server = zbus::ObjectServer::new(&connection);
|
||||
let executor = Executor::new();
|
||||
|
||||
let early_return = Arc::new(AtomicBool::new(false));
|
||||
// Set up the anime data and run loop/thread
|
||||
if supported.anime_ctrl.0 {
|
||||
// Inner behind mutex required for thread safety
|
||||
let inner = Arc::new(Mutex::new(CtrlAnimeInner::new(
|
||||
anime,
|
||||
client,
|
||||
&ANIME_INNER_EARLY_RETURN,
|
||||
)?));
|
||||
// Need new client object for dbus control part
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
let anime_control =
|
||||
CtrlAnime::new(config, inner.clone(), client, &ANIME_INNER_EARLY_RETURN)?;
|
||||
anime_control.add_to_server(&mut server);
|
||||
// Thread using inner
|
||||
let _anime_thread = thread::Builder::new()
|
||||
.name("Anime User".into())
|
||||
.spawn(move || loop {
|
||||
if let Ok(inner) = inner.try_lock() {
|
||||
inner.run().unwrap();
|
||||
let anime_type = get_anime_type()?;
|
||||
let anime_config = UserAnimeConfig::load_config(config.active_anime)?;
|
||||
let anime = anime_config.create_anime(anime_type)?;
|
||||
let anime_config = Arc::new(Mutex::new(anime_config));
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
// Create server
|
||||
let mut connection = Connection::session().await.unwrap();
|
||||
connection.request_name(DBUS_NAME).await.unwrap();
|
||||
|
||||
// Inner behind mutex required for thread safety
|
||||
let inner = Arc::new(Mutex::new(
|
||||
CtrlAnimeInner::new(anime, client, early_return.clone()).unwrap(),
|
||||
));
|
||||
// Need new client object for dbus control part
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let anime_control =
|
||||
CtrlAnime::new(anime_config, inner.clone(), client, early_return).unwrap();
|
||||
anime_control.add_to_server(&mut connection).await;
|
||||
loop {
|
||||
if let Ok(inner) = inner.clone().try_lock() {
|
||||
inner.run().ok();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
if supported.keyboard_led.per_key_led_mode {}
|
||||
// if supported.keyboard_led.per_key_led_mode {
|
||||
// executor
|
||||
// .spawn(async move {
|
||||
// //
|
||||
// })
|
||||
// .detach();
|
||||
// }
|
||||
|
||||
loop {
|
||||
if let Err(err) = server.try_handle_next() {
|
||||
println!("{}", err);
|
||||
}
|
||||
smol::block_on(executor.tick());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,6 @@ pub mod ctrl_anime;
|
||||
|
||||
pub mod zbus_anime;
|
||||
|
||||
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
|
||||
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
|
||||
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -4,53 +4,164 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use rog_anime::{AnimTime, AnimeAction, Sequences, Vec2};
|
||||
use rog_anime::{ActionLoader, AnimTime, AnimeType, Fade, Sequences, Vec2};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct UserConfig {
|
||||
pub anime: Vec<AnimeAction>,
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct UserAnimeConfig {
|
||||
pub name: String,
|
||||
pub anime: Vec<ActionLoader>,
|
||||
}
|
||||
|
||||
impl UserConfig {
|
||||
pub fn new() -> Self {
|
||||
let x = Self {
|
||||
impl UserAnimeConfig {
|
||||
pub fn create_anime(&self, anime_type: AnimeType) -> Result<Sequences, Error> {
|
||||
let mut seq = Sequences::new(anime_type);
|
||||
|
||||
for (idx, action) in self.anime.iter().enumerate() {
|
||||
seq.insert(idx, action)?;
|
||||
}
|
||||
|
||||
Ok(seq)
|
||||
}
|
||||
|
||||
pub fn write(&self) -> Result<(), Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(Error::XdgVars);
|
||||
};
|
||||
|
||||
path.push("rog");
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
let name = self.name.clone();
|
||||
path.push(name + ".cfg");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&path)?;
|
||||
|
||||
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||
file.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_config(name: String) -> Result<UserAnimeConfig, Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(Error::XdgVars);
|
||||
};
|
||||
|
||||
path.push("rog");
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
|
||||
path.push(name.clone() + ".cfg");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
let default = UserAnimeConfig {
|
||||
name,
|
||||
..Default::default()
|
||||
};
|
||||
let json = serde_json::to_string_pretty(&default).unwrap();
|
||||
file.write_all(json.as_bytes())?;
|
||||
return Ok(default);
|
||||
} else if let Ok(data) = serde_json::from_str::<UserAnimeConfig>(&buf) {
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
Err(Error::ConfigLoadFail)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserAnimeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "default".to_string(),
|
||||
anime: vec![
|
||||
AnimeAction::AsusAnimation {
|
||||
ActionLoader::AsusImage {
|
||||
file: "/usr/share/asusd/anime/custom/diagonal-template.png".into(),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
None,
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
},
|
||||
ActionLoader::AsusAnimation {
|
||||
file: "/usr/share/asusd/anime/asus/rog/Sunset.gif".into(),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Cycles(1),
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(6),
|
||||
None,
|
||||
Duration::from_secs(3),
|
||||
)),
|
||||
},
|
||||
AnimeAction::ImageAnimation {
|
||||
ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Time(Duration::from_secs(5)),
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
Some(Duration::from_secs(2)),
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
},
|
||||
AnimeAction::Image {
|
||||
ActionLoader::Image {
|
||||
file: "/usr/share/asusd/anime/custom/rust.png".into(),
|
||||
scale: 1.0,
|
||||
angle: 0.0,
|
||||
translation: Vec2::default(),
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
Some(Duration::from_secs(1)),
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
brightness: 0.6,
|
||||
},
|
||||
AnimeAction::Pause(Duration::from_secs(6)),
|
||||
AnimeAction::ImageAnimation {
|
||||
ActionLoader::Pause(Duration::from_secs(1)),
|
||||
ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.0,
|
||||
translation: Vec2::new(3.0, 2.0),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Cycles(2),
|
||||
time: AnimTime::Count(2),
|
||||
},
|
||||
],
|
||||
};
|
||||
println!("{}", serde_json::to_string_pretty(&x).unwrap());
|
||||
x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct UserConfig {
|
||||
/// Name of active anime config file in the user config directory
|
||||
pub active_anime: String,
|
||||
}
|
||||
|
||||
impl UserConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
active_anime: "anime-default".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(&mut self) -> Result<(), Error> {
|
||||
@@ -80,7 +191,7 @@ impl UserConfig {
|
||||
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||
file.write_all(json.as_bytes())?;
|
||||
} else if let Ok(data) = serde_json::from_str::<UserConfig>(&buf) {
|
||||
self.anime = data.anime;
|
||||
self.active_anime = data.active_anime;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -101,21 +212,14 @@ impl UserConfig {
|
||||
|
||||
path.push("rog-user.cfg");
|
||||
|
||||
let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(&path)?;
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&path)?;
|
||||
|
||||
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||
dbg!(&json);
|
||||
file.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_anime(&self) -> Result<Sequences, Error> {
|
||||
let mut seq = Sequences::new();
|
||||
|
||||
for (idx, action) in self.anime.iter().enumerate() {
|
||||
seq.insert(idx, action)?;
|
||||
}
|
||||
|
||||
Ok(seq)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//! * [`zbus::fdo::PropertiesProxy`]
|
||||
//!
|
||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use zbus::dbus_proxy;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "daemon"
|
||||
version = "3.4.0"
|
||||
version = "4.3.0"
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
@@ -18,28 +18,31 @@ name = "asusd"
|
||||
path = "src/daemon.rs"
|
||||
|
||||
[dependencies]
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
rog_anime = { path = "../rog-anime", features = ["dbus"] }
|
||||
rog_aura = { path = "../rog-aura", features = ["dbus"] }
|
||||
rog_supported = { path = "../rog-supported" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rusb = "^0.8"
|
||||
|
||||
async-trait = "^0.1"
|
||||
smol = "^1.2"
|
||||
|
||||
rusb = "^0.9"
|
||||
udev = "^0.6"
|
||||
|
||||
# cli and logging
|
||||
log = "^0.4"
|
||||
env_logger = "^0.8"
|
||||
env_logger = "^0.9"
|
||||
|
||||
zbus = "^1.9.1"
|
||||
zvariant = "^2.6"
|
||||
logind-zbus = "^0.7.1"
|
||||
zbus = "^2.2"
|
||||
zvariant = "^3.2"
|
||||
logind-zbus = { version = "^3.0" } #, default-features = false, features = ["non_blocking"] }
|
||||
|
||||
# serialisation
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
toml = "^0.5"
|
||||
toml = "^0.5.8"
|
||||
|
||||
# Device control
|
||||
sysfs-class = "^0.1.2" # used for backlight control and baord ID
|
||||
rog_fan_curve = { version = "0.1", features = ["serde"] }
|
||||
# cpu power management
|
||||
intel-pstate = "^0.2"
|
||||
@@ -1,109 +1,57 @@
|
||||
use log::{error, info, warn};
|
||||
use rog_types::{gfx_vendors::GfxVendors, profile::Profile};
|
||||
use log::{error, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use crate::config_old::*;
|
||||
use crate::VERSION;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
|
||||
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
pub struct Config {
|
||||
pub gfx_mode: GfxVendors,
|
||||
pub gfx_managed: bool,
|
||||
pub gfx_vfio_enable: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
/// Save charge limit for restoring on boot
|
||||
pub bat_charge_limit: u8,
|
||||
pub power_profiles: BTreeMap<String, Profile>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let mut pwr = BTreeMap::new();
|
||||
pwr.insert("normal".into(), Profile::new(0, 100, true, 0, None));
|
||||
pwr.insert("boost".into(), Profile::new(0, 100, true, 1, None));
|
||||
pwr.insert("silent".into(), Profile::new(0, 100, true, 2, None));
|
||||
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: true,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: "normal".into(),
|
||||
toggle_profiles: vec!["normal".into(), "boost".into(), "silent".into()],
|
||||
curr_fan_mode: 0,
|
||||
bat_charge_limit: 100,
|
||||
power_profiles: pwr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new() -> Self {
|
||||
Config {
|
||||
bat_charge_limit: 100,
|
||||
}
|
||||
}
|
||||
|
||||
/// `load` will attempt to read the config, and panic if the dir is missing
|
||||
pub fn load() -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
CONFIG_PATH
|
||||
)
|
||||
}); // okay to cause panic here
|
||||
.open(&PathBuf::from(CONFIG_PATH))
|
||||
.unwrap_or_else(|e| panic!("Error opening {}, {}", CONFIG_PATH, e)); // okay to cause panic here
|
||||
let mut buf = String::new();
|
||||
let config;
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
return Config::create_default(&mut file);
|
||||
config = Self::new();
|
||||
} else if let Ok(data) = serde_json::from_str(&buf) {
|
||||
config = data;
|
||||
} else {
|
||||
if let Ok(data) = serde_json::from_str(&buf) {
|
||||
return data;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV324>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV317>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV301>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV222>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV212>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
}
|
||||
warn!("Could not deserialise {}", CONFIG_PATH);
|
||||
panic!("Please remove {} then restart asusd", CONFIG_PATH);
|
||||
warn!(
|
||||
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
|
||||
CONFIG_PATH, CONFIG_PATH
|
||||
);
|
||||
let cfg_old = CONFIG_PATH.to_string() + "-old";
|
||||
std::fs::rename(CONFIG_PATH, cfg_old).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
CONFIG_PATH, err
|
||||
)
|
||||
});
|
||||
config = Self::new();
|
||||
}
|
||||
} else {
|
||||
config = Self::new()
|
||||
}
|
||||
Config::create_default(&mut file)
|
||||
}
|
||||
|
||||
fn create_default(file: &mut File) -> Self {
|
||||
let config = Config::default();
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
let json = serde_json::to_string_pretty(&config).unwrap();
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Could not write {}", CONFIG_PATH));
|
||||
config.write();
|
||||
config
|
||||
}
|
||||
|
||||
@@ -117,24 +65,12 @@ impl Config {
|
||||
if l == 0 {
|
||||
warn!("File is empty {}", CONFIG_PATH);
|
||||
} else {
|
||||
let x: Config = serde_json::from_str(&buf)
|
||||
*self = serde_json::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", CONFIG_PATH));
|
||||
*self = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_new() -> Result<Config, Box<dyn std::error::Error>> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
let x: Config = serde_json::from_str(&buf)?;
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
use crate::laptops::LaptopLedData;
|
||||
use log::{error, info, warn};
|
||||
use rog_types::aura_modes::{AuraEffect, AuraModeNum, AuraMultiZone, AuraZone, LedBrightness};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/aura.conf";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AuraConfigV320 {
|
||||
pub brightness: u32,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub multizone: Option<AuraMultiZone>,
|
||||
}
|
||||
|
||||
impl AuraConfigV320 {
|
||||
pub(crate) fn into_current(self) -> AuraConfig {
|
||||
AuraConfig {
|
||||
brightness: <LedBrightness>::from(self.brightness),
|
||||
current_mode: self.current_mode,
|
||||
builtins: self.builtins,
|
||||
multizone: self.multizone,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AuraConfig {
|
||||
pub brightness: LedBrightness,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub multizone: Option<AuraMultiZone>,
|
||||
}
|
||||
|
||||
impl Default for AuraConfig {
|
||||
fn default() -> Self {
|
||||
AuraConfig {
|
||||
brightness: LedBrightness::Med,
|
||||
current_mode: AuraModeNum::Static,
|
||||
builtins: BTreeMap::new(),
|
||||
multizone: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuraConfig {
|
||||
/// `load` will attempt to read the config, and panic if the dir is missing
|
||||
pub fn load(supported_led_modes: &LaptopLedData) -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&AURA_CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
AURA_CONFIG_PATH
|
||||
)
|
||||
}); // okay to cause panic here
|
||||
let mut buf = String::new();
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
return AuraConfig::create_default(&mut file, &supported_led_modes);
|
||||
} else {
|
||||
if let Ok(data) = serde_json::from_str(&buf) {
|
||||
return data;
|
||||
} else if let Ok(data) = serde_json::from_str::<AuraConfigV320>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated AuraConfig version");
|
||||
return config;
|
||||
}
|
||||
warn!("Could not deserialise {}", AURA_CONFIG_PATH);
|
||||
panic!("Please remove {} then restart asusd", AURA_CONFIG_PATH);
|
||||
}
|
||||
}
|
||||
AuraConfig::create_default(&mut file, &supported_led_modes)
|
||||
}
|
||||
|
||||
fn create_default(file: &mut File, support_data: &LaptopLedData) -> Self {
|
||||
// create a default config here
|
||||
let mut config = AuraConfig::default();
|
||||
|
||||
for n in &support_data.standard {
|
||||
config
|
||||
.builtins
|
||||
.insert(*n, AuraEffect::default_with_mode(*n));
|
||||
}
|
||||
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
let json = serde_json::to_string(&config).unwrap();
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Could not write {}", AURA_CONFIG_PATH));
|
||||
config
|
||||
}
|
||||
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&AURA_CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", AURA_CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("File is empty {}", AURA_CONFIG_PATH);
|
||||
} else {
|
||||
let x: AuraConfig = serde_json::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", AURA_CONFIG_PATH));
|
||||
*self = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(AURA_CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
|
||||
/// Multipurpose, will accecpt AuraEffect with zones and put in the correct store
|
||||
pub fn set_builtin(&mut self, effect: AuraEffect) {
|
||||
match effect.zone() {
|
||||
AuraZone::None => {
|
||||
self.builtins.insert(*effect.mode(), effect);
|
||||
}
|
||||
_ => {
|
||||
if let Some(multi) = self.multizone.as_mut() {
|
||||
multi.set(effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect; 4]> {
|
||||
if let Some(multi) = &self.multizone {
|
||||
if aura_type == AuraModeNum::Static {
|
||||
return Some(multi.static_());
|
||||
} else if aura_type == AuraModeNum::Breathe {
|
||||
return Some(multi.breathe());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
use rog_types::{aura_modes::AuraEffect, gfx_vendors::GfxVendors, profile::Profile};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
/// for parsing old v2.1.2 config
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct ConfigV212 {
|
||||
gfx_managed: bool,
|
||||
bat_charge_limit: u8,
|
||||
active_profile: String,
|
||||
toggle_profiles: Vec<String>,
|
||||
power_profiles: BTreeMap<String, Profile>,
|
||||
power_profile: u8,
|
||||
kbd_led_brightness: u8,
|
||||
kbd_backlight_mode: u8,
|
||||
kbd_backlight_modes: Vec<AuraEffect>,
|
||||
}
|
||||
|
||||
impl ConfigV212 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: self.gfx_managed,
|
||||
active_profile: self.active_profile,
|
||||
gfx_vfio_enable: false,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.power_profile,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: self.power_profiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// for parsing old v2.2.2 config
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct ConfigV222 {
|
||||
gfx_managed: bool,
|
||||
bat_charge_limit: u8,
|
||||
active_profile: String,
|
||||
toggle_profiles: Vec<String>,
|
||||
power_profiles: BTreeMap<String, Profile>,
|
||||
power_profile: u8,
|
||||
kbd_led_brightness: u8,
|
||||
kbd_backlight_mode: u8,
|
||||
kbd_backlight_modes: Vec<AuraEffect>,
|
||||
}
|
||||
|
||||
impl ConfigV222 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: self.gfx_managed,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.power_profile,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: self.power_profiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// for parsing old v3.0.1 config
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub(crate) struct ConfigV301 {
|
||||
pub gfx_managed: bool,
|
||||
pub gfx_nv_mode_is_dedicated: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
// TODO: remove power_profile
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub kbd_led_brightness: u8,
|
||||
pub kbd_backlight_mode: u8,
|
||||
pub kbd_backlight_modes: Vec<AuraEffect>,
|
||||
pub power_profiles: BTreeMap<String, Profile>,
|
||||
}
|
||||
|
||||
impl ConfigV301 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: self.gfx_managed,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.curr_fan_mode,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: self.power_profiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// for parsing old v3.1.7 config
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub(crate) struct ConfigV317 {
|
||||
pub gfx_mode: GfxVendors,
|
||||
pub gfx_managed: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub kbd_led_brightness: u8,
|
||||
pub kbd_backlight_mode: u8,
|
||||
#[serde(skip)]
|
||||
pub kbd_backlight_modes: Option<bool>,
|
||||
pub power_profiles: BTreeMap<String, Profile>,
|
||||
}
|
||||
|
||||
impl ConfigV317 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: self.gfx_managed,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.curr_fan_mode,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: self.power_profiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct ConfigV324 {
|
||||
pub gfx_mode: GfxVendors,
|
||||
pub gfx_managed: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub power_profiles: BTreeMap<String, Profile>,
|
||||
}
|
||||
|
||||
impl ConfigV324 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: self.gfx_managed,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.curr_fan_mode,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: self.power_profiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
use log::{error, info, warn};
|
||||
use rog_anime::{
|
||||
usb::{
|
||||
pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID,
|
||||
VENDOR_ID,
|
||||
},
|
||||
AnimeDataBuffer, AnimePacketType,
|
||||
};
|
||||
use rog_types::supported::AnimeSupportedFunctions;
|
||||
use rusb::{Device, DeviceHandle};
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
use crate::GetSupported;
|
||||
|
||||
impl GetSupported for CtrlAnimeDisplay {
|
||||
type A = AnimeSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
AnimeSupportedFunctions(CtrlAnimeDisplay::get_device(VENDOR_ID, PROD_ID).is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeDisplay {
|
||||
handle: DeviceHandle<rusb::GlobalContext>,
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for CtrlAnimeDisplay {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlAnimeDisplay: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnimeDisplay {
|
||||
/// Writes a data stream of length
|
||||
fn write(&self, input: AnimeDataBuffer) {
|
||||
self.write_data_buffer(input);
|
||||
}
|
||||
|
||||
fn set_on_off(&self, status: bool) {
|
||||
self.write_bytes(&pkt_for_set_on(status));
|
||||
}
|
||||
|
||||
fn set_boot_on_off(&self, on: bool) {
|
||||
self.write_bytes(&pkt_for_set_boot(on));
|
||||
self.write_bytes(&pkt_for_apply());
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlAnimeDisplay {
|
||||
#[inline]
|
||||
pub fn new() -> Result<CtrlAnimeDisplay, Box<dyn Error>> {
|
||||
// We don't expect this ID to ever change
|
||||
let device = CtrlAnimeDisplay::get_device(0x0b05, 0x193b)?;
|
||||
|
||||
let mut device = device.open()?;
|
||||
device.reset()?;
|
||||
|
||||
device.set_auto_detach_kernel_driver(true).map_err(|err| {
|
||||
error!("Auto-detach kernel driver failed: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
device.claim_interface(0).map_err(|err| {
|
||||
error!("Could not claim device interface: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
info!("Device has an AniMe Matrix display");
|
||||
let ctrl = CtrlAnimeDisplay { handle: device };
|
||||
ctrl.do_initialization();
|
||||
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn get_device(vendor: u16, product: u16) -> Result<Device<rusb::GlobalContext>, rusb::Error> {
|
||||
for device in rusb::devices()?.iter() {
|
||||
let device_desc = device.device_descriptor()?;
|
||||
if device_desc.vendor_id() == vendor && device_desc.product_id() == product {
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
Err(rusb::Error::NoDevice)
|
||||
}
|
||||
|
||||
fn write_bytes(&self, message: &[u8]) {
|
||||
match self.handle.write_control(
|
||||
0x21, // request_type
|
||||
0x09, // request
|
||||
0x35e, // value
|
||||
0x00, // index
|
||||
message,
|
||||
Duration::from_millis(200),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
rusb::Error::Timeout => {}
|
||||
_ => error!("Failed to write to led interrupt: {}", err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn write_data_buffer(&self, buffer: AnimeDataBuffer) {
|
||||
let data = AnimePacketType::from(buffer);
|
||||
for row in data.iter() {
|
||||
self.write_bytes(row);
|
||||
}
|
||||
self.write_bytes(&pkt_for_flush());
|
||||
}
|
||||
|
||||
fn do_initialization(&self) {
|
||||
let pkts = pkts_for_init();
|
||||
self.write_bytes(&pkts[0]);
|
||||
self.write_bytes(&pkts[1]);
|
||||
}
|
||||
}
|
||||
272
daemon/src/ctrl_anime/config.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use crate::VERSION;
|
||||
use log::{error, info, warn};
|
||||
use rog_anime::{error::AnimeError, ActionData, ActionLoader, AnimTime, Vec2};
|
||||
use rog_anime::{AnimeType, Fade};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
pub static ANIME_CONFIG_PATH: &str = "/etc/asusd/anime.conf";
|
||||
pub static ANIME_CACHE_PATH: &str = "/etc/asusd/anime-cache.conf";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AnimeConfigV341 {
|
||||
pub system: Option<ActionLoader>,
|
||||
pub boot: Option<ActionLoader>,
|
||||
pub suspend: Option<ActionLoader>,
|
||||
pub shutdown: Option<ActionLoader>,
|
||||
}
|
||||
|
||||
impl AnimeConfigV341 {
|
||||
pub(crate) fn into_current(self) -> AnimeConfig {
|
||||
AnimeConfig {
|
||||
system: if let Some(ani) = self.system {
|
||||
vec![ani]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
boot: if let Some(ani) = self.boot {
|
||||
vec![ani]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
wake: if let Some(ani) = self.suspend {
|
||||
vec![ani]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
shutdown: if let Some(ani) = self.shutdown {
|
||||
vec![ani]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
brightness: 1.0,
|
||||
awake_enabled: true,
|
||||
boot_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AnimeConfigV352 {
|
||||
pub system: Vec<ActionLoader>,
|
||||
pub boot: Vec<ActionLoader>,
|
||||
pub wake: Vec<ActionLoader>,
|
||||
pub shutdown: Vec<ActionLoader>,
|
||||
pub brightness: f32,
|
||||
}
|
||||
|
||||
impl AnimeConfigV352 {
|
||||
pub(crate) fn into_current(self) -> AnimeConfig {
|
||||
AnimeConfig {
|
||||
system: self.system,
|
||||
boot: self.boot,
|
||||
wake: self.wake,
|
||||
shutdown: self.shutdown,
|
||||
brightness: 1.0,
|
||||
awake_enabled: true,
|
||||
boot_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
pub struct AnimeConfigCached {
|
||||
pub system: Vec<ActionData>,
|
||||
pub boot: Vec<ActionData>,
|
||||
pub wake: Vec<ActionData>,
|
||||
pub shutdown: Vec<ActionData>,
|
||||
}
|
||||
|
||||
impl AnimeConfigCached {
|
||||
pub fn init_from_config(
|
||||
&mut self,
|
||||
config: &AnimeConfig,
|
||||
anime_type: AnimeType,
|
||||
) -> Result<(), AnimeError> {
|
||||
let mut sys = Vec::with_capacity(config.system.len());
|
||||
for ani in config.system.iter() {
|
||||
sys.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.system = sys;
|
||||
|
||||
let mut boot = Vec::with_capacity(config.boot.len());
|
||||
for ani in config.boot.iter() {
|
||||
boot.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.boot = boot;
|
||||
|
||||
let mut wake = Vec::with_capacity(config.wake.len());
|
||||
for ani in config.wake.iter() {
|
||||
wake.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.wake = wake;
|
||||
|
||||
let mut shutdown = Vec::with_capacity(config.shutdown.len());
|
||||
for ani in config.shutdown.iter() {
|
||||
shutdown.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.shutdown = shutdown;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Config for base system actions for the anime display
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AnimeConfig {
|
||||
pub system: Vec<ActionLoader>,
|
||||
pub boot: Vec<ActionLoader>,
|
||||
pub wake: Vec<ActionLoader>,
|
||||
pub shutdown: Vec<ActionLoader>,
|
||||
pub brightness: f32,
|
||||
pub awake_enabled: bool,
|
||||
pub boot_anim_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for AnimeConfig {
|
||||
fn default() -> Self {
|
||||
AnimeConfig {
|
||||
system: Vec::new(),
|
||||
boot: Vec::new(),
|
||||
wake: Vec::new(),
|
||||
shutdown: Vec::new(),
|
||||
brightness: 1.0,
|
||||
awake_enabled: true,
|
||||
boot_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimeConfig {
|
||||
/// `load` will attempt to read the config, and panic if the dir is missing
|
||||
pub fn load() -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&ANIME_CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
ANIME_CONFIG_PATH
|
||||
)
|
||||
}); // okay to cause panic here
|
||||
let mut buf = String::new();
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
return AnimeConfig::create_default(&mut file);
|
||||
} else {
|
||||
if let Ok(mut data) = serde_json::from_str(&buf) {
|
||||
Self::clamp_config_brightness(&mut data);
|
||||
return data;
|
||||
} else if let Ok(data) = serde_json::from_str::<AnimeConfigV341>(&buf) {
|
||||
let mut config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
Self::clamp_config_brightness(&mut config);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<AnimeConfigV352>(&buf) {
|
||||
let mut config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
Self::clamp_config_brightness(&mut config);
|
||||
return config;
|
||||
}
|
||||
warn!(
|
||||
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
|
||||
ANIME_CONFIG_PATH, ANIME_CONFIG_PATH
|
||||
);
|
||||
let cfg_old = ANIME_CONFIG_PATH.to_string() + "-old";
|
||||
std::fs::rename(ANIME_CONFIG_PATH, cfg_old).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
ANIME_CONFIG_PATH, err
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
AnimeConfig::create_default(&mut file)
|
||||
}
|
||||
|
||||
fn clamp_config_brightness(mut config: &mut AnimeConfig) {
|
||||
if config.brightness < 0.0 || config.brightness > 1.0 {
|
||||
warn!(
|
||||
"Clamped brightness to [0.0 ; 1.0], was {}",
|
||||
config.brightness
|
||||
);
|
||||
config.brightness = f32::max(0.0, f32::min(1.0, config.brightness));
|
||||
}
|
||||
}
|
||||
|
||||
fn create_default(file: &mut File) -> Self {
|
||||
// create a default config here
|
||||
let config = AnimeConfig {
|
||||
system: vec![],
|
||||
boot: vec![ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
Some(Duration::from_secs(2)),
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
}],
|
||||
wake: vec![ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
Some(Duration::from_secs(2)),
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
}],
|
||||
shutdown: vec![ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.0,
|
||||
translation: Vec2::new(3.0, 2.0),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Infinite,
|
||||
}],
|
||||
brightness: 1.0,
|
||||
awake_enabled: true,
|
||||
boot_anim_enabled: true,
|
||||
};
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
let json = serde_json::to_string_pretty(&config).unwrap();
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Could not write {}", ANIME_CONFIG_PATH));
|
||||
config
|
||||
}
|
||||
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&ANIME_CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", ANIME_CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("File is empty {}", ANIME_CONFIG_PATH);
|
||||
} else {
|
||||
let x: AnimeConfig = serde_json::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", ANIME_CONFIG_PATH));
|
||||
*self = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(ANIME_CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
}
|
||||
393
daemon/src/ctrl_anime/mod.rs
Normal file
@@ -0,0 +1,393 @@
|
||||
pub mod config;
|
||||
pub mod zbus;
|
||||
|
||||
use ::zbus::Connection;
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use rog_anime::{
|
||||
error::AnimeError,
|
||||
usb::{
|
||||
find_node, get_anime_type, pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on,
|
||||
pkts_for_init, PROD_ID, VENDOR_ID,
|
||||
},
|
||||
ActionData, AnimeDataBuffer, AnimePacketType, AnimeType,
|
||||
};
|
||||
use rog_supported::AnimeSupportedFunctions;
|
||||
use rusb::{Device, DeviceHandle};
|
||||
use smol::{stream::StreamExt, Executor};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
convert::TryFrom,
|
||||
error::Error,
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
thread::sleep,
|
||||
};
|
||||
use std::{
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{error::RogError, GetSupported};
|
||||
|
||||
use self::config::{AnimeConfig, AnimeConfigCached};
|
||||
|
||||
impl GetSupported for CtrlAnime {
|
||||
type A = AnimeSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
AnimeSupportedFunctions(CtrlAnime::get_device(VENDOR_ID, PROD_ID).is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnime {
|
||||
_node: String,
|
||||
anime_type: AnimeType,
|
||||
handle: RefCell<DeviceHandle<rusb::GlobalContext>>,
|
||||
cache: AnimeConfigCached,
|
||||
config: AnimeConfig,
|
||||
// set to force thread to exit
|
||||
thread_exit: Arc<AtomicBool>,
|
||||
// Set to false when the thread exits
|
||||
thread_running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CtrlAnime {
|
||||
#[inline]
|
||||
pub fn new(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
|
||||
let node = find_node("193b")?;
|
||||
let anime_type = get_anime_type()?;
|
||||
let device = Self::get_dev_handle()?;
|
||||
|
||||
info!("Device has an AniMe Matrix display");
|
||||
let mut cache = AnimeConfigCached::default();
|
||||
cache.init_from_config(&config, anime_type)?;
|
||||
|
||||
let ctrl = CtrlAnime {
|
||||
_node: node,
|
||||
anime_type,
|
||||
handle: RefCell::new(device),
|
||||
cache,
|
||||
config,
|
||||
thread_exit: Arc::new(AtomicBool::new(false)),
|
||||
thread_running: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
ctrl.do_initialization();
|
||||
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn get_dev_handle() -> Result<DeviceHandle<rusb::GlobalContext>, Box<dyn Error>> {
|
||||
// We don't expect this ID to ever change
|
||||
let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
|
||||
|
||||
let mut device = device.open()?;
|
||||
device.reset()?;
|
||||
|
||||
device.set_auto_detach_kernel_driver(true).map_err(|err| {
|
||||
error!("Auto-detach kernel driver failed: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
device.claim_interface(0).map_err(|err| {
|
||||
error!("Could not claim device interface: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
fn get_device(vendor: u16, product: u16) -> Result<Device<rusb::GlobalContext>, rusb::Error> {
|
||||
for device in rusb::devices()?.iter() {
|
||||
let device_desc = device.device_descriptor()?;
|
||||
if device_desc.vendor_id() == vendor && device_desc.product_id() == product {
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
Err(rusb::Error::NoDevice)
|
||||
}
|
||||
|
||||
/// Start an action thread. This is classed as a singleton and there should be only
|
||||
/// one running - so the thread uses atomics to signal run/exit.
|
||||
///
|
||||
/// Because this also writes to the usb device, other write tries (display only) *must*
|
||||
/// get the mutex lock and set the thread_exit atomic.
|
||||
fn run_thread(inner: Arc<Mutex<CtrlAnime>>, actions: Vec<ActionData>, mut once: bool) {
|
||||
if actions.is_empty() {
|
||||
warn!("AniMe system actions was empty");
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop rules:
|
||||
// - Lock the mutex **only when required**. That is, the lock must be held for the shortest duration possible.
|
||||
// - An AtomicBool used for thread exit should be checked in every loop, including nested
|
||||
|
||||
// The only reason for this outer thread is to prevent blocking while waiting for the
|
||||
// next spawned thread to exit
|
||||
std::thread::Builder::new()
|
||||
.name("AniMe system thread start".into())
|
||||
.spawn(move || {
|
||||
info!("AniMe new system thread started");
|
||||
// Getting copies of these Atomics is done *in* the thread to ensure
|
||||
// we don't block other threads/main
|
||||
let thread_exit;
|
||||
let thread_running;
|
||||
let anime_type;
|
||||
loop {
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
thread_exit = lock.thread_exit.clone();
|
||||
thread_running = lock.thread_running.clone();
|
||||
anime_type = lock.anime_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// First two loops are to ensure we *do* aquire a lock on the mutex
|
||||
// The reason the loop is required is because the USB writes can block
|
||||
// for up to 10ms. We can't fail to get the atomics.
|
||||
while thread_running.load(Ordering::SeqCst) {
|
||||
// Make any running loop exit first
|
||||
thread_exit.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
info!("AniMe no previous system thread running (now)");
|
||||
thread_exit.store(false, Ordering::SeqCst);
|
||||
|
||||
'main: loop {
|
||||
thread_running.store(true, Ordering::SeqCst);
|
||||
for action in actions.iter() {
|
||||
if thread_exit.load(Ordering::SeqCst) {
|
||||
break 'main;
|
||||
}
|
||||
match action {
|
||||
ActionData::Animation(frames) => {
|
||||
if let Err(err) = rog_anime::run_animation(frames, &|frame| {
|
||||
if thread_exit.load(Ordering::Acquire) {
|
||||
info!("rog-anime: frame-loop was asked to exit");
|
||||
return Ok(true); // Do safe exit
|
||||
}
|
||||
inner
|
||||
.try_lock()
|
||||
.map(|lock| {
|
||||
lock.write_data_buffer(frame)
|
||||
.map_err(|err| {
|
||||
warn!(
|
||||
"rog_anime::run_animation:callback {}",
|
||||
err
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
false // Don't exit yet
|
||||
})
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
AnimeError::NoFrames
|
||||
})
|
||||
}) {
|
||||
warn!("rog_anime::run_animation:Animation {}", err);
|
||||
break 'main;
|
||||
};
|
||||
}
|
||||
ActionData::Image(image) => {
|
||||
once = false;
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
lock.write_data_buffer(image.as_ref().clone())
|
||||
.map_err(|e| error!("{}", e))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
ActionData::Pause(duration) => sleep(*duration),
|
||||
ActionData::AudioEq => {}
|
||||
ActionData::SystemInfo => {}
|
||||
ActionData::TimeDate => {}
|
||||
ActionData::Matrix => {}
|
||||
}
|
||||
}
|
||||
if thread_exit.load(Ordering::SeqCst) {
|
||||
break 'main;
|
||||
}
|
||||
if once || actions.is_empty() {
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
// Clear the display on exit
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
if let Ok(data) =
|
||||
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()])
|
||||
.map_err(|e| error!("{}", e))
|
||||
{
|
||||
lock.write_data_buffer(data)
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
// Loop ended, set the atmonics
|
||||
thread_running.store(false, Ordering::SeqCst);
|
||||
info!("AniMe system thread exited");
|
||||
})
|
||||
.map(|err| info!("AniMe system thread: {:?}", err))
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn write_bytes(&self, message: &[u8]) {
|
||||
// if let Ok(mut file) = OpenOptions::new().write(true).open(&self.node) {
|
||||
// println!("write: {:02x?}", &message);
|
||||
// return file
|
||||
// .write_all(message).unwrap();
|
||||
// }
|
||||
let mut error = false;
|
||||
|
||||
match self.handle.borrow().write_control(
|
||||
0x21, // request_type
|
||||
0x09, // request
|
||||
0x35e, // value
|
||||
0x00, // index
|
||||
message,
|
||||
Duration::from_millis(200),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
rusb::Error::Timeout => {}
|
||||
_ => {
|
||||
error = true;
|
||||
error!("Failed to write to led interrupt: {}", err);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if error {
|
||||
warn!("Will attempt to get AniMe device handle again");
|
||||
match Self::get_dev_handle() {
|
||||
Ok(dev) => {
|
||||
self.handle.replace(dev);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to get AniMe device: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write only a data packet. This will modify the leds brightness using the
|
||||
/// global brightness set in config.
|
||||
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> {
|
||||
for led in buffer.data_mut()[7..].iter_mut() {
|
||||
let mut bright = *led as f32 * self.config.brightness;
|
||||
if bright > 254.0 {
|
||||
bright = 254.0;
|
||||
}
|
||||
*led = bright as u8;
|
||||
}
|
||||
let data = AnimePacketType::try_from(buffer)?;
|
||||
for row in data.iter() {
|
||||
self.write_bytes(row);
|
||||
}
|
||||
self.write_bytes(&pkt_for_flush());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_initialization(&self) {
|
||||
let pkts = pkts_for_init();
|
||||
self.write_bytes(&pkts[0]);
|
||||
self.write_bytes(&pkts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeTask {
|
||||
inner: Arc<Mutex<CtrlAnime>>,
|
||||
}
|
||||
|
||||
impl CtrlAnimeTask {
|
||||
pub async fn new(inner: Arc<Mutex<CtrlAnime>>) -> CtrlAnimeTask {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::CtrlTask for CtrlAnimeTask {
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("CtrlAnimeTask could not create dbus connection");
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlAnimeTask could not create ManagerProxy");
|
||||
|
||||
let run_action =
|
||||
|start: bool, lock: MutexGuard<CtrlAnime>, inner: Arc<Mutex<CtrlAnime>>| {
|
||||
if start {
|
||||
info!("CtrlAnimeTask running sleep animation");
|
||||
CtrlAnime::run_thread(inner.clone(), lock.cache.shutdown.clone(), true);
|
||||
} else {
|
||||
info!("CtrlAnimeTask running wake animation");
|
||||
CtrlAnime::run_thread(inner.clone(), lock.cache.wake.clone(), true);
|
||||
}
|
||||
};
|
||||
|
||||
let inner = self.inner.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_sleep().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
// Loop is required to try an attempt to get the mutex *without* blocking
|
||||
// other threads - it is possible to end up with deadlocks otherwise.
|
||||
loop {
|
||||
if let Ok(lock) = inner.clone().try_lock() {
|
||||
run_action(args.start, lock, inner.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlAnimeTask could not create ManagerProxy");
|
||||
|
||||
let inner = self.inner.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_shutdown().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
loop {
|
||||
if let Ok(lock) = inner.clone().try_lock() {
|
||||
run_action(args.start, lock, inner.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeReloader(pub Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl crate::Reloadable for CtrlAnimeReloader {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_on(lock.config.awake_enabled));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
lock.write_bytes(&pkt_for_set_boot(lock.config.boot_anim_enabled));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
|
||||
let action = lock.cache.boot.clone();
|
||||
CtrlAnime::run_thread(self.0.clone(), action, true);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
145
daemon/src/ctrl_anime/zbus.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::warn;
|
||||
use rog_anime::{
|
||||
usb::{pkt_for_apply, pkt_for_set_boot, pkt_for_set_on},
|
||||
AnimeDataBuffer, AnimePowerStates,
|
||||
};
|
||||
use zbus::{dbus_interface, Connection, SignalContext};
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use super::CtrlAnime;
|
||||
|
||||
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
/// The struct with the main dbus methods requires this trait
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlAnimeZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Anime", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
// None of these calls can be guarnateed to succeed unless we loop until okay
|
||||
// If the try_lock *does* succeed then any other thread trying to lock will not grab it
|
||||
// until we finish.
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnimeZbus {
|
||||
/// Writes a data stream of length. Will force system thread to exit until it is restarted
|
||||
fn write(&self, input: AnimeDataBuffer) -> zbus::fdo::Result<()> {
|
||||
'outer: loop {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
lock.write_data_buffer(input).map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
err
|
||||
})?;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the global AniMe brightness
|
||||
fn set_brightness(&self, bright: f32) {
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
let mut bright = bright;
|
||||
if bright < 0.0 {
|
||||
bright = 0.0
|
||||
} else if bright > 1.0 {
|
||||
bright = 1.0;
|
||||
}
|
||||
lock.config.brightness = bright;
|
||||
lock.config.write();
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the AniMe is displaying images/data
|
||||
async fn set_on_off(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>, status: bool) {
|
||||
let states;
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_on(status));
|
||||
lock.config.awake_enabled = status;
|
||||
lock.config.write();
|
||||
|
||||
states = Some(AnimePowerStates {
|
||||
brightness: lock.config.brightness.floor() as u8,
|
||||
enabled: lock.config.awake_enabled,
|
||||
boot_anim_enabled: lock.config.boot_anim_enabled,
|
||||
});
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
if let Some(state) = states {
|
||||
Self::notify_power_states(&ctxt, state).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the AniMe will show boot, suspend, or off animations
|
||||
async fn set_boot_on_off(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>, on: bool) {
|
||||
let states;
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_boot(on));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
lock.config.boot_anim_enabled = on;
|
||||
lock.config.write();
|
||||
|
||||
states = Some(AnimePowerStates {
|
||||
brightness: lock.config.brightness.floor() as u8,
|
||||
enabled: lock.config.awake_enabled,
|
||||
boot_anim_enabled: lock.config.boot_anim_enabled,
|
||||
});
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
if let Some(state) = states {
|
||||
Self::notify_power_states(&ctxt, state).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// The main loop is the base system set action if the user isn't running
|
||||
/// the user daemon
|
||||
fn run_main_loop(&self, start: bool) {
|
||||
if start {
|
||||
'outer: loop {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get status of if the AniMe LEDs are on/displaying while system is awake
|
||||
#[dbus_interface(property)]
|
||||
fn awake_enabled(&self) -> bool {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.config.awake_enabled;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Get the status of if factory system-status animations are enabled
|
||||
#[dbus_interface(property)]
|
||||
fn boot_enabled(&self) -> bool {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.config.boot_anim_enabled;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Notify listeners of the status of AniMe LED power and factory system-status animations
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_power_states(
|
||||
ctxt: &SignalContext<'_>,
|
||||
data: AnimePowerStates,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
352
daemon/src/ctrl_aura/config.rs
Normal file
@@ -0,0 +1,352 @@
|
||||
use crate::laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES};
|
||||
use log::{error, warn};
|
||||
use rog_aura::usb::{AuraDev1866, AuraDev19b6, AuraPowerDev};
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Direction, LedBrightness, Speed, GRADIENT};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use super::controller::CtrlKbdLed;
|
||||
|
||||
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/aura.conf";
|
||||
|
||||
/// Enable/disable LED control in various states such as
|
||||
/// when the device is awake, suspended, shutting down or
|
||||
/// booting.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AuraPowerConfig {
|
||||
AuraDev1866(HashSet<AuraDev1866>),
|
||||
AuraDev19b6(HashSet<AuraDev19b6>),
|
||||
}
|
||||
|
||||
impl AuraPowerConfig {
|
||||
pub fn to_bytes(control: &Self) -> [u8; 3] {
|
||||
match control {
|
||||
AuraPowerConfig::AuraDev1866(c) => {
|
||||
let c: Vec<AuraDev1866> = c.iter().map(|v| *v).collect();
|
||||
AuraDev1866::to_bytes(&c)
|
||||
}
|
||||
AuraPowerConfig::AuraDev19b6(c) => {
|
||||
let c: Vec<AuraDev19b6> = c.iter().map(|v| *v).collect();
|
||||
AuraDev19b6::to_bytes(&c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_0x1866(&mut self, power: AuraDev1866, on: bool) {
|
||||
if let Self::AuraDev1866(p) = self {
|
||||
if on {
|
||||
p.insert(power);
|
||||
} else {
|
||||
p.remove(&power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_0x19b6(&mut self, power: AuraDev19b6, on: bool) {
|
||||
if let Self::AuraDev19b6(p) = self {
|
||||
if on {
|
||||
p.insert(power);
|
||||
} else {
|
||||
p.remove(&power);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuraPowerConfig> for AuraPowerDev {
|
||||
fn from(config: &AuraPowerConfig) -> Self {
|
||||
match config {
|
||||
AuraPowerConfig::AuraDev1866(d) => AuraPowerDev {
|
||||
x1866: d.iter().map(|o| *o).collect(),
|
||||
x19b6: vec![],
|
||||
},
|
||||
AuraPowerConfig::AuraDev19b6(d) => AuraPowerDev {
|
||||
x1866: vec![],
|
||||
x19b6: d.iter().map(|o| *o).collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct AuraConfig {
|
||||
pub brightness: LedBrightness,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub multizone: Option<BTreeMap<AuraModeNum, Vec<AuraEffect>>>,
|
||||
pub multizone_on: bool,
|
||||
pub enabled: AuraPowerConfig,
|
||||
}
|
||||
|
||||
impl Default for AuraConfig {
|
||||
fn default() -> Self {
|
||||
let mut prod_id = String::new();
|
||||
for prod in ASUS_KEYBOARD_DEVICES.iter() {
|
||||
if let Ok(_) = CtrlKbdLed::find_led_node(prod) {
|
||||
prod_id = prod.to_string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let enabled = if prod_id == "19b6" {
|
||||
AuraPowerConfig::AuraDev19b6(HashSet::from([
|
||||
AuraDev19b6::BootLogo,
|
||||
AuraDev19b6::BootKeyb,
|
||||
AuraDev19b6::SleepLogo,
|
||||
AuraDev19b6::SleepKeyb,
|
||||
AuraDev19b6::AwakeLogo,
|
||||
AuraDev19b6::AwakeKeyb,
|
||||
AuraDev19b6::ShutdownLogo,
|
||||
AuraDev19b6::ShutdownKeyb,
|
||||
AuraDev19b6::AwakeBar,
|
||||
AuraDev19b6::BootBar,
|
||||
AuraDev19b6::SleepBar,
|
||||
AuraDev19b6::ShutdownBar,
|
||||
]))
|
||||
} else {
|
||||
AuraPowerConfig::AuraDev1866(HashSet::from([
|
||||
AuraDev1866::Awake,
|
||||
AuraDev1866::Boot,
|
||||
AuraDev1866::Sleep,
|
||||
AuraDev1866::Keyboard,
|
||||
AuraDev1866::Lightbar,
|
||||
]))
|
||||
};
|
||||
|
||||
AuraConfig {
|
||||
brightness: LedBrightness::Med,
|
||||
current_mode: AuraModeNum::Static,
|
||||
builtins: BTreeMap::new(),
|
||||
multizone: None,
|
||||
multizone_on: false,
|
||||
enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuraConfig {
|
||||
/// `load` will attempt to read the config, and panic if the dir is missing
|
||||
pub fn load(supported_led_modes: &LaptopLedData) -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&AURA_CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
AURA_CONFIG_PATH
|
||||
)
|
||||
}); // okay to cause panic here
|
||||
let mut buf = String::new();
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
return AuraConfig::create_default(&mut file, supported_led_modes);
|
||||
} else {
|
||||
if let Ok(data) = serde_json::from_str(&buf) {
|
||||
return data;
|
||||
}
|
||||
warn!(
|
||||
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
|
||||
AURA_CONFIG_PATH, AURA_CONFIG_PATH
|
||||
);
|
||||
let cfg_old = AURA_CONFIG_PATH.to_string() + "-old";
|
||||
std::fs::rename(AURA_CONFIG_PATH, cfg_old).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
AURA_CONFIG_PATH, err
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
AuraConfig::create_default(&mut file, supported_led_modes)
|
||||
}
|
||||
|
||||
fn create_default(file: &mut File, support_data: &LaptopLedData) -> Self {
|
||||
// create a default config here
|
||||
let mut config = AuraConfig::default();
|
||||
|
||||
for n in &support_data.standard {
|
||||
config
|
||||
.builtins
|
||||
.insert(*n, AuraEffect::default_with_mode(*n));
|
||||
|
||||
if !support_data.multizone.is_empty() {
|
||||
let mut default = vec![];
|
||||
for (i, tmp) in support_data.multizone.iter().enumerate() {
|
||||
default.push(AuraEffect {
|
||||
mode: *n,
|
||||
zone: *tmp,
|
||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Left,
|
||||
})
|
||||
}
|
||||
if let Some(m) = config.multizone.as_mut() {
|
||||
m.insert(*n, default);
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*n, default);
|
||||
config.multizone = Some(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
let json = serde_json::to_string(&config).unwrap();
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Could not write {}", AURA_CONFIG_PATH));
|
||||
config
|
||||
}
|
||||
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&AURA_CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", AURA_CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("File is empty {}", AURA_CONFIG_PATH);
|
||||
} else {
|
||||
let x: AuraConfig = serde_json::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", AURA_CONFIG_PATH));
|
||||
*self = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(AURA_CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
|
||||
/// Set the mode data, current mode, and if multizone enabled.
|
||||
///
|
||||
/// Multipurpose, will accept AuraEffect with zones and put in the correct store.
|
||||
pub fn set_builtin(&mut self, effect: AuraEffect) {
|
||||
self.current_mode = effect.mode;
|
||||
match effect.zone() {
|
||||
AuraZone::None => {
|
||||
self.builtins.insert(*effect.mode(), effect);
|
||||
self.multizone_on = false;
|
||||
}
|
||||
_ => {
|
||||
if let Some(multi) = self.multizone.as_mut() {
|
||||
if let Some(fx) = multi.get_mut(effect.mode()) {
|
||||
for fx in fx.iter_mut() {
|
||||
if fx.zone == effect.zone {
|
||||
*fx = effect;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fx.push(effect);
|
||||
} else {
|
||||
multi.insert(*effect.mode(), vec![effect]);
|
||||
}
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*effect.mode(), vec![effect]);
|
||||
self.multizone = Some(tmp);
|
||||
}
|
||||
self.multizone_on = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect]> {
|
||||
if let Some(multi) = &self.multizone {
|
||||
return multi.get(&aura_type).map(|v| v.as_slice());
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AuraConfig;
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
|
||||
|
||||
#[test]
|
||||
fn set_multizone_4key_config() {
|
||||
let mut config = AuraConfig::default();
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.colour1 = Colour(0xff, 0x00, 0xff);
|
||||
effect.zone = AuraZone::Key1;
|
||||
config.set_builtin(effect);
|
||||
|
||||
assert!(config.multizone.is_some());
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.colour1 = Colour(0x00, 0xff, 0xff);
|
||||
effect.zone = AuraZone::Key2;
|
||||
config.set_builtin(effect);
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.colour1 = Colour(0xff, 0xff, 0x00);
|
||||
effect.zone = AuraZone::Key3;
|
||||
config.set_builtin(effect);
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.colour1 = Colour(0x00, 0xff, 0x00);
|
||||
effect.zone = AuraZone::Key4;
|
||||
let effect_clone = effect.clone();
|
||||
config.set_builtin(effect);
|
||||
// This should replace existing
|
||||
config.set_builtin(effect_clone);
|
||||
|
||||
let res = config.multizone.unwrap();
|
||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(sta.len(), 4);
|
||||
assert_eq!(sta[0].colour1, Colour(0xff, 0x00, 0xff));
|
||||
assert_eq!(sta[1].colour1, Colour(0x00, 0xff, 0xff));
|
||||
assert_eq!(sta[2].colour1, Colour(0xff, 0xff, 0x00));
|
||||
assert_eq!(sta[3].colour1, Colour(0x00, 0xff, 0x00));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_multizone_multimode_config() {
|
||||
let mut config = AuraConfig::default();
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.zone = AuraZone::Key1;
|
||||
config.set_builtin(effect);
|
||||
|
||||
assert!(config.multizone.is_some());
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.zone = AuraZone::Key2;
|
||||
effect.mode = AuraModeNum::Breathe;
|
||||
config.set_builtin(effect);
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.zone = AuraZone::Key3;
|
||||
effect.mode = AuraModeNum::Comet;
|
||||
config.set_builtin(effect);
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.zone = AuraZone::Key4;
|
||||
effect.mode = AuraModeNum::Pulse;
|
||||
config.set_builtin(effect);
|
||||
|
||||
let res = config.multizone.unwrap();
|
||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Breathe).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Comet).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Pulse).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
}
|
||||
}
|
||||
651
daemon/src/ctrl_aura/controller.rs
Normal file
@@ -0,0 +1,651 @@
|
||||
// Only these two packets must be 17 bytes
|
||||
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
|
||||
|
||||
use crate::{
|
||||
error::RogError,
|
||||
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
|
||||
CtrlTask,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use rog_aura::{
|
||||
usb::{LED_APPLY, LED_SET},
|
||||
AuraEffect, LedBrightness, LED_MSG_LEN,
|
||||
};
|
||||
use rog_aura::{AuraZone, Direction, Speed, GRADIENT};
|
||||
use rog_supported::LedSupportedFunctions;
|
||||
use smol::{stream::StreamExt, Executor};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::{fs::OpenOptions, sync::MutexGuard};
|
||||
use zbus::Connection;
|
||||
|
||||
use crate::GetSupported;
|
||||
|
||||
use super::config::{AuraConfig, AuraPowerConfig};
|
||||
|
||||
impl GetSupported for CtrlKbdLed {
|
||||
type A = LedSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
// let mode = <&str>::from(&<AuraModes>::from(*mode));
|
||||
let laptop = LaptopLedData::get_data();
|
||||
let stock_led_modes = laptop.standard;
|
||||
let multizone_led_mode = laptop.multizone;
|
||||
let per_key_led_mode = laptop.per_key;
|
||||
|
||||
let mut prod_id = String::new();
|
||||
for prod in ASUS_KEYBOARD_DEVICES.iter() {
|
||||
if let Ok(_) = Self::find_led_node(prod) {
|
||||
prod_id = prod.to_string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LedSupportedFunctions {
|
||||
prod_id,
|
||||
brightness_set: CtrlKbdLed::get_kbd_bright_path().is_some(),
|
||||
stock_led_modes,
|
||||
multizone_led_mode,
|
||||
per_key_led_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLed {
|
||||
// TODO: config stores the keyboard type as an AuraPower, use or update this
|
||||
pub led_prod: Option<String>,
|
||||
pub led_node: Option<String>,
|
||||
pub bright_node: String,
|
||||
pub supported_modes: LaptopLedData,
|
||||
pub flip_effect_write: bool,
|
||||
pub config: AuraConfig,
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedTask {
|
||||
inner: Arc<Mutex<CtrlKbdLed>>,
|
||||
}
|
||||
|
||||
impl CtrlKbdLedTask {
|
||||
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&lock.bright_node)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&lock.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&lock.bright_node).into(), err),
|
||||
})?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if lock.config.brightness != num.into() {
|
||||
lock.config.read();
|
||||
lock.config.brightness = num.into();
|
||||
lock.config.write();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Err(RogError::ParseLed)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlKbdLedTask {
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("CtrlKbdLedTask could not create dbus connection");
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlKbdLedTask could not create ManagerProxy");
|
||||
|
||||
let load_save = |start: bool, mut lock: MutexGuard<CtrlKbdLed>| {
|
||||
// If waking up
|
||||
if !start {
|
||||
info!("CtrlKbdLedTask reloading brightness and modes");
|
||||
lock.set_brightness(lock.config.brightness)
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
lock.write_current_config_mode()
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
} else if start {
|
||||
info!("CtrlKbdLedTask saving last brightness");
|
||||
Self::update_config(&mut lock)
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
|
||||
let inner = self.inner.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_sleep().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
loop {
|
||||
// Loop so that we do aquire the lock but also don't block other
|
||||
// threads (prevents potential deadlocks)
|
||||
if let Ok(lock) = inner.clone().try_lock() {
|
||||
load_save(args.start, lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
if let Ok(notif) = manager.receive_prepare_for_shutdown().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
loop {
|
||||
if let Ok(lock) = inner.clone().try_lock() {
|
||||
load_save(args.start, lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedReloader(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl crate::Reloadable for CtrlKbdLedReloader {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.write_current_config_mode()?;
|
||||
ctrl.set_power_states(&ctrl.config)
|
||||
.map_err(|err| warn!("{err}"))
|
||||
.ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedZbus(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl CtrlKbdLedZbus {
|
||||
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlKbdLed {
|
||||
pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> {
|
||||
// TODO: return error if *all* nodes are None
|
||||
let mut led_prod = None;
|
||||
let mut led_node = None;
|
||||
for prod in ASUS_KEYBOARD_DEVICES.iter() {
|
||||
match Self::find_led_node(prod) {
|
||||
Ok(node) => {
|
||||
led_prod = Some(prod.to_string());
|
||||
led_node = Some(node);
|
||||
info!("Looked for keyboard controller 0x{prod}: Found");
|
||||
break;
|
||||
}
|
||||
Err(err) => info!("Looked for keyboard controller 0x{prod}: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
let bright_node = Self::get_kbd_bright_path();
|
||||
|
||||
if led_node.is_none() {
|
||||
return Err(RogError::NoAuraKeyboard);
|
||||
}
|
||||
|
||||
if bright_node.is_none() {
|
||||
return Err(RogError::MissingFunction(
|
||||
"No brightness control, you may require a v5.11 series kernel or newer".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let ctrl = CtrlKbdLed {
|
||||
led_prod,
|
||||
led_node,
|
||||
bright_node: bright_node.unwrap(), // If was none then we already returned above
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
config,
|
||||
};
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn get_kbd_bright_path() -> Option<String> {
|
||||
if Path::new(KBD_BRIGHT_PATH).exists() {
|
||||
return Some(KBD_BRIGHT_PATH.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn get_brightness(&self) -> Result<u8, RogError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&self.bright_node)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&self.bright_node).into(), err),
|
||||
})?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
|
||||
pub(super) fn set_brightness(&self, brightness: LedBrightness) -> Result<(), RogError> {
|
||||
let path = Path::new(&self.bright_node);
|
||||
let mut file =
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&self.bright_node).into(), err),
|
||||
})?;
|
||||
file.write_all(&[brightness.as_char_code()])
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_brightness(&mut self) -> Result<(), RogError> {
|
||||
let mut bright = (self.config.brightness as u32) + 1;
|
||||
if bright > 3 {
|
||||
bright = 0;
|
||||
}
|
||||
self.config.brightness = <LedBrightness>::from(bright);
|
||||
self.config.write();
|
||||
self.set_brightness(self.config.brightness)
|
||||
}
|
||||
|
||||
pub fn prev_brightness(&mut self) -> Result<(), RogError> {
|
||||
let mut bright = self.config.brightness as u32;
|
||||
if bright == 0 {
|
||||
bright = 3;
|
||||
} else {
|
||||
bright -= 1;
|
||||
}
|
||||
self.config.brightness = <LedBrightness>::from(bright);
|
||||
self.config.write();
|
||||
self.set_brightness(self.config.brightness)
|
||||
}
|
||||
|
||||
/// Set combination state for boot animation/sleep animation/all leds/keys leds/side leds LED active
|
||||
pub(super) fn set_power_states(&self, config: &AuraConfig) -> Result<(), RogError> {
|
||||
let bytes = AuraPowerConfig::to_bytes(&config.enabled);
|
||||
let message = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2]];
|
||||
|
||||
self.write_bytes(&message)?;
|
||||
self.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn find_led_node(id_product: &str) -> Result<String, RogError> {
|
||||
let mut enumerator = udev::Enumerator::new().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("enumerator failed".into(), err)
|
||||
})?;
|
||||
enumerator.match_subsystem("hidraw").map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("match_subsystem failed".into(), err)
|
||||
})?;
|
||||
|
||||
for device in enumerator.scan_devices().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("scan_devices failed".into(), err)
|
||||
})? {
|
||||
if let Some(parent) = device
|
||||
.parent_with_subsystem_devtype("usb", "usb_device")
|
||||
.map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("parent_with_subsystem_devtype failed".into(), err)
|
||||
})?
|
||||
{
|
||||
if parent
|
||||
.attribute_value("idProduct")
|
||||
.ok_or_else(|| RogError::NotFound("LED idProduct".into()))?
|
||||
== id_product
|
||||
{
|
||||
if let Some(dev_node) = device.devnode() {
|
||||
info!("Using device at: {:?} for LED control", dev_node);
|
||||
return Ok(dev_node.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(RogError::MissingFunction(
|
||||
"ASUS LED device node not found".into(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Set an Aura effect if the effect mode or zone is supported.
|
||||
///
|
||||
/// On success the aura config file is read to refresh cached values, then the effect is
|
||||
/// stored and config written to disk.
|
||||
pub(crate) fn set_effect(&mut self, effect: AuraEffect) -> Result<(), RogError> {
|
||||
if !self.supported_modes.standard.contains(&effect.mode) {
|
||||
return Err(RogError::AuraEffectNotSupported);
|
||||
} else if effect.zone != AuraZone::None
|
||||
&& !self.supported_modes.multizone.contains(&effect.zone)
|
||||
{
|
||||
return Err(RogError::AuraEffectNotSupported);
|
||||
}
|
||||
|
||||
self.write_mode(&effect)?;
|
||||
self.config.read(); // refresh config if successful
|
||||
self.config.set_builtin(effect);
|
||||
self.config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Should only be used if the bytes you are writing are verified correct
|
||||
fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
|
||||
if let Some(led_node) = &self.led_node {
|
||||
if let Ok(mut file) = OpenOptions::new().write(true).open(led_node) {
|
||||
// println!("write: {:02x?}", &message);
|
||||
return file
|
||||
.write_all(message)
|
||||
.map_err(|err| RogError::Write("write_bytes".into(), err));
|
||||
}
|
||||
}
|
||||
Err(RogError::NoAuraNode)
|
||||
}
|
||||
|
||||
/// Write an effect block. This is for per-key
|
||||
fn _write_effect(&mut self, effect: &[Vec<u8>]) -> Result<(), RogError> {
|
||||
if self.flip_effect_write {
|
||||
for row in effect.iter().rev() {
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
} else {
|
||||
for row in effect.iter() {
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
}
|
||||
self.flip_effect_write = !self.flip_effect_write;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
|
||||
let current = self.config.current_mode;
|
||||
if let Some(idx) = self
|
||||
.supported_modes
|
||||
.standard
|
||||
.iter()
|
||||
.position(|v| *v == current)
|
||||
{
|
||||
let mut idx = idx;
|
||||
// goes past end of array
|
||||
if reverse {
|
||||
if idx == 0 {
|
||||
idx = self.supported_modes.standard.len() - 1;
|
||||
} else {
|
||||
idx -= 1;
|
||||
}
|
||||
} else {
|
||||
idx += 1;
|
||||
if idx == self.supported_modes.standard.len() {
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
let next = self.supported_modes.standard[idx];
|
||||
|
||||
self.config.read();
|
||||
if self.config.builtins.contains_key(&next) {
|
||||
self.config.current_mode = next;
|
||||
self.write_current_config_mode()?;
|
||||
}
|
||||
self.config.write();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_mode(&self, mode: &AuraEffect) -> Result<(), RogError> {
|
||||
let bytes: [u8; LED_MSG_LEN] = mode.into();
|
||||
self.write_bytes(&bytes)?;
|
||||
self.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_current_config_mode(&mut self) -> Result<(), RogError> {
|
||||
if self.config.multizone_on {
|
||||
let mode = self.config.current_mode;
|
||||
let mut create = false;
|
||||
// There is no multizone config for this mode so create one here
|
||||
// using the colours of rainbow if it exists, or first available
|
||||
// mode, or random
|
||||
if self.config.multizone.is_none() {
|
||||
create = true;
|
||||
} else if let Some(multizones) = self.config.multizone.as_ref() {
|
||||
if !multizones.contains_key(&mode) {
|
||||
create = true;
|
||||
}
|
||||
}
|
||||
if create {
|
||||
info!("No user-set config for zone founding, attempting a default");
|
||||
self.create_multizone_default()?;
|
||||
}
|
||||
|
||||
if let Some(multizones) = self.config.multizone.as_ref() {
|
||||
if let Some(set) = multizones.get(&mode) {
|
||||
for mode in set {
|
||||
self.write_mode(mode)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mode = self.config.current_mode;
|
||||
if let Some(effect) = self.config.builtins.get(&mode) {
|
||||
self.write_mode(effect)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a default for the `current_mode` if multizone and no config exists.
|
||||
fn create_multizone_default(&mut self) -> Result<(), RogError> {
|
||||
let mut default = vec![];
|
||||
for (i, tmp) in self.supported_modes.multizone.iter().enumerate() {
|
||||
default.push(AuraEffect {
|
||||
mode: self.config.current_mode,
|
||||
zone: *tmp,
|
||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Left,
|
||||
})
|
||||
}
|
||||
if default.is_empty() {
|
||||
return Err(RogError::AuraEffectNotSupported);
|
||||
}
|
||||
|
||||
if let Some(multizones) = self.config.multizone.as_mut() {
|
||||
multizones.insert(self.config.current_mode, default);
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(self.config.current_mode, default);
|
||||
self.config.multizone = Some(tmp);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
|
||||
|
||||
use crate::{ctrl_aura::config::AuraConfig, laptops::LaptopLedData};
|
||||
|
||||
use super::CtrlKbdLed;
|
||||
|
||||
#[test]
|
||||
// #[ignore = "Must be manually run due to detection stage"]
|
||||
fn check_set_mode_errors() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::default();
|
||||
let supported_modes = LaptopLedData {
|
||||
prod_family: "".into(),
|
||||
board_names: vec![],
|
||||
standard: vec![AuraModeNum::Static],
|
||||
multizone: vec![],
|
||||
per_key: false,
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_prod: None,
|
||||
led_node: None,
|
||||
bright_node: String::new(),
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
config,
|
||||
};
|
||||
|
||||
let mut effect = AuraEffect::default();
|
||||
effect.colour1 = Colour(0xff, 0x00, 0xff);
|
||||
effect.zone = AuraZone::None;
|
||||
|
||||
// This error comes from write_bytes because we don't have a keyboard node stored
|
||||
assert_eq!(
|
||||
controller
|
||||
.set_effect(effect.clone())
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"No Aura keyboard node found"
|
||||
);
|
||||
|
||||
effect.mode = AuraModeNum::Laser;
|
||||
assert_eq!(
|
||||
controller
|
||||
.set_effect(effect.clone())
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"Aura effect not supported"
|
||||
);
|
||||
|
||||
effect.mode = AuraModeNum::Static;
|
||||
effect.zone = AuraZone::Key2;
|
||||
assert_eq!(
|
||||
controller
|
||||
.set_effect(effect.clone())
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"Aura effect not supported"
|
||||
);
|
||||
|
||||
controller.supported_modes.multizone.push(AuraZone::Key2);
|
||||
assert_eq!(
|
||||
controller
|
||||
.set_effect(effect.clone())
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"No Aura keyboard node found"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_multizone_if_no_config() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::default();
|
||||
let supported_modes = LaptopLedData {
|
||||
prod_family: "".into(),
|
||||
board_names: vec![],
|
||||
standard: vec![AuraModeNum::Static],
|
||||
multizone: vec![],
|
||||
per_key: false,
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_prod: None,
|
||||
led_node: None,
|
||||
bright_node: String::new(),
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
config,
|
||||
};
|
||||
|
||||
assert!(controller.config.multizone.is_none());
|
||||
assert!(controller.create_multizone_default().is_err());
|
||||
assert!(controller.config.multizone.is_none());
|
||||
|
||||
controller.supported_modes.multizone.push(AuraZone::Key1);
|
||||
controller.supported_modes.multizone.push(AuraZone::Key2);
|
||||
assert!(controller.create_multizone_default().is_ok());
|
||||
assert!(controller.config.multizone.is_some());
|
||||
|
||||
let m = controller.config.multizone.unwrap();
|
||||
assert!(m.contains_key(&AuraModeNum::Static));
|
||||
let e = m.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(e.len(), 2);
|
||||
assert_eq!(e[0].zone, AuraZone::Key1);
|
||||
assert_eq!(e[1].zone, AuraZone::Key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_mode_create_multizone_if_no_config() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::default();
|
||||
let supported_modes = LaptopLedData {
|
||||
prod_family: "".into(),
|
||||
board_names: vec![],
|
||||
standard: vec![AuraModeNum::Static],
|
||||
multizone: vec![AuraZone::Key1, AuraZone::Key2],
|
||||
per_key: false,
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_prod: None,
|
||||
led_node: None,
|
||||
bright_node: String::new(),
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
config,
|
||||
};
|
||||
|
||||
assert!(controller.config.multizone.is_none());
|
||||
controller.config.multizone_on = true;
|
||||
// This is called in toggle_mode. It will error here because we have no
|
||||
// keyboard node in tests.
|
||||
assert_eq!(
|
||||
controller
|
||||
.write_current_config_mode()
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"No Aura keyboard node found"
|
||||
);
|
||||
assert!(controller.config.multizone.is_some());
|
||||
|
||||
let m = controller.config.multizone.unwrap();
|
||||
assert!(m.contains_key(&AuraModeNum::Static));
|
||||
let e = m.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(e.len(), 2);
|
||||
assert_eq!(e[0].zone, AuraZone::Key1);
|
||||
assert_eq!(e[1].zone, AuraZone::Key2);
|
||||
}
|
||||
}
|
||||
3
daemon/src/ctrl_aura/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod config;
|
||||
pub mod controller;
|
||||
pub mod zbus;
|
||||
221
daemon/src/ctrl_aura/zbus.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
use async_trait::async_trait;
|
||||
use log::warn;
|
||||
use rog_aura::{usb::AuraPowerDev, AuraEffect, LedBrightness};
|
||||
use zbus::{dbus_interface, Connection, SignalContext};
|
||||
|
||||
use super::controller::CtrlKbdLedZbus;
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlKbdLedZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Aura", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// The main interface for changing, reading, or notfying signals
|
||||
///
|
||||
/// LED commands are split between Brightness, Modes, Per-Key
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlKbdLedZbus {
|
||||
/// Set the keyboard brightness level (0-3)
|
||||
async fn set_brightness(&mut self, brightness: LedBrightness) {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
ctrl.set_brightness(brightness)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a variety of states, input is array of enum.
|
||||
///
|
||||
/// enum AuraControl {
|
||||
/// BootLogo,
|
||||
/// BootKeyb,
|
||||
/// AwakeLogo,
|
||||
/// AwakeKeyb,
|
||||
/// SleepLogo,
|
||||
/// SleepKeyb,
|
||||
/// ShutdownLogo,
|
||||
/// ShutdownKeyb,
|
||||
/// AwakeBar,
|
||||
/// BootBar,
|
||||
/// SleepBar,
|
||||
/// ShutdownBar,
|
||||
/// }
|
||||
async fn set_leds_power(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
options: AuraPowerDev,
|
||||
enabled: bool,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut states = None;
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
for p in options.x1866 {
|
||||
ctrl.config.enabled.set_0x1866(p, enabled);
|
||||
}
|
||||
for p in options.x19b6 {
|
||||
ctrl.config.enabled.set_0x19b6(p, enabled);
|
||||
}
|
||||
|
||||
ctrl.config.write();
|
||||
|
||||
ctrl.set_power_states(&ctrl.config).map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
states = Some(AuraPowerDev::from(&ctrl.config.enabled));
|
||||
}
|
||||
// Need to pull state out like this due to MutexGuard
|
||||
if let Some(states) = states {
|
||||
Self::notify_power_states(&ctxt, &states)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_led_mode(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
effect: AuraEffect,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut led = None;
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.set_effect(effect).map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
led = Some(mode.clone());
|
||||
}
|
||||
}
|
||||
if let Some(led) = led {
|
||||
Self::notify_led(&ctxt, led)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_led_mode(
|
||||
&self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut led = None;
|
||||
if let Ok(mut ctrl) = self.0.lock() {
|
||||
ctrl.toggle_mode(false).map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
led = Some(mode.clone());
|
||||
}
|
||||
}
|
||||
if let Some(led) = led {
|
||||
Self::notify_led(&ctxt, led)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prev_led_mode(
|
||||
&self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut led = None;
|
||||
if let Ok(mut ctrl) = self.0.lock() {
|
||||
ctrl.toggle_mode(true).map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
led = Some(mode.clone());
|
||||
}
|
||||
}
|
||||
if let Some(led) = led {
|
||||
Self::notify_led(&ctxt, led)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_led_brightness(&self) -> zbus::fdo::Result<()> {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.next_brightness().map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prev_led_brightness(&self) -> zbus::fdo::Result<()> {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.prev_brightness().map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// As property doesn't work for AuraPowerDev (complexity of serialization?)
|
||||
// #[dbus_interface(property)]
|
||||
async fn leds_power(&self) -> AuraPowerDev {
|
||||
loop {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return AuraPowerDev::from(&ctrl.config.enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the current mode data
|
||||
#[dbus_interface(property)]
|
||||
async fn led_mode(&self) -> String {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not deserialise".to_string()
|
||||
}
|
||||
|
||||
/// Return a list of available modes
|
||||
#[dbus_interface(property)]
|
||||
async fn led_modes(&self) -> String {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not serialise".to_string()
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
#[dbus_interface(property)]
|
||||
async fn led_brightness(&self) -> i8 {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1);
|
||||
}
|
||||
warn!("SetKeyBacklight could not serialise");
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_led(signal_ctxt: &SignalContext<'_>, data: AuraEffect) -> zbus::Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_power_states(
|
||||
signal_ctxt: &SignalContext<'_>,
|
||||
data: &AuraPowerDev,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
@@ -1,16 +1,23 @@
|
||||
use crate::CtrlTask;
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
//use crate::dbus::DbusEvents;
|
||||
use async_trait::async_trait;
|
||||
use log::{info, warn};
|
||||
use rog_types::supported::ChargeSupportedFunctions;
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use rog_supported::ChargeSupportedFunctions;
|
||||
use smol::stream::StreamExt;
|
||||
use smol::Executor;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
use zbus::Connection;
|
||||
use zbus::SignalContext;
|
||||
|
||||
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
|
||||
static BAT_CHARGE_PATH0: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
|
||||
static BAT_CHARGE_PATH1: &str = "/sys/class/power_supply/BAT1/charge_control_end_threshold";
|
||||
static BAT_CHARGE_PATH2: &str = "/sys/class/power_supply/BAT2/charge_control_end_threshold";
|
||||
|
||||
impl GetSupported for CtrlCharge {
|
||||
type A = ChargeSupportedFunctions;
|
||||
@@ -28,24 +35,27 @@ pub struct CtrlCharge {
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlCharge {
|
||||
pub fn set_limit(&mut self, limit: u8) {
|
||||
async fn set_limit(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
limit: u8,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
if !(20..=100).contains(&limit) {
|
||||
return Err(RogError::ChargeLimit(limit))?;
|
||||
}
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
self.set(limit, &mut config)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
self.notify_charge(limit)
|
||||
Self::set(limit, &mut config)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Self::notify_charge(&ctxt, limit).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn limit(&self) -> i8 {
|
||||
fn limit(&self) -> i8 {
|
||||
if let Ok(config) = self.config.try_lock() {
|
||||
return config.bat_charge_limit as i8;
|
||||
}
|
||||
@@ -53,21 +63,13 @@ impl CtrlCharge {
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
pub fn notify_charge(&self, limit: u8) -> zbus::Result<()> {}
|
||||
async fn notify_charge(ctxt: &SignalContext<'_>, limit: u8) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlCharge {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Charge"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Charge", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +77,7 @@ impl crate::Reloadable for CtrlCharge {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
config.read();
|
||||
self.set(config.bat_charge_limit, &mut config)?;
|
||||
Self::set(config.bat_charge_limit, &mut config)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -88,8 +90,12 @@ impl CtrlCharge {
|
||||
}
|
||||
|
||||
fn get_battery_path() -> Result<&'static str, RogError> {
|
||||
if Path::new(BAT_CHARGE_PATH).exists() {
|
||||
Ok(BAT_CHARGE_PATH)
|
||||
if Path::new(BAT_CHARGE_PATH0).exists() {
|
||||
Ok(BAT_CHARGE_PATH0)
|
||||
} else if Path::new(BAT_CHARGE_PATH1).exists() {
|
||||
Ok(BAT_CHARGE_PATH1)
|
||||
} else if Path::new(BAT_CHARGE_PATH2).exists() {
|
||||
Ok(BAT_CHARGE_PATH2)
|
||||
} else {
|
||||
Err(RogError::MissingFunction(
|
||||
"Charge control not available, you may require a v5.8.10 series kernel or newer"
|
||||
@@ -98,20 +104,19 @@ impl CtrlCharge {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set(&self, limit: u8, config: &mut Config) -> Result<(), RogError> {
|
||||
pub(super) fn set(limit: u8, config: &mut Config) -> Result<(), RogError> {
|
||||
if !(20..=100).contains(&limit) {
|
||||
warn!(
|
||||
"Unable to set battery charge limit, must be between 20-100: requested {}",
|
||||
limit
|
||||
);
|
||||
return Err(RogError::ChargeLimit(limit));
|
||||
}
|
||||
|
||||
let path = Self::get_battery_path()?;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(BAT_CHARGE_PATH)
|
||||
.map_err(|err| RogError::Path(BAT_CHARGE_PATH.into(), err))?;
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
file.write_all(limit.to_string().as_bytes())
|
||||
.map_err(|err| RogError::Write(BAT_CHARGE_PATH.into(), err))?;
|
||||
.map_err(|err| RogError::Write(path.into(), err))?;
|
||||
info!("Battery charge limit: {}", limit);
|
||||
|
||||
config.read();
|
||||
@@ -121,3 +126,76 @@ impl CtrlCharge {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlCharge {
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("CtrlCharge could not create dbus connection");
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlCharge could not create ManagerProxy");
|
||||
|
||||
let config1 = self.config.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_sleep().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
// If waking up
|
||||
if !args.start {
|
||||
info!("CtrlCharge reloading charge limit");
|
||||
if let Ok(mut lock) = config1.try_lock() {
|
||||
Self::set(lock.bat_charge_limit, &mut lock)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlCharge could not create ManagerProxy");
|
||||
|
||||
let config = self.config.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_shutdown().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
// If waking up - intention is to catch hibernation event
|
||||
if !args.start {
|
||||
info!("CtrlCharge reloading charge limit");
|
||||
loop {
|
||||
if let Ok(mut lock) = config.clone().try_lock() {
|
||||
Self::set(lock.bat_charge_limit, &mut lock)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,394 +0,0 @@
|
||||
use crate::error::RogError;
|
||||
use crate::{config::Config, GetSupported};
|
||||
use log::{info, warn};
|
||||
use rog_types::{profile::{FanLevel, Profile, ProfileEvent}, supported::FanCpuSupportedFunctions};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::{dbus_interface, fdo::Error};
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy";
|
||||
static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode";
|
||||
static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost";
|
||||
|
||||
pub struct CtrlFanAndCpu {
|
||||
pub path: &'static str,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlFanAndCpu {
|
||||
type A = FanCpuSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
FanCpuSupportedFunctions {
|
||||
stock_fan_modes: CtrlFanAndCpu::get_fan_path().is_ok(),
|
||||
min_max_freq: intel_pstate::PState::new().is_ok(),
|
||||
fan_curve_set: rog_fan_curve::Board::from_board_name().is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DbusFanAndCpu {
|
||||
inner: Arc<Mutex<CtrlFanAndCpu>>,
|
||||
}
|
||||
|
||||
impl DbusFanAndCpu {
|
||||
pub fn new(inner: Arc<Mutex<CtrlFanAndCpu>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl DbusFanAndCpu {
|
||||
/// Set profile details
|
||||
fn set_profile(&self, profile: String) {
|
||||
if let Ok(event) = serde_json::from_str(&profile) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
cfg.read();
|
||||
ctrl.handle_profile_event(&event, &mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
if let Ok(json) = serde_json::to_string(profile) {
|
||||
self.notify_profile(&json)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the active profile name
|
||||
fn next_profile(&mut self) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
cfg.read();
|
||||
ctrl.do_next_profile(&mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
if let Ok(json) = serde_json::to_string(profile) {
|
||||
self.notify_profile(&json)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the active profile name
|
||||
fn active_profile_name(&mut self) -> zbus::fdo::Result<String> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
return Ok(cfg.active_profile.clone());
|
||||
}
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get active profile name".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
// TODO: Profile can't implement Type because of Curve
|
||||
/// Fetch the active profile details
|
||||
fn profile(&mut self) -> zbus::fdo::Result<String> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
if let Ok(json) = serde_json::to_string_pretty(profile) {
|
||||
return Ok(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get active profile details".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Fetch all profile data
|
||||
fn profiles(&mut self) -> zbus::fdo::Result<String> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
if let Ok(json) = serde_json::to_string_pretty(&cfg.power_profiles) {
|
||||
return Ok(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get all profile details".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
fn profile_names(&self) -> zbus::fdo::Result<Vec<String>> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
let profile_names = cfg.power_profiles.keys().cloned().collect::<Vec<String>>();
|
||||
return Ok(profile_names);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Failed("Failed to get all profile names".to_string()))
|
||||
}
|
||||
|
||||
fn remove(&self, profile: &str) -> zbus::fdo::Result<()> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
|
||||
if !cfg.power_profiles.contains_key(profile) {
|
||||
return Err(Error::Failed("Invalid profile specified".to_string()));
|
||||
}
|
||||
|
||||
if cfg.power_profiles.keys().len() == 1 {
|
||||
return Err(Error::Failed("Cannot delete the last profile".to_string()));
|
||||
}
|
||||
|
||||
if cfg.active_profile == *profile {
|
||||
return Err(Error::Failed(
|
||||
"Cannot delete the active profile".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
cfg.power_profiles.remove(profile);
|
||||
cfg.write();
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Failed("Failed to lock configuration".to_string()))
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_profile(&self, profile: &str) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for DbusFanAndCpu {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Profile"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("DbusFanAndCpu: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlFanAndCpu {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut config) = self.config.clone().try_lock() {
|
||||
let profile = config.active_profile.clone();
|
||||
self.set(&profile, &mut config)?;
|
||||
// info!(
|
||||
// "Reloaded fan mode: {:?}",
|
||||
// FanLevel::from(config.power_profile)
|
||||
// );
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlFanAndCpu {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
let path = CtrlFanAndCpu::get_fan_path()?;
|
||||
info!("Device has thermal throttle control");
|
||||
Ok(CtrlFanAndCpu { path, config })
|
||||
}
|
||||
|
||||
fn get_fan_path() -> Result<&'static str, RogError> {
|
||||
if Path::new(FAN_TYPE_1_PATH).exists() {
|
||||
Ok(FAN_TYPE_1_PATH)
|
||||
} else if Path::new(FAN_TYPE_2_PATH).exists() {
|
||||
Ok(FAN_TYPE_2_PATH)
|
||||
} else {
|
||||
Err(RogError::MissingFunction(
|
||||
"Fan mode not available, you may require a v5.8.10 series kernel or newer".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle to next profile in list
|
||||
pub(super) fn do_next_profile(&mut self, config: &mut Config) -> Result<(), RogError> {
|
||||
config.read();
|
||||
|
||||
let mut i = config
|
||||
.toggle_profiles
|
||||
.iter()
|
||||
.position(|x| x == &config.active_profile)
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0);
|
||||
if i >= config.toggle_profiles.len() {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
let new_profile = config
|
||||
.toggle_profiles
|
||||
.get(i)
|
||||
.unwrap_or(&config.active_profile)
|
||||
.clone();
|
||||
|
||||
self.set(&new_profile, config)?;
|
||||
|
||||
info!("Profile was changed: {}", &new_profile);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_fan_mode(&mut self, preset: u8, config: &mut Config) -> Result<(), RogError> {
|
||||
let mode = config.active_profile.clone();
|
||||
let mut fan_ctrl = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
config.read();
|
||||
let mut mode_config = config
|
||||
.power_profiles
|
||||
.get_mut(&mode)
|
||||
.ok_or_else(|| RogError::MissingProfile(mode.clone()))?;
|
||||
config.curr_fan_mode = preset;
|
||||
mode_config.fan_preset = preset;
|
||||
config.write();
|
||||
fan_ctrl
|
||||
.write_all(format!("{}\n", preset).as_bytes())
|
||||
.map_err(|err| RogError::Write(self.path.into(), err))?;
|
||||
info!("Fan mode set to: {:?}", FanLevel::from(preset));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_profile_event(
|
||||
&mut self,
|
||||
event: &ProfileEvent,
|
||||
config: &mut Config,
|
||||
) -> Result<(), RogError> {
|
||||
match event {
|
||||
ProfileEvent::Toggle => self.do_next_profile(config)?,
|
||||
ProfileEvent::ChangeMode(mode) => {
|
||||
self.set_fan_mode(*mode, config)?;
|
||||
let mode = config.active_profile.clone();
|
||||
self.set_pstate_for_fan_mode(&mode, config)?;
|
||||
self.set_fan_curve_for_fan_mode(&mode, config)?;
|
||||
}
|
||||
ProfileEvent::Cli(command) => {
|
||||
let profile_key = match command.profile.as_ref() {
|
||||
Some(k) => k.clone(),
|
||||
None => config.active_profile.clone(),
|
||||
};
|
||||
|
||||
let mut profile = if command.create {
|
||||
config
|
||||
.power_profiles
|
||||
.entry(profile_key.clone())
|
||||
.or_insert_with(Profile::default)
|
||||
} else {
|
||||
config
|
||||
.power_profiles
|
||||
.get_mut(&profile_key)
|
||||
.ok_or_else(|| RogError::MissingProfile(profile_key.clone()))?
|
||||
};
|
||||
|
||||
if command.turbo.is_some() {
|
||||
profile.turbo = command.turbo.unwrap();
|
||||
}
|
||||
if let Some(min_perc) = command.min_percentage {
|
||||
profile.min_percentage = min_perc;
|
||||
}
|
||||
if let Some(max_perc) = command.max_percentage {
|
||||
profile.max_percentage = max_perc;
|
||||
}
|
||||
if let Some(ref preset) = command.fan_preset {
|
||||
profile.fan_preset = preset.into();
|
||||
}
|
||||
if let Some(ref curve) = command.curve {
|
||||
profile.fan_curve = Some(curve.clone());
|
||||
}
|
||||
|
||||
self.set(&profile_key, config)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set(&mut self, profile: &str, config: &mut Config) -> Result<(), RogError> {
|
||||
let mode_config = config
|
||||
.power_profiles
|
||||
.get(profile)
|
||||
.ok_or_else(|| RogError::MissingProfile(profile.into()))?;
|
||||
let mut fan_ctrl = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
config.curr_fan_mode = mode_config.fan_preset;
|
||||
fan_ctrl
|
||||
.write_all(format!("{}\n", mode_config.fan_preset).as_bytes())
|
||||
.map_err(|err| RogError::Write(self.path.into(), err))?;
|
||||
|
||||
self.set_pstate_for_fan_mode(profile, config)?;
|
||||
self.set_fan_curve_for_fan_mode(profile, config)?;
|
||||
|
||||
config.active_profile = profile.into();
|
||||
|
||||
config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pstate_for_fan_mode(&self, mode: &str, config: &mut Config) -> Result<(), RogError> {
|
||||
info!("Setting pstate");
|
||||
let mode_config = config
|
||||
.power_profiles
|
||||
.get(mode)
|
||||
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
|
||||
|
||||
// Set CPU pstate
|
||||
if let Ok(pstate) = intel_pstate::PState::new() {
|
||||
pstate.set_min_perf_pct(mode_config.min_percentage)?;
|
||||
pstate.set_max_perf_pct(mode_config.max_percentage)?;
|
||||
pstate.set_no_turbo(!mode_config.turbo)?;
|
||||
info!(
|
||||
"Intel CPU Power: min: {}%, max: {}%, turbo: {}",
|
||||
mode_config.min_percentage, mode_config.max_percentage, mode_config.turbo
|
||||
);
|
||||
} else {
|
||||
info!("Setting pstate for AMD CPU");
|
||||
// must be AMD CPU
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(AMD_BOOST_PATH)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
|
||||
let boost = if mode_config.turbo { "1" } else { "0" }; // opposite of Intel
|
||||
file.write_all(boost.as_bytes())
|
||||
.map_err(|err| RogError::Write(AMD_BOOST_PATH.into(), err))?;
|
||||
info!("AMD CPU Turbo: {}", boost);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_fan_curve_for_fan_mode(&self, mode: &str, config: &Config) -> Result<(), RogError> {
|
||||
let mode_config = &config
|
||||
.power_profiles
|
||||
.get(mode)
|
||||
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
|
||||
|
||||
if let Some(ref curve) = mode_config.fan_curve {
|
||||
use rog_fan_curve::{Board, Fan};
|
||||
if let Some(board) = Board::from_board_name() {
|
||||
curve.apply(board, Fan::Cpu)?;
|
||||
curve.apply(board, Fan::Gpu)?;
|
||||
} else {
|
||||
warn!("Fan curve unsupported on this board.")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::{error, process::ExitStatus};
|
||||
|
||||
use crate::error::RogError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GfxError {
|
||||
ParseVendor,
|
||||
ParsePower,
|
||||
Bus(String, std::io::Error),
|
||||
DisplayManagerAction(String, ExitStatus),
|
||||
DisplayManagerTimeout(String),
|
||||
GsyncModeActive,
|
||||
VfioBuiltin,
|
||||
VfioDisabled,
|
||||
MissingModule(String),
|
||||
Modprobe(String),
|
||||
Command(String, std::io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for GfxError {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
GfxError::ParseVendor => write!(f, "Could not parse vendor name"),
|
||||
GfxError::ParsePower => write!(f, "Could not parse dGPU power status"),
|
||||
GfxError::Bus(func, error) => write!(f, "Bus error: {}: {}", func, error),
|
||||
GfxError::DisplayManagerAction(action, status) => {
|
||||
write!(f, "Display-manager action {} failed: {}", action, status)
|
||||
}
|
||||
GfxError::DisplayManagerTimeout(state) => {
|
||||
write!(f, "Timed out waiting for display-manager {} state", state)
|
||||
}
|
||||
GfxError::GsyncModeActive => write!(
|
||||
f,
|
||||
"Can not switch gfx modes when dedicated/G-Sync mode is active"
|
||||
),
|
||||
GfxError::VfioBuiltin => write!(
|
||||
f,
|
||||
"Can not switch to vfio mode if the modules are built in to kernel"
|
||||
),
|
||||
GfxError::VfioDisabled => {
|
||||
write!(f, "Can not switch to vfio mode if disabled in config file")
|
||||
}
|
||||
GfxError::MissingModule(m) => write!(f, "The module {} is missing", m),
|
||||
GfxError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
|
||||
GfxError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for GfxError {}
|
||||
|
||||
impl From<GfxError> for RogError {
|
||||
fn from(err: GfxError) -> Self {
|
||||
RogError::GfxSwitching(err)
|
||||
}
|
||||
}
|
||||
@@ -1,679 +0,0 @@
|
||||
use ctrl_gfx::error::GfxError;
|
||||
use ctrl_gfx::*;
|
||||
use ctrl_rog_bios::CtrlRogBios;
|
||||
use log::{error, info, warn};
|
||||
use logind_zbus::{
|
||||
types::{SessionClass, SessionInfo, SessionState, SessionType},
|
||||
ManagerProxy, SessionProxy,
|
||||
};
|
||||
use rog_types::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors};
|
||||
use std::{io::Write, ops::Add, path::Path, time::Instant};
|
||||
use std::{iter::FromIterator, thread::JoinHandle};
|
||||
use std::{process::Command, thread::sleep, time::Duration};
|
||||
use std::{str::FromStr, sync::mpsc};
|
||||
use std::{sync::Arc, sync::Mutex};
|
||||
use sysfs_class::{PciDevice, SysClass};
|
||||
use system::{GraphicsDevice, PciBus};
|
||||
use zbus::{dbus_interface, Connection};
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
use crate::*;
|
||||
|
||||
const THREAD_TIMEOUT_MSG: &str = "GFX: thread time exceeded 3 minutes, exiting";
|
||||
|
||||
pub struct CtrlGraphics {
|
||||
bus: PciBus,
|
||||
_amd: Vec<GraphicsDevice>,
|
||||
_intel: Vec<GraphicsDevice>,
|
||||
nvidia: Vec<GraphicsDevice>,
|
||||
#[allow(dead_code)]
|
||||
other: Vec<GraphicsDevice>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
thread_kill: Arc<Mutex<Option<mpsc::Sender<bool>>>>,
|
||||
}
|
||||
|
||||
trait Dbus {
|
||||
fn vendor(&self) -> zbus::fdo::Result<GfxVendors>;
|
||||
fn power(&self) -> zbus::fdo::Result<GfxPower>;
|
||||
fn set_vendor(&mut self, vendor: GfxVendors) -> zbus::fdo::Result<GfxRequiredUserAction>;
|
||||
fn notify_gfx(&self, vendor: &GfxVendors) -> zbus::Result<()>;
|
||||
fn notify_action(&self, action: &GfxRequiredUserAction) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl Dbus for CtrlGraphics {
|
||||
fn vendor(&self) -> zbus::fdo::Result<GfxVendors> {
|
||||
self.get_gfx_mode().map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
|
||||
})
|
||||
}
|
||||
|
||||
fn power(&self) -> zbus::fdo::Result<GfxPower> {
|
||||
Self::get_runtime_status().map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
|
||||
})
|
||||
}
|
||||
|
||||
fn set_vendor(&mut self, vendor: GfxVendors) -> zbus::fdo::Result<GfxRequiredUserAction> {
|
||||
info!("GFX: Switching gfx mode to {}", <&str>::from(vendor));
|
||||
let msg = self.set_gfx_config(vendor).map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
|
||||
})?;
|
||||
self.notify_gfx(&vendor)
|
||||
.unwrap_or_else(|err| warn!("GFX: {}", err));
|
||||
self.notify_action(&msg)
|
||||
.unwrap_or_else(|err| warn!("GFX: {}", err));
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_gfx(&self, vendor: &GfxVendors) -> zbus::Result<()> {}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_action(&self, action: &GfxRequiredUserAction) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
impl ZbusAdd for CtrlGraphics {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Gfx"), self)
|
||||
.map_err(|err| {
|
||||
warn!("GFX: CtrlGraphics: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl Reloadable for CtrlGraphics {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
self.auto_power()?;
|
||||
info!("GFX: Reloaded gfx mode: {:?}", self.get_gfx_mode()?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlGraphics {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> std::io::Result<CtrlGraphics> {
|
||||
let bus = PciBus::new()?;
|
||||
info!("GFX: Rescanning PCI bus");
|
||||
bus.rescan()?;
|
||||
let devs = PciDevice::all()?;
|
||||
|
||||
let functions = |parent: &PciDevice| -> Vec<PciDevice> {
|
||||
let mut functions = Vec::new();
|
||||
if let Some(parent_slot) = parent.id().split('.').next() {
|
||||
for func in devs.iter() {
|
||||
if let Some(func_slot) = func.id().split('.').next() {
|
||||
if func_slot == parent_slot {
|
||||
info!("GFX: {}: Function for {}", func.id(), parent.id());
|
||||
functions.push(func.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
functions
|
||||
};
|
||||
|
||||
let mut amd = Vec::new();
|
||||
let mut intel = Vec::new();
|
||||
let mut nvidia = Vec::new();
|
||||
let mut other = Vec::new();
|
||||
for dev in devs.iter() {
|
||||
let c = dev.class()?;
|
||||
if 0x03 == (c >> 16) & 0xFF {
|
||||
match dev.vendor()? {
|
||||
0x1002 => {
|
||||
info!("GFX: {}: AMD graphics", dev.id());
|
||||
amd.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
|
||||
}
|
||||
0x10DE => {
|
||||
info!("GFX: {}: NVIDIA graphics", dev.id());
|
||||
nvidia.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
|
||||
}
|
||||
0x8086 => {
|
||||
info!("GFX: {}: Intel graphics", dev.id());
|
||||
intel.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
|
||||
}
|
||||
vendor => {
|
||||
info!("GFX: {}: Other({:X}) graphics", dev.id(), vendor);
|
||||
other.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CtrlGraphics {
|
||||
bus,
|
||||
_amd: amd,
|
||||
_intel: intel,
|
||||
nvidia,
|
||||
other,
|
||||
config,
|
||||
thread_kill: Arc::new(Mutex::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bus(&self) -> PciBus {
|
||||
self.bus.clone()
|
||||
}
|
||||
|
||||
pub fn devices(&self) -> Vec<GraphicsDevice> {
|
||||
self.nvidia.clone()
|
||||
}
|
||||
|
||||
/// Save the selected `Vendor` mode to config
|
||||
fn save_gfx_mode(vendor: GfxVendors, config: Arc<Mutex<Config>>) {
|
||||
if let Ok(mut config) = config.lock() {
|
||||
config.gfx_mode = vendor;
|
||||
config.write();
|
||||
}
|
||||
}
|
||||
|
||||
/// Associated method to get which vendor mode is set
|
||||
pub fn get_gfx_mode(&self) -> Result<GfxVendors, RogError> {
|
||||
if let Ok(config) = self.config.lock() {
|
||||
return Ok(config.gfx_mode);
|
||||
}
|
||||
// TODO: Error here
|
||||
Ok(GfxVendors::Hybrid)
|
||||
}
|
||||
|
||||
fn get_runtime_status() -> Result<GfxPower, RogError> {
|
||||
let path = Path::new("/sys/bus/pci/devices/0000:01:00.0/power/runtime_status");
|
||||
if path.exists() {
|
||||
let buf = std::fs::read_to_string(path).map_err(|err| {
|
||||
RogError::Read(
|
||||
"/sys/bus/pci/devices/0000:01:00.0/power/runtime_status".to_string(),
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
Ok(GfxPower::from_str(&buf)?)
|
||||
} else {
|
||||
Ok(GfxPower::Off)
|
||||
}
|
||||
}
|
||||
|
||||
/// Some systems have a fallback service to load nouveau if nvidia fails
|
||||
fn toggle_fallback_service(vendor: GfxVendors) -> Result<(), RogError> {
|
||||
let action = if vendor == GfxVendors::Nvidia {
|
||||
info!("GFX: Enabling nvidia-fallback.service");
|
||||
"enable"
|
||||
} else {
|
||||
info!("GFX: Disabling nvidia-fallback.service");
|
||||
"disable"
|
||||
};
|
||||
|
||||
let status = Command::new("systemctl")
|
||||
.arg(action)
|
||||
.arg("nvidia-fallback.service")
|
||||
.status()
|
||||
.map_err(|err| RogError::Command("systemctl".into(), err))?;
|
||||
|
||||
if !status.success() {
|
||||
// Error is ignored in case this service is removed
|
||||
warn!(
|
||||
"systemctl: {} (ignore warning if service does not exist!)",
|
||||
status
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the appropriate xorg config for the chosen mode
|
||||
fn write_xorg_conf(vendor: GfxVendors) -> Result<(), RogError> {
|
||||
let text = if vendor == GfxVendors::Nvidia {
|
||||
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_NVIDIA, PRIMARY_GPU_END].concat()
|
||||
} else {
|
||||
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_END].concat()
|
||||
};
|
||||
|
||||
if !Path::new(XORG_PATH).exists() {
|
||||
std::fs::create_dir(XORG_PATH).map_err(|err| RogError::Write(XORG_PATH.into(), err))?;
|
||||
}
|
||||
|
||||
let file = XORG_PATH.to_string().add(XORG_FILE);
|
||||
info!("GFX: Writing {}", file);
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(&file)
|
||||
.map_err(|err| RogError::Write(file, err))?;
|
||||
|
||||
file.write_all(&text)
|
||||
.and_then(|_| file.sync_all())
|
||||
.map_err(|err| RogError::Write(MODPROBE_PATH.into(), err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates the full modprobe.conf required for vfio pass-through
|
||||
fn get_vfio_conf(devices: &[GraphicsDevice]) -> Vec<u8> {
|
||||
let mut vifo = MODPROBE_VFIO.to_vec();
|
||||
for (d_count, dev) in devices.iter().enumerate() {
|
||||
for (f_count, func) in dev.functions().iter().enumerate() {
|
||||
let vendor = func.vendor().unwrap();
|
||||
let device = func.device().unwrap();
|
||||
unsafe {
|
||||
vifo.append(format!("{:x}", vendor).as_mut_vec());
|
||||
}
|
||||
vifo.append(&mut vec![b':']);
|
||||
unsafe {
|
||||
vifo.append(format!("{:x}", device).as_mut_vec());
|
||||
}
|
||||
if f_count < dev.functions().len() - 1 {
|
||||
vifo.append(&mut vec![b',']);
|
||||
}
|
||||
}
|
||||
if d_count < dev.functions().len() - 1 {
|
||||
vifo.append(&mut vec![b',']);
|
||||
}
|
||||
}
|
||||
let mut conf = MODPROBE_INTEGRATED.to_vec();
|
||||
conf.append(&mut vifo);
|
||||
conf
|
||||
}
|
||||
|
||||
fn write_modprobe_conf(vendor: GfxVendors, devices: &[GraphicsDevice]) -> Result<(), RogError> {
|
||||
info!("GFX: Writing {}", MODPROBE_PATH);
|
||||
let content = match vendor {
|
||||
GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Compute => MODPROBE_BASE.to_vec(),
|
||||
GfxVendors::Vfio => Self::get_vfio_conf(devices),
|
||||
// GfxVendors::Compute => {}
|
||||
GfxVendors::Integrated => MODPROBE_INTEGRATED.to_vec(),
|
||||
};
|
||||
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(MODPROBE_PATH)
|
||||
.map_err(|err| RogError::Path(MODPROBE_PATH.into(), err))?;
|
||||
|
||||
file.write_all(&content)
|
||||
.and_then(|_| file.sync_all())
|
||||
.map_err(|err| RogError::Write(MODPROBE_PATH.into(), err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unbind_remove_nvidia(devices: &[GraphicsDevice]) -> Result<(), RogError> {
|
||||
// Unbind NVIDIA graphics devices and their functions
|
||||
let unbinds = devices.iter().map(|dev| dev.unbind());
|
||||
// Remove NVIDIA graphics devices and their functions
|
||||
let removes = devices.iter().map(|dev| dev.remove());
|
||||
Result::from_iter(unbinds.chain(removes))
|
||||
.map_err(|err| RogError::Command("device unbind error".into(), err))
|
||||
}
|
||||
|
||||
fn unbind_only(devices: &[GraphicsDevice]) -> Result<(), RogError> {
|
||||
let unbinds = devices.iter().map(|dev| dev.unbind());
|
||||
Result::from_iter(unbinds)
|
||||
.map_err(|err| RogError::Command("device unbind error".into(), err))
|
||||
}
|
||||
|
||||
fn do_driver_action(driver: &str, action: &str) -> Result<(), GfxError> {
|
||||
let mut cmd = Command::new(action);
|
||||
cmd.arg(driver);
|
||||
|
||||
let mut count = 0;
|
||||
const MAX_TRIES: i32 = 6;
|
||||
loop {
|
||||
if count > MAX_TRIES {
|
||||
let msg = format!("{} {} failed for unknown reason", action, driver);
|
||||
error!("GFX: {}", msg);
|
||||
return Ok(()); //Err(RogError::Modprobe(msg));
|
||||
}
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?;
|
||||
if !output.status.success() {
|
||||
if output
|
||||
.stderr
|
||||
.ends_with("is not currently loaded\n".as_bytes())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
if output.stderr.ends_with("is builtin.\n".as_bytes()) {
|
||||
return Err(GfxError::VfioBuiltin);
|
||||
}
|
||||
if output.stderr.ends_with("Permission denied\n".as_bytes()) {
|
||||
warn!(
|
||||
"{} {} failed: {:?}",
|
||||
action,
|
||||
driver,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
warn!("GFX: It may be safe to ignore the above error, run `lsmod |grep {}` to confirm modules loaded", driver);
|
||||
return Ok(());
|
||||
}
|
||||
if String::from_utf8_lossy(&output.stderr)
|
||||
.contains(&format!("Module {} not found", driver))
|
||||
{
|
||||
return Err(GfxError::MissingModule(driver.into()));
|
||||
}
|
||||
if count >= MAX_TRIES {
|
||||
let msg = format!(
|
||||
"{} {} failed: {:?}",
|
||||
action,
|
||||
driver,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return Err(GfxError::Modprobe(msg));
|
||||
}
|
||||
} else if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
count += 1;
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
|
||||
fn do_display_manager_action(action: &str) -> Result<(), RogError> {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
cmd.arg(action);
|
||||
cmd.arg(DISPLAY_MANAGER);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
|
||||
if !status.success() {
|
||||
let msg = format!(
|
||||
"systemctl {} {} failed: {:?}",
|
||||
action, DISPLAY_MANAGER, status
|
||||
);
|
||||
return Err(GfxError::DisplayManagerAction(msg, status).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_display_manager_state(state: &str) -> Result<(), RogError> {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
cmd.arg("is-active");
|
||||
cmd.arg(DISPLAY_MANAGER);
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
while count <= 5 {
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
|
||||
if output.stdout.starts_with(state.as_bytes()) {
|
||||
return Ok(());
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
count += 1;
|
||||
}
|
||||
Err(GfxError::DisplayManagerTimeout(state.into()).into())
|
||||
}
|
||||
|
||||
/// Determine if we need to logout/thread. Integrated<->Vfio mode does not
|
||||
/// require logout.
|
||||
fn logout_required(&self, vendor: GfxVendors) -> GfxRequiredUserAction {
|
||||
if let Ok(config) = self.config.lock() {
|
||||
let current = config.gfx_mode;
|
||||
if matches!(current, GfxVendors::Integrated | GfxVendors::Vfio)
|
||||
&& matches!(vendor, GfxVendors::Integrated | GfxVendors::Vfio)
|
||||
{
|
||||
return GfxRequiredUserAction::None;
|
||||
}
|
||||
}
|
||||
GfxRequiredUserAction::Logout
|
||||
}
|
||||
|
||||
/// Write the config changes and add/remove drivers and devices depending
|
||||
/// on selected mode:
|
||||
///
|
||||
/// Tasks:
|
||||
/// - write xorg config
|
||||
/// - write modprobe config
|
||||
/// - rescan for devices
|
||||
/// + add drivers
|
||||
/// + or remove drivers and devices
|
||||
///
|
||||
/// The daemon needs direct access to this function when it detects that the
|
||||
pub fn do_vendor_tasks(
|
||||
vendor: GfxVendors,
|
||||
vfio_enable: bool,
|
||||
devices: &[GraphicsDevice],
|
||||
bus: &PciBus,
|
||||
) -> Result<(), RogError> {
|
||||
// Rescan before doing remove or add drivers
|
||||
bus.rescan()?;
|
||||
//
|
||||
Self::write_xorg_conf(vendor)?;
|
||||
// Write different modprobe to enable boot control to work
|
||||
Self::write_modprobe_conf(vendor, devices)?;
|
||||
|
||||
match vendor {
|
||||
GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Compute => {
|
||||
if vfio_enable {
|
||||
for driver in VFIO_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
}
|
||||
for driver in NVIDIA_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "modprobe")?;
|
||||
}
|
||||
}
|
||||
GfxVendors::Vfio => {
|
||||
if vfio_enable {
|
||||
Self::do_driver_action("nouveau", "rmmod")?;
|
||||
for driver in NVIDIA_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
Self::unbind_only(&devices)?;
|
||||
Self::do_driver_action("vfio-pci", "modprobe")?;
|
||||
} else {
|
||||
return Err(GfxError::VfioDisabled.into());
|
||||
}
|
||||
}
|
||||
GfxVendors::Integrated => {
|
||||
Self::do_driver_action("nouveau", "rmmod")?;
|
||||
if vfio_enable {
|
||||
for driver in VFIO_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
}
|
||||
for driver in NVIDIA_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
Self::unbind_remove_nvidia(&devices)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the user has any graphical uiser sessions that are active or online
|
||||
fn graphical_user_sessions_exist(
|
||||
connection: &Connection,
|
||||
sessions: &[SessionInfo],
|
||||
) -> Result<bool, RogError> {
|
||||
for session in sessions {
|
||||
let session_proxy = SessionProxy::new(connection, session)?;
|
||||
if session_proxy.get_class()? == SessionClass::User {
|
||||
match session_proxy.get_type()? {
|
||||
SessionType::X11 | SessionType::Wayland | SessionType::MIR => {
|
||||
match session_proxy.get_state()? {
|
||||
SessionState::Online | SessionState::Active => return Ok(true),
|
||||
SessionState::Closing | SessionState::Invalid => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Spools until all user sessions are ended then switches to requested mode
|
||||
fn fire_starter(
|
||||
vendor: GfxVendors,
|
||||
devices: Vec<GraphicsDevice>,
|
||||
bus: PciBus,
|
||||
thread_stop: mpsc::Receiver<bool>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
) -> Result<String, RogError> {
|
||||
info!("GFX: display-manager thread started");
|
||||
|
||||
const SLEEP_PERIOD: Duration = Duration::from_millis(100);
|
||||
let start_time = Instant::now();
|
||||
|
||||
let connection = Connection::new_system()?;
|
||||
let manager = ManagerProxy::new(&connection)?;
|
||||
let mut sessions = manager.list_sessions()?;
|
||||
|
||||
loop {
|
||||
let tmp = manager.list_sessions()?;
|
||||
if !tmp.iter().eq(&sessions) {
|
||||
info!("GFX thread: Sessions list changed");
|
||||
sessions = tmp;
|
||||
}
|
||||
|
||||
if !Self::graphical_user_sessions_exist(&connection, &sessions)? {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Ok(stop) = thread_stop.try_recv() {
|
||||
if stop {
|
||||
return Ok("Graphics mode change was cancelled".into());
|
||||
}
|
||||
}
|
||||
// exit if 3 minutes pass
|
||||
if Instant::now().duration_since(start_time).as_secs() > 180 {
|
||||
warn!("{}", THREAD_TIMEOUT_MSG);
|
||||
return Ok(THREAD_TIMEOUT_MSG.into());
|
||||
}
|
||||
|
||||
// Don't spin at max speed
|
||||
sleep(SLEEP_PERIOD);
|
||||
}
|
||||
|
||||
info!("GFX thread: all graphical user sessions ended, continuing");
|
||||
Self::do_display_manager_action("stop")?;
|
||||
Self::wait_display_manager_state("inactive")?;
|
||||
|
||||
let vfio_enable = if let Ok(config) = config.lock() {
|
||||
config.gfx_vfio_enable
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Self::do_vendor_tasks(vendor, vfio_enable, &devices, &bus)?;
|
||||
Self::do_display_manager_action("restart")?;
|
||||
// Save selected mode in case of reboot
|
||||
Self::save_gfx_mode(vendor, config);
|
||||
info!("GFX thread: display-manager started");
|
||||
|
||||
let v: &str = vendor.into();
|
||||
info!("GFX thread: Graphics mode changed to {} successfully", v);
|
||||
Ok(format!("Graphics mode changed to {} successfully", v))
|
||||
}
|
||||
|
||||
/// Before starting a new thread the old one *must* be cancelled
|
||||
fn cancel_thread(&self) {
|
||||
if let Ok(lock) = self.thread_kill.lock() {
|
||||
if let Some(tx) = lock.as_ref() {
|
||||
// Cancel the running thread
|
||||
info!("GFX: Cancelling previous thread");
|
||||
tx.send(true)
|
||||
.map_err(|err| {
|
||||
warn!("GFX thread: {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The thread is used only in cases where a logout is required
|
||||
fn setup_thread(&mut self, vendor: GfxVendors) {
|
||||
let config = self.config.clone();
|
||||
let devices = self.nvidia.clone();
|
||||
let bus = self.bus.clone();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
if let Ok(mut lock) = self.thread_kill.lock() {
|
||||
*lock = Some(tx);
|
||||
}
|
||||
let killer = self.thread_kill.clone();
|
||||
|
||||
let _join: JoinHandle<()> = std::thread::spawn(move || {
|
||||
Self::fire_starter(vendor, devices, bus, rx, config)
|
||||
.map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
})
|
||||
.ok();
|
||||
// clear the tx/rx when done
|
||||
if let Ok(mut lock) = killer.try_lock() {
|
||||
*lock = None;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Initiates a mode change by starting a thread that will wait until all
|
||||
/// graphical sessions are exited before performing the tasks required
|
||||
/// to switch modes.
|
||||
///
|
||||
/// For manually calling (not on boot/startup) via dbus
|
||||
pub fn set_gfx_config(
|
||||
&mut self,
|
||||
vendor: GfxVendors,
|
||||
) -> Result<GfxRequiredUserAction, RogError> {
|
||||
if let Ok(gsync) = CtrlRogBios::get_gfx_mode() {
|
||||
if gsync == 1 {
|
||||
return Err(GfxError::GsyncModeActive.into());
|
||||
}
|
||||
}
|
||||
|
||||
let vfio_enable = if let Ok(config) = self.config.lock() {
|
||||
config.gfx_vfio_enable
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if !vfio_enable && matches!(vendor, GfxVendors::Vfio) {
|
||||
return Err(GfxError::VfioDisabled.into());
|
||||
}
|
||||
|
||||
// Must always cancel any thread running
|
||||
self.cancel_thread();
|
||||
// determine which method we need here
|
||||
let action_required = self.logout_required(vendor);
|
||||
if matches!(action_required, GfxRequiredUserAction::Logout) {
|
||||
// Yeah need the thread to check if all users are logged out
|
||||
info!("GFX: mode change requires a logout to complete");
|
||||
self.setup_thread(vendor);
|
||||
} else {
|
||||
// Okay cool, we can switch on/off vfio
|
||||
info!("GFX: mode change does not require logout");
|
||||
let devices = self.nvidia.clone();
|
||||
let bus = self.bus.clone();
|
||||
Self::do_vendor_tasks(vendor, vfio_enable, &devices, &bus)?;
|
||||
info!("GFX: Graphics mode changed to {}", <&str>::from(vendor));
|
||||
}
|
||||
// TODO: undo if failed? Save last mode, catch errors...
|
||||
Ok(action_required)
|
||||
}
|
||||
|
||||
/// Used only on boot to set correct mode
|
||||
fn auto_power(&mut self) -> Result<(), RogError> {
|
||||
let vendor = self.get_gfx_mode()?;
|
||||
let devices = self.nvidia.clone();
|
||||
let bus = self.bus.clone();
|
||||
|
||||
let vfio_enable = if let Ok(config) = self.config.lock() {
|
||||
config.gfx_vfio_enable
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Self::do_vendor_tasks(vendor, vfio_enable, &devices, &bus)?;
|
||||
Self::toggle_fallback_service(vendor)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
pub mod error;
|
||||
|
||||
pub mod gfx;
|
||||
|
||||
pub mod system;
|
||||
|
||||
const NVIDIA_DRIVERS: [&str; 4] = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"];
|
||||
|
||||
const VFIO_DRIVERS: [&str; 5] = [
|
||||
"vfio-pci",
|
||||
"vfio_iommu_type1",
|
||||
"vfio_virqfd",
|
||||
"vfio_mdev",
|
||||
"vfio",
|
||||
];
|
||||
|
||||
const DISPLAY_MANAGER: &str = "display-manager.service";
|
||||
|
||||
const MODPROBE_PATH: &str = "/etc/modprobe.d/asusd.conf";
|
||||
|
||||
static MODPROBE_BASE: &[u8] = br#"# Automatically generated by asusd
|
||||
# If you have issues with i2c_nvidia_gpu, copy the 2 lines below to a
|
||||
# new blacklist file and uncomment
|
||||
#blacklist i2c_nvidia_gpu
|
||||
#alias i2c_nvidia_gpu off
|
||||
blacklist nouveau
|
||||
alias nouveau off
|
||||
options nvidia NVreg_DynamicPowerManagement=0x02
|
||||
options nvidia-drm modeset=1
|
||||
"#;
|
||||
|
||||
static MODPROBE_INTEGRATED: &[u8] = br#"# Automatically generated by asusd
|
||||
blacklist i2c_nvidia_gpu
|
||||
blacklist nvidia
|
||||
blacklist nvidia-drm
|
||||
blacklist nvidia-modeset
|
||||
blacklist nouveau
|
||||
alias nouveau off
|
||||
"#;
|
||||
|
||||
static MODPROBE_VFIO: &[u8] = br#"options vfio-pci ids="#;
|
||||
|
||||
const XORG_FILE: &str = "90-nvidia-primary.conf";
|
||||
const XORG_PATH: &str = "/etc/X11/xorg.conf.d/";
|
||||
|
||||
static PRIMARY_GPU_BEGIN: &[u8] = br#"# Automatically generated by asusd
|
||||
Section "OutputClass"
|
||||
Identifier "nvidia"
|
||||
MatchDriver "nvidia-drm"
|
||||
Driver "nvidia"
|
||||
Option "AllowEmptyInitialConfiguration" "true""#;
|
||||
|
||||
static PRIMARY_GPU_NVIDIA: &[u8] = br#"
|
||||
Option "PrimaryGPU" "true""#;
|
||||
|
||||
static PRIMARY_GPU_END: &[u8] = br#"
|
||||
EndSection"#;
|
||||
@@ -1,160 +0,0 @@
|
||||
use log::{error, info, warn};
|
||||
use std::fs::read_to_string;
|
||||
use std::{fs::write, io, path::PathBuf};
|
||||
use sysfs_class::{PciDevice, SysClass};
|
||||
|
||||
pub struct Module {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
fn parse(line: &str) -> io::Result<Module> {
|
||||
let mut parts = line.split(' ');
|
||||
|
||||
let name = parts
|
||||
.next()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "module name not found"))?;
|
||||
|
||||
Ok(Module {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Module>> {
|
||||
let mut modules = Vec::new();
|
||||
|
||||
let data = read_to_string("/proc/modules")?;
|
||||
for line in data.lines() {
|
||||
let module = Module::parse(line)?;
|
||||
modules.push(module);
|
||||
}
|
||||
|
||||
Ok(modules)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PciBus {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl PciBus {
|
||||
pub fn new() -> io::Result<PciBus> {
|
||||
let path = PathBuf::from("/sys/bus/pci");
|
||||
if path.is_dir() {
|
||||
Ok(PciBus { path })
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"pci directory not found",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Will rescan the device tree, which adds all removed devices back
|
||||
pub fn rescan(&self) -> io::Result<()> {
|
||||
write(self.path.join("rescan"), "1")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GraphicsDevice {
|
||||
_id: String,
|
||||
functions: Vec<PciDevice>,
|
||||
}
|
||||
|
||||
impl GraphicsDevice {
|
||||
pub fn new(id: String, functions: Vec<PciDevice>) -> GraphicsDevice {
|
||||
GraphicsDevice { _id: id, functions }
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
self.functions.iter().any(|func| func.path().exists())
|
||||
}
|
||||
|
||||
pub fn functions(&self) -> &[PciDevice] {
|
||||
&self.functions
|
||||
}
|
||||
|
||||
pub fn unbind(&self) -> Result<(), std::io::Error> {
|
||||
for func in self.functions.iter() {
|
||||
if func.path().exists() {
|
||||
match func.driver() {
|
||||
Ok(driver) => {
|
||||
info!("{}: Unbinding {}", driver.id(), func.id());
|
||||
unsafe {
|
||||
driver.unbind(&func).map_err(|err| {
|
||||
error!("gfx unbind: {}", err);
|
||||
err
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Err(err) => match err.kind() {
|
||||
io::ErrorKind::NotFound => (),
|
||||
_ => {
|
||||
error!("gfx driver: {:?}, {}", func.path(), err);
|
||||
return Err(err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rebind(&self) -> Result<(), std::io::Error> {
|
||||
for func in self.functions.iter() {
|
||||
if func.path().exists() {
|
||||
match func.driver() {
|
||||
Ok(driver) => {
|
||||
info!("{}: Binding {}", driver.id(), func.id());
|
||||
unsafe {
|
||||
driver.bind(&func).map_err(|err| {
|
||||
error!("gfx bind: {}", err);
|
||||
err
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Err(err) => match err.kind() {
|
||||
io::ErrorKind::NotFound => (),
|
||||
_ => {
|
||||
error!("gfx driver: {:?}, {}", func.path(), err);
|
||||
return Err(err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&self) -> Result<(), std::io::Error> {
|
||||
for func in self.functions.iter() {
|
||||
if func.path().exists() {
|
||||
match func.driver() {
|
||||
Ok(driver) => {
|
||||
error!("{}: in use by {}", func.id(), driver.id());
|
||||
}
|
||||
Err(why) => match why.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
info!("{}: Removing", func.id());
|
||||
unsafe {
|
||||
// ignore errors and carry on
|
||||
if let Err(err) = func.remove() {
|
||||
error!("gfx remove: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Remove device failed");
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
warn!("{}: Already removed", func.id());
|
||||
}
|
||||
}
|
||||
info!("Removed all gfx devices");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,451 +0,0 @@
|
||||
// Only these two packets must be 17 bytes
|
||||
static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
|
||||
|
||||
use crate::{
|
||||
config_aura::AuraConfig,
|
||||
error::RogError,
|
||||
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
|
||||
};
|
||||
use log::{error, info, warn};
|
||||
use rog_types::{LED_MSG_LEN, aura_modes::{AuraEffect, AuraModeNum, LedBrightness}, supported::LedSupportedFunctions};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
use crate::GetSupported;
|
||||
|
||||
impl GetSupported for CtrlKbdBacklight {
|
||||
type A = LedSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
// let mode = <&str>::from(&<AuraModes>::from(*mode));
|
||||
let multizone_led_mode = false;
|
||||
let per_key_led_mode = false;
|
||||
let laptop = LaptopLedData::get_data();
|
||||
let stock_led_modes = if laptop.standard.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(laptop.standard)
|
||||
};
|
||||
|
||||
LedSupportedFunctions {
|
||||
brightness_set: CtrlKbdBacklight::get_kbd_bright_path().is_some(),
|
||||
stock_led_modes,
|
||||
multizone_led_mode,
|
||||
per_key_led_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdBacklight {
|
||||
led_node: Option<String>,
|
||||
pub bright_node: String,
|
||||
supported_modes: LaptopLedData,
|
||||
flip_effect_write: bool,
|
||||
config: AuraConfig,
|
||||
}
|
||||
|
||||
pub struct DbusKbdBacklight {
|
||||
inner: Arc<Mutex<CtrlKbdBacklight>>,
|
||||
}
|
||||
|
||||
impl DbusKbdBacklight {
|
||||
pub fn new(inner: Arc<Mutex<CtrlKbdBacklight>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for DbusKbdBacklight {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Led"), self)
|
||||
.map_err(|err| {
|
||||
error!("DbusKbdBacklight: add_to_server {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// The main interface for changing, reading, or notfying signals
|
||||
///
|
||||
/// LED commands are split between Brightness, Modes, Per-Key
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl DbusKbdBacklight {
|
||||
fn set_brightness(&mut self, brightness: LedBrightness) {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
ctrl.set_brightness(brightness)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_led_mode(&mut self, effect: AuraEffect) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
let mode_name = effect.mode_name();
|
||||
match ctrl.do_command(effect) {
|
||||
Ok(_) => {
|
||||
self.notify_led(&mode_name).ok();
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_led_mode(&self) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.toggle_mode(false)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
self.notify_led(&json)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_led_mode(&self) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.toggle_mode(true)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
self.notify_led(&json)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the current mode data
|
||||
#[dbus_interface(property)]
|
||||
fn led_mode(&self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not deserialise".to_string()
|
||||
}
|
||||
|
||||
/// Return a list of available modes
|
||||
#[dbus_interface(property)]
|
||||
fn led_modes(&self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not serialise".to_string()
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
#[dbus_interface(property)]
|
||||
fn led_brightness(&self) -> i8 {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
return ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1);
|
||||
}
|
||||
warn!("SetKeyBacklight could not serialise");
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_led(&self, data: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlKbdBacklight {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
// set current mode (if any)
|
||||
if self.supported_modes.standard.len() > 1 {
|
||||
let current_mode = self.config.current_mode;
|
||||
if self.supported_modes.standard.contains(&(current_mode)) {
|
||||
let mode = self
|
||||
.config
|
||||
.builtins
|
||||
.get(¤t_mode)
|
||||
.ok_or(RogError::NotSupported)?
|
||||
.to_owned();
|
||||
self.write_mode(&mode)?;
|
||||
info!("Reloaded last used mode");
|
||||
} else {
|
||||
warn!(
|
||||
"An unsupported mode was set: {}, reset to first mode available",
|
||||
<&str>::from(&self.config.current_mode)
|
||||
);
|
||||
self.config.builtins.remove(¤t_mode);
|
||||
self.config.current_mode = AuraModeNum::Static;
|
||||
// TODO: do a recursive call with a boxed dyn future later
|
||||
let mode = self
|
||||
.config
|
||||
.builtins
|
||||
.get(¤t_mode)
|
||||
.ok_or(RogError::NotSupported)?
|
||||
.to_owned();
|
||||
self.write_mode(&mode)?;
|
||||
info!("Reloaded last used mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Reload brightness
|
||||
let bright = self.config.brightness;
|
||||
self.set_brightness(bright)?;
|
||||
info!("Reloaded last used brightness");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::CtrlTask for CtrlKbdBacklight {
|
||||
fn do_task(&mut self) -> Result<(), RogError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&self.bright_node)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&self.bright_node).into(), err),
|
||||
})?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if self.config.brightness != num.into() {
|
||||
self.config.read();
|
||||
self.config.brightness = num.into();
|
||||
self.config.write();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Err(RogError::ParseLed)
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlKbdBacklight {
|
||||
#[inline]
|
||||
pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> {
|
||||
// TODO: return error if *all* nodes are None
|
||||
let mut led_node = None;
|
||||
for prod in ASUS_KEYBOARD_DEVICES.iter() {
|
||||
match Self::find_led_node(prod) {
|
||||
Ok(node) => {
|
||||
led_node = Some(node);
|
||||
break;
|
||||
}
|
||||
Err(err) => warn!("led_node: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
let bright_node = Self::get_kbd_bright_path();
|
||||
|
||||
if led_node.is_none() && bright_node.is_none() {
|
||||
return Err(RogError::MissingFunction(
|
||||
"All keyboard features missing, you may require a v5.11 series kernel or newer"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
|
||||
if bright_node.is_none() {
|
||||
return Err(RogError::MissingFunction(
|
||||
"No brightness control, you may require a v5.11 series kernel or newer".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let ctrl = CtrlKbdBacklight {
|
||||
led_node,
|
||||
bright_node: bright_node.unwrap(), // If was none then we already returned above
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
config,
|
||||
};
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn get_kbd_bright_path() -> Option<String> {
|
||||
if Path::new(KBD_BRIGHT_PATH).exists() {
|
||||
return Some(KBD_BRIGHT_PATH.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_brightness(&self) -> Result<u8, RogError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&self.bright_node)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&self.bright_node).into(), err),
|
||||
})?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
|
||||
pub fn set_brightness(&self, brightness: LedBrightness) -> Result<(), RogError> {
|
||||
let path = Path::new(&self.bright_node);
|
||||
let mut file =
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&self.bright_node).into(), err),
|
||||
})?;
|
||||
file.write_all(&[brightness.as_char_code()])
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_led_node(id_product: &str) -> Result<String, RogError> {
|
||||
let mut enumerator = udev::Enumerator::new().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("enumerator failed".into(), err)
|
||||
})?;
|
||||
enumerator.match_subsystem("hidraw").map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("match_subsystem failed".into(), err)
|
||||
})?;
|
||||
|
||||
for device in enumerator.scan_devices().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("scan_devices failed".into(), err)
|
||||
})? {
|
||||
if let Some(parent) = device
|
||||
.parent_with_subsystem_devtype("usb", "usb_device")
|
||||
.map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("parent_with_subsystem_devtype failed".into(), err)
|
||||
})?
|
||||
{
|
||||
if parent
|
||||
.attribute_value("idProduct")
|
||||
.ok_or_else(|| RogError::NotFound("LED idProduct".into()))?
|
||||
== id_product
|
||||
{
|
||||
if let Some(dev_node) = device.devnode() {
|
||||
info!("Using device at: {:?} for LED control", dev_node);
|
||||
return Ok(dev_node.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(RogError::MissingFunction(
|
||||
"ASUS LED device node not found".into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn do_command(&mut self, mode: AuraEffect) -> Result<(), RogError> {
|
||||
self.set_and_save(mode)
|
||||
}
|
||||
|
||||
/// Should only be used if the bytes you are writing are verified correct
|
||||
#[inline]
|
||||
fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
|
||||
if let Some(led_node) = &self.led_node {
|
||||
if let Ok(mut file) = OpenOptions::new().write(true).open(led_node) {
|
||||
// println!("write: {:02x?}", &message);
|
||||
return file
|
||||
.write_all(message)
|
||||
.map_err(|err| RogError::Write("write_bytes".into(), err));
|
||||
}
|
||||
}
|
||||
Err(RogError::NotSupported)
|
||||
}
|
||||
|
||||
/// Write an effect block
|
||||
#[inline]
|
||||
fn write_effect(&mut self, effect: &[Vec<u8>]) -> Result<(), RogError> {
|
||||
if self.flip_effect_write {
|
||||
for row in effect.iter().rev() {
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
} else {
|
||||
for row in effect.iter() {
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
}
|
||||
self.flip_effect_write = !self.flip_effect_write;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used to set a builtin mode and save the settings for it
|
||||
///
|
||||
/// This needs to be universal so that settings applied by dbus stick
|
||||
#[inline]
|
||||
fn set_and_save(&mut self, mode: AuraEffect) -> Result<(), RogError> {
|
||||
self.config.read();
|
||||
self.write_mode(&mode)?;
|
||||
self.config.current_mode = *mode.mode();
|
||||
self.config.set_builtin(mode);
|
||||
self.config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
|
||||
let current = self.config.current_mode;
|
||||
if let Some(idx) = self
|
||||
.supported_modes
|
||||
.standard
|
||||
.iter()
|
||||
.position(|v| *v == current)
|
||||
{
|
||||
let mut idx = idx;
|
||||
// goes past end of array
|
||||
if reverse {
|
||||
if idx == 0 {
|
||||
idx = self.supported_modes.standard.len() - 1;
|
||||
} else {
|
||||
idx -= 1;
|
||||
}
|
||||
} else {
|
||||
idx += 1;
|
||||
if idx == self.supported_modes.standard.len() {
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
let next = self.supported_modes.standard[idx];
|
||||
|
||||
self.config.read();
|
||||
if let Some(data) = self.config.builtins.get(&next) {
|
||||
self.write_mode(&data)?;
|
||||
self.config.current_mode = next;
|
||||
}
|
||||
self.config.write();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_mode(&self, mode: &AuraEffect) -> Result<(), RogError> {
|
||||
if !self.supported_modes.standard.contains(&mode.mode()) {
|
||||
return Err(RogError::NotSupported);
|
||||
}
|
||||
let bytes: [u8; LED_MSG_LEN] = mode.into();
|
||||
self.write_bytes(&bytes)?;
|
||||
self.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
101
daemon/src/ctrl_profiles/config.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use log::{error, warn};
|
||||
use rog_profiles::{FanCurveProfiles, Profile};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct ProfileConfig {
|
||||
#[serde(skip)]
|
||||
config_path: String,
|
||||
/// For restore on boot
|
||||
pub active_profile: Profile,
|
||||
/// States to restore
|
||||
pub fan_curves: Option<FanCurveProfiles>,
|
||||
}
|
||||
|
||||
impl ProfileConfig {
|
||||
fn new(config_path: String) -> Self {
|
||||
Self {
|
||||
config_path,
|
||||
active_profile: Profile::Balanced,
|
||||
fan_curves: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_defaults_and_save(&mut self) {
|
||||
self.active_profile = Profile::get_active_profile().unwrap_or(Profile::Balanced);
|
||||
if let Ok(res) = FanCurveProfiles::is_supported() {
|
||||
if res {
|
||||
let curves = FanCurveProfiles::default();
|
||||
self.fan_curves = Some(curves);
|
||||
}
|
||||
}
|
||||
self.write();
|
||||
}
|
||||
|
||||
pub fn load(config_path: String) -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&config_path)
|
||||
.unwrap_or_else(|_| panic!("The directory /etc/asusd/ is missing")); // okay to cause panic here
|
||||
let mut buf = String::new();
|
||||
let mut config;
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
config = Self::new(config_path);
|
||||
config.set_defaults_and_save();
|
||||
} else if let Ok(data) = toml::from_str(&buf) {
|
||||
config = data;
|
||||
config.config_path = config_path;
|
||||
} else {
|
||||
warn!(
|
||||
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
|
||||
config_path, config_path
|
||||
);
|
||||
let cfg_old = config_path.clone() + "-old";
|
||||
std::fs::rename(config_path.clone(), cfg_old).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
config_path, err
|
||||
)
|
||||
});
|
||||
config = Self::new(config_path);
|
||||
config.set_defaults_and_save();
|
||||
}
|
||||
} else {
|
||||
config = Self::new(config_path);
|
||||
config.set_defaults_and_save();
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&self.config_path)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", self.config_path, err));
|
||||
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("File is empty {}", self.config_path);
|
||||
} else {
|
||||
let mut data: ProfileConfig = toml::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", self.config_path));
|
||||
// copy over serde skipped values
|
||||
data.config_path = self.config_path.clone();
|
||||
*self = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(&self.config_path).expect("Couldn't overwrite config");
|
||||
let data = toml::to_string(self).expect("Parse config to toml failed");
|
||||
file.write_all(data.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
}
|
||||
157
daemon/src/ctrl_profiles/controller.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::error::RogError;
|
||||
use crate::{CtrlTask, GetSupported};
|
||||
use async_trait::async_trait;
|
||||
use log::{info, warn};
|
||||
use rog_profiles::error::ProfileError;
|
||||
use rog_profiles::{FanCurveProfiles, Profile};
|
||||
use rog_supported::PlatformProfileFunctions;
|
||||
use smol::Executor;
|
||||
|
||||
use super::config::ProfileConfig;
|
||||
|
||||
pub struct CtrlPlatformProfile {
|
||||
pub config: ProfileConfig,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlPlatformProfile {
|
||||
type A = PlatformProfileFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
if !Profile::is_platform_profile_supported() {
|
||||
warn!(
|
||||
r#"
|
||||
platform_profile kernel interface not found, your laptop does not support this, or the interface is missing.
|
||||
To enable profile support you require a kernel version 5.15.2 minimum.
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
let res = FanCurveProfiles::is_supported();
|
||||
let mut fan_curve_supported = res.is_err();
|
||||
if let Ok(r) = res {
|
||||
fan_curve_supported = r;
|
||||
};
|
||||
|
||||
if !fan_curve_supported {
|
||||
info!(
|
||||
r#"
|
||||
fan curves kernel interface not found, your laptop does not support this, or the interface is missing.
|
||||
To enable fan-curve support you require a kernel with the following patch applied:
|
||||
https://lkml.org/lkml/2021/10/23/250
|
||||
This patch has been accepted upstream for 5.17 kernel release.
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
PlatformProfileFunctions {
|
||||
platform_profile: Profile::is_platform_profile_supported(),
|
||||
fan_curves: fan_curve_supported,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlPlatformProfile {
|
||||
/// Fetch the active profile and use that to set all related components up
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Some(curves) = &mut self.config.fan_curves {
|
||||
if let Ok(mut device) = FanCurveProfiles::get_device() {
|
||||
// There is a possibility that the curve was default zeroed, so this call initialises
|
||||
// the data from system read and we need to save it after
|
||||
curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?;
|
||||
self.config.write();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlPlatformProfile {
|
||||
pub fn new(config: ProfileConfig) -> Result<Self, RogError> {
|
||||
if Profile::is_platform_profile_supported() {
|
||||
info!("Device has profile control available");
|
||||
|
||||
if FanCurveProfiles::get_device().is_ok() {
|
||||
info!("Device has fan curves available");
|
||||
}
|
||||
|
||||
return Ok(CtrlPlatformProfile { config });
|
||||
}
|
||||
|
||||
Err(ProfileError::NotSupported.into())
|
||||
}
|
||||
|
||||
pub fn save_config(&self) {
|
||||
self.config.write();
|
||||
}
|
||||
|
||||
/// Toggle to next profile in list. This will first read the config, switch, then write out
|
||||
pub(super) fn set_next_profile(&mut self) -> Result<(), RogError> {
|
||||
// Read first just incase the user has modified the config before calling this
|
||||
match self.config.active_profile {
|
||||
Profile::Balanced => {
|
||||
Profile::set_profile(Profile::Performance)?;
|
||||
self.config.active_profile = Profile::Performance;
|
||||
}
|
||||
Profile::Performance => {
|
||||
Profile::set_profile(Profile::Quiet)?;
|
||||
self.config.active_profile = Profile::Quiet;
|
||||
}
|
||||
Profile::Quiet => {
|
||||
Profile::set_profile(Profile::Balanced)?;
|
||||
self.config.active_profile = Profile::Balanced;
|
||||
}
|
||||
}
|
||||
self.write_profile_curve_to_platform()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the curve for the active profile active
|
||||
pub(super) fn write_profile_curve_to_platform(&mut self) -> Result<(), RogError> {
|
||||
if let Some(curves) = &mut self.config.fan_curves {
|
||||
if let Ok(mut device) = FanCurveProfiles::get_device() {
|
||||
curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn set_active_curve_to_defaults(&mut self) -> Result<(), RogError> {
|
||||
if let Some(curves) = self.config.fan_curves.as_mut() {
|
||||
if let Ok(mut device) = FanCurveProfiles::get_device() {
|
||||
curves.set_active_curve_to_defaults(self.config.active_profile, &mut device)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlProfileTask {
|
||||
ctrl: Arc<Mutex<CtrlPlatformProfile>>,
|
||||
}
|
||||
|
||||
impl CtrlProfileTask {
|
||||
pub fn new(ctrl: Arc<Mutex<CtrlPlatformProfile>>) -> Self {
|
||||
Self { ctrl }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlProfileTask {
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let ctrl = self.ctrl.clone();
|
||||
self.repeating_task(666, executor, move || {
|
||||
if let Ok(ref mut lock) = ctrl.try_lock() {
|
||||
let new_profile = Profile::get_active_profile().unwrap();
|
||||
if new_profile != lock.config.active_profile {
|
||||
lock.config.active_profile = new_profile;
|
||||
lock.write_profile_curve_to_platform().unwrap();
|
||||
lock.save_config();
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
3
daemon/src/ctrl_profiles/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod config;
|
||||
pub mod controller;
|
||||
pub mod zbus;
|
||||
187
daemon/src/ctrl_profiles/zbus.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
use async_trait::async_trait;
|
||||
use log::warn;
|
||||
use rog_profiles::fan_curve_set::CurveData;
|
||||
use rog_profiles::fan_curve_set::FanCurveSet;
|
||||
use rog_profiles::Profile;
|
||||
use zbus::Connection;
|
||||
use zbus::SignalContext;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::{dbus_interface, fdo::Error};
|
||||
|
||||
use super::controller::CtrlPlatformProfile;
|
||||
|
||||
static UNSUPPORTED_MSG: &str =
|
||||
"Fan curves are not supported on this laptop or you require a patched kernel";
|
||||
|
||||
pub struct ProfileZbus {
|
||||
inner: Arc<Mutex<CtrlPlatformProfile>>,
|
||||
}
|
||||
|
||||
impl ProfileZbus {
|
||||
pub fn new(inner: Arc<Mutex<CtrlPlatformProfile>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl ProfileZbus {
|
||||
/// Fetch profile names
|
||||
fn profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
|
||||
if let Ok(profiles) = Profile::get_profile_names() {
|
||||
return Ok(profiles);
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get all profile details".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Toggle to next platform_profile. Names provided by `Profiles`.
|
||||
/// If fan-curves are supported will also activate a fan curve for profile.
|
||||
async fn next_profile(&mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
|
||||
let mut profile = None;
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.set_next_profile()
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
ctrl.save_config();
|
||||
profile = Some(ctrl.config.active_profile);
|
||||
}
|
||||
if let Some(profile) = profile {
|
||||
Self::notify_profile(&ctxt, profile).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the active profile name
|
||||
fn active_profile(&mut self) -> zbus::fdo::Result<Profile> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
return Ok(ctrl.config.active_profile);
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get active profile name".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Set this platform_profile name as active
|
||||
async fn set_active_profile(
|
||||
&self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
profile: Profile,
|
||||
) {
|
||||
let mut tmp = None;
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
// Read first just incase the user has modified the config before calling this
|
||||
ctrl.config.read();
|
||||
Profile::set_profile(profile)
|
||||
.map_err(|e| warn!("set_profile, {}", e))
|
||||
.ok();
|
||||
ctrl.config.active_profile = profile;
|
||||
ctrl.write_profile_curve_to_platform()
|
||||
.map_err(|e| warn!("write_profile_curve_to_platform, {}", e))
|
||||
.ok();
|
||||
|
||||
ctrl.save_config();
|
||||
tmp = Some(ctrl.config.active_profile);
|
||||
}
|
||||
if let Some(profile) = tmp {
|
||||
Self::notify_profile(&ctxt, profile).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a list of profiles that have fan-curves enabled.
|
||||
fn enabled_fan_profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
if let Some(curves) = &ctrl.config.fan_curves {
|
||||
return Ok(curves.get_enabled_curve_profiles().to_vec());
|
||||
}
|
||||
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get enabled fan curve names".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Set a profile fan curve enabled status. Will also activate a fan curve if in the
|
||||
/// same profile mode
|
||||
fn set_fan_curve_enabled(&mut self, profile: Profile, enabled: bool) -> zbus::fdo::Result<()> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
return if let Some(curves) = &mut ctrl.config.fan_curves {
|
||||
curves.set_profile_curve_enabled(profile, enabled);
|
||||
|
||||
ctrl.write_profile_curve_to_platform()
|
||||
.map_err(|e| warn!("write_profile_curve_to_platform, {}", e))
|
||||
.ok();
|
||||
|
||||
ctrl.save_config();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Failed(UNSUPPORTED_MSG.to_string()))
|
||||
};
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get enabled fan curve names".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Get the fan-curve data for the currently active Profile
|
||||
fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result<FanCurveSet> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
if let Some(curves) = &ctrl.config.fan_curves {
|
||||
let curve = curves.get_fan_curves_for(profile);
|
||||
return Ok(curve.clone());
|
||||
}
|
||||
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
|
||||
}
|
||||
Err(Error::Failed("Failed to get fan curve data".to_string()))
|
||||
}
|
||||
|
||||
/// Set the fan curve for the specified profile.
|
||||
/// Will also activate the fan curve if the user is in the same mode.
|
||||
fn set_fan_curve(&self, profile: Profile, curve: CurveData) -> zbus::fdo::Result<()> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
if let Some(curves) = &mut ctrl.config.fan_curves {
|
||||
curves
|
||||
.save_fan_curve(curve, profile)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
} else {
|
||||
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
|
||||
}
|
||||
ctrl.write_profile_curve_to_platform()
|
||||
.map_err(|e| warn!("Profile::set_profile, {}", e))
|
||||
.ok();
|
||||
ctrl.save_config();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset the stored (self) and device curve to the defaults of the platform.
|
||||
///
|
||||
/// Each platform_profile has a different default and the defualt can be read
|
||||
/// only for the currently active profile.
|
||||
fn set_active_curve_to_defaults(&self) -> zbus::fdo::Result<()> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
ctrl.set_active_curve_to_defaults()
|
||||
.map_err(|e| warn!("Profile::set_active_curve_to_defaults, {}", e))
|
||||
.ok();
|
||||
ctrl.save_config();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_profile(signal_ctxt: &SignalContext<'_>, profile: Profile) -> zbus::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for ProfileZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Profile", server).await;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use rog_types::supported::RogBiosSupportedFunctions;
|
||||
use rog_supported::RogBiosSupportedFunctions;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufRead;
|
||||
use std::io::{Read, Write};
|
||||
@@ -8,8 +9,8 @@ use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
use zbus::Connection;
|
||||
use zbus::{dbus_interface, SignalContext};
|
||||
|
||||
const INITRAMFS_PATH: &str = "/usr/sbin/update-initramfs";
|
||||
const DRACUT_PATH: &str = "/usr/bin/dracut";
|
||||
@@ -18,6 +19,9 @@ static ASUS_SWITCH_GRAPHIC_MODE: &str =
|
||||
"/sys/firmware/efi/efivars/AsusSwitchGraphicMode-607005d5-3f75-4b2e-98f0-85ba66797a3e";
|
||||
static ASUS_POST_LOGO_SOUND: &str =
|
||||
"/sys/firmware/efi/efivars/AsusPostLogoSound-607005d5-3f75-4b2e-98f0-85ba66797a3e";
|
||||
static ASUS_PANEL_OD_PATH: &str = "/sys/devices/platform/asus-nb-wmi/panel_od";
|
||||
static ASUS_DGPU_DISABLE_PATH: &str = "/sys/devices/platform/asus-nb-wmi/dgpu_disable";
|
||||
static ASUS_EGPU_ENABLE_PATH: &str = "/sys/devices/platform/asus-nb-wmi/egpu_enable";
|
||||
|
||||
pub struct CtrlRogBios {
|
||||
_config: Arc<Mutex<Config>>,
|
||||
@@ -28,30 +32,34 @@ impl GetSupported for CtrlRogBios {
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
RogBiosSupportedFunctions {
|
||||
post_sound_toggle: CtrlRogBios::check_path_exists(ASUS_POST_LOGO_SOUND).is_ok(),
|
||||
dedicated_gfx_toggle: CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE).is_ok(),
|
||||
post_sound: Path::new(ASUS_POST_LOGO_SOUND).exists(),
|
||||
dedicated_gfx: Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists(),
|
||||
panel_overdrive: Path::new(ASUS_PANEL_OD_PATH).exists(),
|
||||
dgpu_disable: Path::new(ASUS_DGPU_DISABLE_PATH).exists(),
|
||||
egpu_enable: Path::new(ASUS_EGPU_ENABLE_PATH).exists(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlRogBios {
|
||||
pub fn set_dedicated_graphic_mode(&mut self, dedicated: bool) {
|
||||
async fn set_dedicated_graphic_mode(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
dedicated: bool,
|
||||
) {
|
||||
self.set_gfx_mode(dedicated)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_asus_switch_graphic_mode {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
self.notify_dedicated_graphic_mode(dedicated)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: notify_asus_switch_graphic_mode {}", err);
|
||||
err
|
||||
})
|
||||
Self::notify_dedicated_graphic_mode(&ctxt, dedicated)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn dedicated_graphic_mode(&self) -> i8 {
|
||||
fn dedicated_graphic_mode(&self) -> i8 {
|
||||
Self::get_gfx_mode()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_gfx_mode {}", err);
|
||||
@@ -61,26 +69,27 @@ impl CtrlRogBios {
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
pub fn notify_dedicated_graphic_mode(&self, dedicated: bool) -> zbus::Result<()> {}
|
||||
async fn notify_dedicated_graphic_mode(
|
||||
signal_ctxt: &SignalContext<'_>,
|
||||
dedicated: bool,
|
||||
) -> zbus::Result<()> {
|
||||
}
|
||||
|
||||
// // // // // // // // // //
|
||||
|
||||
pub fn set_post_boot_sound(&mut self, on: bool) {
|
||||
async fn set_post_boot_sound(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
on: bool,
|
||||
) {
|
||||
Self::set_boot_sound(on)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_post_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
self.notify_post_boot_sound(on)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: notify_post_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
Self::notify_post_boot_sound(&ctxt, on).await.ok();
|
||||
}
|
||||
|
||||
pub fn post_boot_sound(&self) -> i8 {
|
||||
fn post_boot_sound(&self) -> i8 {
|
||||
Self::get_boot_sound()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_boot_sound {}", err);
|
||||
@@ -90,21 +99,59 @@ impl CtrlRogBios {
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
pub fn notify_post_boot_sound(&self, dedicated: bool) -> zbus::Result<()> {}
|
||||
}
|
||||
async fn notify_post_boot_sound(ctxt: &SignalContext<'_>, on: bool) -> zbus::Result<()> {}
|
||||
|
||||
impl crate::ZbusAdd for CtrlRogBios {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/RogBios"),
|
||||
self,
|
||||
)
|
||||
async fn set_panel_overdrive(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
overdrive: bool,
|
||||
) {
|
||||
if self
|
||||
.set_panel_od(overdrive)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: add_to_server {}", err);
|
||||
warn!("CtrlRogBios: set_panel_overdrive {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
.is_ok()
|
||||
{
|
||||
Self::notify_panel_overdrive(&ctxt, overdrive).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn panel_overdrive(&self) -> i8 {
|
||||
let path = ASUS_PANEL_OD_PATH;
|
||||
if let Ok(mut file) = OpenOptions::new().read(true).open(path).map_err(|err| {
|
||||
warn!("CtrlRogBios: panel_overdrive {}", err);
|
||||
err
|
||||
}) {
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_panel_overdrive {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
|
||||
if buf.len() >= 1 {
|
||||
let tmp = String::from_utf8_lossy(&buf[0..1]);
|
||||
return tmp.parse::<i8>().unwrap_or(-1);
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_panel_overdrive(
|
||||
signal_ctxt: &SignalContext<'_>,
|
||||
overdrive: bool,
|
||||
) -> zbus::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlRogBios {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/RogBios", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,22 +163,17 @@ impl crate::Reloadable for CtrlRogBios {
|
||||
|
||||
impl CtrlRogBios {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
match CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE) {
|
||||
Ok(_) => {
|
||||
CtrlRogBios::set_path_mutable(ASUS_SWITCH_GRAPHIC_MODE)?;
|
||||
}
|
||||
Err(err) => {
|
||||
info!("ROG Switchable Graphics (bios) not detected: {}", err);
|
||||
}
|
||||
if Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists() {
|
||||
CtrlRogBios::set_path_mutable(ASUS_SWITCH_GRAPHIC_MODE)?;
|
||||
} else {
|
||||
info!("G-Sync Switchable Graphics not detected");
|
||||
info!("If your laptop is not a G-Sync enabled laptop then you can ignore this. Standard graphics switching will still work.");
|
||||
}
|
||||
|
||||
match CtrlRogBios::check_path_exists(ASUS_POST_LOGO_SOUND) {
|
||||
Ok(_) => {
|
||||
CtrlRogBios::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
|
||||
}
|
||||
Err(err) => {
|
||||
info!("ROG boot sound toggle (bios) not detected: {}", err);
|
||||
}
|
||||
if Path::new(ASUS_POST_LOGO_SOUND).exists() {
|
||||
CtrlRogBios::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
|
||||
} else {
|
||||
info!("Switch for POST boot sound not detected");
|
||||
}
|
||||
|
||||
Ok(CtrlRogBios { _config: config })
|
||||
@@ -147,19 +189,8 @@ impl CtrlRogBios {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_path_exists(path: &str) -> Result<(), RogError> {
|
||||
if Path::new(path).exists() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RogError::MissingFunction(path.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_dedicated_gfx_toggle() -> bool {
|
||||
if CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE).is_ok() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists()
|
||||
}
|
||||
|
||||
pub fn get_gfx_mode() -> Result<i8, RogError> {
|
||||
@@ -186,7 +217,7 @@ impl CtrlRogBios {
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data).unwrap();
|
||||
file.read_to_end(&mut data)?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
if dedicated {
|
||||
@@ -200,17 +231,6 @@ impl CtrlRogBios {
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
self.update_initramfs(dedicated)?;
|
||||
|
||||
// if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
|
||||
// if let Ok(vendor) = CtrlGraphics::get_vendor() {
|
||||
// if ded == 1 && vendor != "nvidia" {
|
||||
// warn!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
|
||||
// CtrlGraphics::set_gfx_config(&GfxVendors::Nvidia)
|
||||
// .unwrap_or_else(|err| warn!("Gfx controller: {}", err));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -298,7 +318,7 @@ impl CtrlRogBios {
|
||||
RogError::Write(module_include.to_string_lossy().to_string(), err)
|
||||
})?;
|
||||
// add nvidia modules to module_include
|
||||
file.write_all(modules.concat().as_bytes()).unwrap();
|
||||
file.write_all(modules.concat().as_bytes())?;
|
||||
} else {
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
@@ -309,11 +329,9 @@ impl CtrlRogBios {
|
||||
|
||||
let mut buf = Vec::new();
|
||||
// remove modules
|
||||
for line in std::io::BufReader::new(file).lines() {
|
||||
if let Ok(l) = line {
|
||||
if !modules.contains(&l.as_ref()) {
|
||||
buf.append(&mut l.as_bytes().to_vec());
|
||||
}
|
||||
for line in std::io::BufReader::new(file).lines().flatten() {
|
||||
if !modules.contains(&line.as_str()) {
|
||||
buf.append(&mut line.as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +341,7 @@ impl CtrlRogBios {
|
||||
.map_err(|err| {
|
||||
RogError::Write(module_include.to_string_lossy().to_string(), err)
|
||||
})?;
|
||||
std::io::BufWriter::new(file).write_all(&buf).unwrap();
|
||||
std::io::BufWriter::new(file).write_all(&buf)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,4 +357,43 @@ impl CtrlRogBios {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_panel_od(&mut self, overdrive: bool) -> Result<(), RogError> {
|
||||
let path = ASUS_PANEL_OD_PATH;
|
||||
let mut file = OpenOptions::new().write(true).open(path).map_err(|err| {
|
||||
warn!("CtrlRogBios: set_panel_overdrive {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
let s = if overdrive { '1' } else { '0' };
|
||||
file.write(&[s as u8]).map_err(|err| {
|
||||
warn!("CtrlRogBios: set_panel_overdrive {}", err);
|
||||
err
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::CtrlRogBios;
|
||||
use crate::config::Config;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[test]
|
||||
#[ignore = "Must be manually tested"]
|
||||
fn set_multizone_4key_config() {
|
||||
let config = Config::default();
|
||||
|
||||
let controller = CtrlRogBios {
|
||||
_config: Arc::new(Mutex::new(config)),
|
||||
};
|
||||
|
||||
let res = controller.panel_overdrive();
|
||||
assert_eq!(res, 1);
|
||||
|
||||
// controller.set_panel_od(false).unwrap();
|
||||
// let res = controller.panel_overdrive();
|
||||
// assert_eq!(res, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,39 @@
|
||||
use log::warn;
|
||||
use async_trait::async_trait;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
use zbus::Connection;
|
||||
use zvariant::Type;
|
||||
|
||||
use crate::{
|
||||
ctrl_anime::CtrlAnimeDisplay, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCpu,
|
||||
ctrl_leds::CtrlKbdBacklight, ctrl_rog_bios::CtrlRogBios, GetSupported,
|
||||
ctrl_anime::CtrlAnime, ctrl_aura::controller::CtrlKbdLed, ctrl_charge::CtrlCharge,
|
||||
ctrl_profiles::controller::CtrlPlatformProfile, ctrl_rog_bios::CtrlRogBios, GetSupported,
|
||||
};
|
||||
|
||||
use rog_types::supported::{
|
||||
AnimeSupportedFunctions, ChargeSupportedFunctions, FanCpuSupportedFunctions,
|
||||
LedSupportedFunctions, RogBiosSupportedFunctions,
|
||||
use rog_supported::{
|
||||
AnimeSupportedFunctions, ChargeSupportedFunctions, LedSupportedFunctions,
|
||||
PlatformProfileFunctions, RogBiosSupportedFunctions,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Type)]
|
||||
pub struct SupportedFunctions {
|
||||
pub anime_ctrl: AnimeSupportedFunctions,
|
||||
pub charge_ctrl: ChargeSupportedFunctions,
|
||||
pub fan_cpu_ctrl: FanCpuSupportedFunctions,
|
||||
pub platform_profile: PlatformProfileFunctions,
|
||||
pub keyboard_led: LedSupportedFunctions,
|
||||
pub rog_bios_ctrl: RogBiosSupportedFunctions,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl SupportedFunctions {
|
||||
fn supported_functions(&self) -> String {
|
||||
serde_json::to_string_pretty(self).unwrap()
|
||||
fn supported_functions(&self) -> &SupportedFunctions {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for SupportedFunctions {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Supported"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("SupportedFunctions: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Supported", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +42,10 @@ impl GetSupported for SupportedFunctions {
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
SupportedFunctions {
|
||||
keyboard_led: CtrlKbdBacklight::get_supported(),
|
||||
anime_ctrl: CtrlAnimeDisplay::get_supported(),
|
||||
anime_ctrl: CtrlAnime::get_supported(),
|
||||
keyboard_led: CtrlKbdLed::get_supported(),
|
||||
charge_ctrl: CtrlCharge::get_supported(),
|
||||
fan_cpu_ctrl: CtrlFanAndCpu::get_supported(),
|
||||
platform_profile: CtrlPlatformProfile::get_supported(),
|
||||
rog_bios_ctrl: CtrlRogBios::get_supported(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
use daemon::ctrl_leds::{CtrlKbdBacklight, DbusKbdBacklight};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use ::zbus::Connection;
|
||||
use daemon::ctrl_profiles::controller::CtrlProfileTask;
|
||||
use log::LevelFilter;
|
||||
use log::{error, info, warn};
|
||||
use smol::Executor;
|
||||
|
||||
use daemon::ctrl_anime::config::AnimeConfig;
|
||||
use daemon::ctrl_anime::zbus::CtrlAnimeZbus;
|
||||
use daemon::ctrl_anime::*;
|
||||
use daemon::ctrl_aura::config::AuraConfig;
|
||||
use daemon::ctrl_aura::controller::{
|
||||
CtrlKbdLed, CtrlKbdLedReloader, CtrlKbdLedTask, CtrlKbdLedZbus,
|
||||
};
|
||||
use daemon::ctrl_charge::CtrlCharge;
|
||||
use daemon::ctrl_profiles::config::ProfileConfig;
|
||||
use daemon::ctrl_rog_bios::CtrlRogBios;
|
||||
use daemon::{
|
||||
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
|
||||
};
|
||||
use daemon::{config_aura::AuraConfig, ctrl_charge::CtrlCharge};
|
||||
use daemon::{ctrl_anime::CtrlAnimeDisplay, ctrl_gfx::gfx::CtrlGraphics};
|
||||
use daemon::{
|
||||
ctrl_fan_cpu::{CtrlFanAndCpu, DbusFanAndCpu},
|
||||
ctrl_profiles::{controller::CtrlPlatformProfile, zbus::ProfileZbus},
|
||||
laptops::LaptopLedData,
|
||||
};
|
||||
|
||||
use daemon::{CtrlTask, Reloadable, ZbusAdd};
|
||||
use log::LevelFilter;
|
||||
use log::{error, info, warn};
|
||||
use rog_dbus::DBUS_NAME;
|
||||
use rog_types::gfx_vendors::GfxVendors;
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use rog_profiles::Profile;
|
||||
|
||||
use daemon::ctrl_rog_bios::CtrlRogBios;
|
||||
use std::convert::Into;
|
||||
use zbus::fdo;
|
||||
use zbus::Connection;
|
||||
use zvariant::ObjectPath;
|
||||
static PROFILE_CONFIG_PATH: &str = "/etc/asusd/profile.conf";
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut logger = env_logger::Builder::new();
|
||||
@@ -33,36 +40,46 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.filter(None, LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
info!(" daemon v{}", daemon::VERSION);
|
||||
info!(" rog-dbus v{}", rog_dbus::VERSION);
|
||||
info!("rog-types v{}", rog_types::VERSION);
|
||||
let is_service = match env::var_os("IS_SERVICE") {
|
||||
Some(val) => val == "1",
|
||||
None => false,
|
||||
};
|
||||
|
||||
start_daemon()?;
|
||||
if !is_service {
|
||||
println!("asusd schould be only run from the right systemd service");
|
||||
println!(
|
||||
"do not run in your terminal, if you need an logs please use journalctl -b -u asusd"
|
||||
);
|
||||
println!("asusd will now exit");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!(" daemon v{}", daemon::VERSION);
|
||||
info!(" rog-anime v{}", rog_anime::VERSION);
|
||||
info!(" rog-aura v{}", rog_aura::VERSION);
|
||||
info!(" rog-dbus v{}", rog_dbus::VERSION);
|
||||
info!(" rog-profiles v{}", rog_profiles::VERSION);
|
||||
info!("rog-supported v{}", rog_supported::VERSION);
|
||||
|
||||
let mut executor = Executor::new();
|
||||
|
||||
smol::block_on(start_daemon(&mut executor))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Timing is such that:
|
||||
// - interrupt write is minimum 1ms (sometimes lower)
|
||||
// - read interrupt must timeout, minimum of 1ms
|
||||
// - for a single usb packet, 2ms total.
|
||||
// - to maintain constant times of 1ms, per-key colours should use
|
||||
// the effect endpoint so that the complete colour block is written
|
||||
// as fast as 1ms per row of the matrix inside it. (10ms total time)
|
||||
fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
/// The actual main loop for the daemon
|
||||
async fn start_daemon(executor: &mut Executor<'_>) -> Result<(), Box<dyn Error>> {
|
||||
let supported = SupportedFunctions::get_supported();
|
||||
print_board_info();
|
||||
println!("{}", serde_json::to_string_pretty(&supported).unwrap());
|
||||
println!("{}", serde_json::to_string_pretty(&supported)?);
|
||||
|
||||
// Start zbus server
|
||||
let mut connection = Connection::system().await?;
|
||||
|
||||
let config = Config::load();
|
||||
let enable_gfx_switching = config.gfx_managed;
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
|
||||
let connection = Connection::new_system()?;
|
||||
fdo::DBusProxy::new(&connection)?
|
||||
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
|
||||
let mut object_server = zbus::ObjectServer::new(&connection);
|
||||
|
||||
supported.add_to_server(&mut object_server);
|
||||
supported.add_to_server(&mut connection).await;
|
||||
|
||||
match CtrlRogBios::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
@@ -70,7 +87,7 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
|
||||
// Then register to dbus server
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
ctrl.add_to_server(&mut connection).await;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("rog_bios_control: {}", err);
|
||||
@@ -83,116 +100,84 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
|
||||
// Then register to dbus server
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
ctrl.add_to_server(&mut connection).await;
|
||||
|
||||
let task = CtrlCharge::new(config)?;
|
||||
task.create_tasks(executor).await.ok();
|
||||
}
|
||||
Err(err) => {
|
||||
error!("charge_control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlAnimeDisplay::new() {
|
||||
if Profile::is_platform_profile_supported() {
|
||||
let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into());
|
||||
match CtrlPlatformProfile::new(profile_config) {
|
||||
Ok(mut ctrl) => {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Profile control: {}", err));
|
||||
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
let task = CtrlProfileTask::new(tmp.clone());
|
||||
task.create_tasks(executor).await.ok();
|
||||
|
||||
let task = ProfileZbus::new(tmp.clone());
|
||||
task.add_to_server(&mut connection).await;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Profile control: {}", err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("platform_profile support not found. This requires kernel 5.15.x or the patch applied: https://lkml.org/lkml/2021/8/18/1022");
|
||||
}
|
||||
|
||||
match CtrlAnime::new(AnimeConfig::load()) {
|
||||
Ok(ctrl) => {
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
let inner = Arc::new(Mutex::new(ctrl));
|
||||
|
||||
let mut reload = CtrlAnimeReloader(inner.clone());
|
||||
reload
|
||||
.reload()
|
||||
.unwrap_or_else(|err| warn!("AniMe: {}", err));
|
||||
|
||||
let zbus = CtrlAnimeZbus(inner.clone());
|
||||
zbus.add_to_server(&mut connection).await;
|
||||
|
||||
let task = CtrlAnimeTask::new(inner).await;
|
||||
task.create_tasks(executor).await.ok();
|
||||
}
|
||||
Err(err) => {
|
||||
error!("AniMe control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if enable_gfx_switching {
|
||||
match CtrlGraphics::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
// Need to check if a laptop has the dedicated gfx switch
|
||||
if CtrlRogBios::has_dedicated_gfx_toggle() {
|
||||
if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
|
||||
if let Ok(vendor) = ctrl.get_gfx_mode() {
|
||||
if ded == 1 && vendor != GfxVendors::Nvidia {
|
||||
warn!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
|
||||
let devices = ctrl.devices();
|
||||
let bus = ctrl.bus();
|
||||
CtrlGraphics::do_vendor_tasks(
|
||||
GfxVendors::Nvidia,
|
||||
false,
|
||||
&devices,
|
||||
&bus,
|
||||
)?;
|
||||
} else if ded == 0 {
|
||||
info!("Dedicated GFX toggle is off");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| error!("Gfx controller: {}", err));
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Gfx control: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect tasks for task thread
|
||||
let mut tasks: Vec<Arc<Mutex<dyn CtrlTask + Send>>> = Vec::new();
|
||||
|
||||
if let Ok(mut ctrl) = CtrlFanAndCpu::new(config).map_err(|err| {
|
||||
error!("Profile control: {}", err);
|
||||
}) {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Profile control: {}", err));
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
DbusFanAndCpu::new(tmp).add_to_server(&mut object_server);
|
||||
};
|
||||
|
||||
let laptop = LaptopLedData::get_data();
|
||||
let aura_config = AuraConfig::load(&laptop);
|
||||
if let Ok(ctrl) = CtrlKbdBacklight::new(laptop, aura_config).map_err(|err| {
|
||||
error!("Keyboard control: {}", err);
|
||||
err
|
||||
}) {
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server);
|
||||
tasks.push(tmp);
|
||||
match CtrlKbdLed::new(laptop, aura_config) {
|
||||
Ok(ctrl) => {
|
||||
let inner = Arc::new(Mutex::new(ctrl));
|
||||
|
||||
let mut reload = CtrlKbdLedReloader(inner.clone());
|
||||
reload
|
||||
.reload()
|
||||
.unwrap_or_else(|err| warn!("Keyboard LED control: {}", err));
|
||||
|
||||
CtrlKbdLedZbus::new(inner.clone())
|
||||
.add_to_server(&mut connection)
|
||||
.await;
|
||||
|
||||
let task = CtrlKbdLedTask::new(inner);
|
||||
task.create_tasks(executor).await.ok();
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Keyboard control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement messaging between threads to check fails
|
||||
// These tasks generally read a sys path or file to check for a
|
||||
// change
|
||||
let handle = std::thread::Builder::new()
|
||||
.name("asusd watch".to_string())
|
||||
.spawn(move || loop {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
for ctrl in tasks.iter() {
|
||||
if let Ok(mut lock) = ctrl.try_lock() {
|
||||
lock.do_task()
|
||||
.map_err(|err| {
|
||||
warn!("do_task error: {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
object_server
|
||||
.with(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Charge"),
|
||||
|obj: &CtrlCharge| {
|
||||
let x = obj.limit();
|
||||
obj.notify_charge(x as u8)
|
||||
},
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("object_server notify_charge error: {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Request dbus name after finishing initalizing all functions
|
||||
connection.request_name(DBUS_NAME).await?;
|
||||
loop {
|
||||
if let Err(err) = &handle {
|
||||
error!("{}", err);
|
||||
}
|
||||
if let Err(err) = object_server.try_handle_next() {
|
||||
error!("{}", err);
|
||||
}
|
||||
smol::block_on(executor.tick());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
use intel_pstate::PStateError;
|
||||
use rog_fan_curve::CurveError;
|
||||
use rog_types::error::GraphicsError;
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_profiles::error::ProfileError;
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
|
||||
use crate::ctrl_gfx::error::GfxError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RogError {
|
||||
ParseFanLevel,
|
||||
ParseVendor,
|
||||
ParseLed,
|
||||
MissingProfile(String),
|
||||
@@ -18,25 +14,26 @@ pub enum RogError {
|
||||
Write(String, std::io::Error),
|
||||
NotSupported,
|
||||
NotFound(String),
|
||||
IntelPstate(PStateError),
|
||||
FanCurve(CurveError),
|
||||
DoTask(String),
|
||||
MissingFunction(String),
|
||||
MissingLedBrightNode(String, std::io::Error),
|
||||
ReloadFail(String),
|
||||
GfxSwitching(GfxError),
|
||||
Profiles(ProfileError),
|
||||
Initramfs(String),
|
||||
Modprobe(String),
|
||||
Command(String, std::io::Error),
|
||||
Io(std::io::Error),
|
||||
Zbus(zbus::Error),
|
||||
ChargeLimit(u8),
|
||||
AuraEffectNotSupported,
|
||||
NoAuraKeyboard,
|
||||
NoAuraNode,
|
||||
Anime(AnimeError),
|
||||
}
|
||||
|
||||
impl fmt::Display for RogError {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
RogError::ParseFanLevel => write!(f, "Parse profile error"),
|
||||
RogError::ParseVendor => write!(f, "Parse gfx vendor error"),
|
||||
RogError::ParseLed => write!(f, "Parse LED error"),
|
||||
RogError::MissingProfile(profile) => write!(f, "Profile does not exist {}", profile),
|
||||
@@ -46,42 +43,35 @@ impl fmt::Display for RogError {
|
||||
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
|
||||
RogError::NotSupported => write!(f, "Not supported"),
|
||||
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
|
||||
RogError::IntelPstate(err) => write!(f, "Intel pstate error: {}", err),
|
||||
RogError::FanCurve(err) => write!(f, "Custom fan-curve error: {}", err),
|
||||
RogError::DoTask(deets) => write!(f, "Task error: {}", deets),
|
||||
RogError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
|
||||
RogError::MissingLedBrightNode(path, error) => write!(f, "Led node at {} is missing, please check you have the required patch or dkms module installed: {}", path, error),
|
||||
RogError::ReloadFail(deets) => write!(f, "Task error: {}", deets),
|
||||
RogError::GfxSwitching(deets) => write!(f, "Graphics switching error: {}", deets),
|
||||
RogError::Profiles(deets) => write!(f, "Profile error: {}", deets),
|
||||
RogError::Initramfs(detail) => write!(f, "Initiramfs error: {}", detail),
|
||||
RogError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
|
||||
RogError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
|
||||
RogError::Io(detail) => write!(f, "std::io error: {}", detail),
|
||||
RogError::Zbus(detail) => write!(f, "Zbus error: {}", detail),
|
||||
RogError::ChargeLimit(value) => write!(f, "Invalid charging limit, not in range 20-100%: {}", value),
|
||||
RogError::AuraEffectNotSupported => write!(f, "Aura effect not supported"),
|
||||
RogError::NoAuraKeyboard => write!(f, "No supported Aura keyboard"),
|
||||
RogError::NoAuraNode => write!(f, "No Aura keyboard node found"),
|
||||
RogError::Anime(deets) => write!(f, "AniMe Matrix error: {}", deets),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RogError {}
|
||||
|
||||
impl From<PStateError> for RogError {
|
||||
fn from(err: PStateError) -> Self {
|
||||
RogError::IntelPstate(err)
|
||||
impl From<ProfileError> for RogError {
|
||||
fn from(err: ProfileError) -> Self {
|
||||
RogError::Profiles(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CurveError> for RogError {
|
||||
fn from(err: CurveError) -> Self {
|
||||
RogError::FanCurve(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GraphicsError> for RogError {
|
||||
fn from(err: GraphicsError) -> Self {
|
||||
match err {
|
||||
GraphicsError::ParseVendor => RogError::GfxSwitching(GfxError::ParseVendor),
|
||||
GraphicsError::ParsePower => RogError::GfxSwitching(GfxError::ParsePower),
|
||||
}
|
||||
impl From<AnimeError> for RogError {
|
||||
fn from(err: AnimeError) -> Self {
|
||||
RogError::Anime(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,3 +86,10 @@ impl From<std::io::Error> for RogError {
|
||||
RogError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RogError> for zbus::fdo::Error {
|
||||
#[inline]
|
||||
fn from(err: RogError) -> Self {
|
||||
zbus::fdo::Error::Failed(format!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
use log::{info, warn};
|
||||
use rog_types::aura_modes::AuraModeNum;
|
||||
use rog_aura::{AuraModeNum, AuraZone};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
|
||||
pub const ASUS_LED_MODE_CONF: &str = "/etc/asusd/asusd-ledmodes.toml";
|
||||
pub const ASUS_LED_MODE_USER_CONF: &str = "/etc/asusd/asusd-user-ledmodes.toml";
|
||||
pub const ASUS_KEYBOARD_DEVICES: [&str; 4] = ["1866", "1869", "1854", "19b6"];
|
||||
|
||||
pub fn print_board_info() {
|
||||
let dmi = sysfs_class::DmiId::default();
|
||||
let board_name = dmi.board_name().expect("Could not get board_name");
|
||||
let prod_name = dmi.product_name().expect("Could not get product_name");
|
||||
let prod_family = dmi.product_family().expect("Could not get product_family");
|
||||
|
||||
info!("Product name: {}", prod_name.trim());
|
||||
info!("Product family: {}", prod_family.trim());
|
||||
info!("Board name: {}", board_name.trim());
|
||||
}
|
||||
@@ -34,17 +33,18 @@ pub fn print_modes(supported_modes: &[u8]) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
struct LedSupportFile {
|
||||
led_data: Vec<LaptopLedData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct LaptopLedData {
|
||||
pub prod_family: String,
|
||||
pub board_names: Vec<String>,
|
||||
pub standard: Vec<AuraModeNum>,
|
||||
pub multizone: bool,
|
||||
pub multizone: Vec<AuraZone>,
|
||||
pub per_key: bool,
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ impl LaptopLedData {
|
||||
prod_family,
|
||||
board_names: vec![board_name],
|
||||
standard: vec![],
|
||||
multizone: false,
|
||||
multizone: vec![],
|
||||
per_key: false,
|
||||
}
|
||||
}
|
||||
@@ -87,19 +87,81 @@ impl LedSupportFile {
|
||||
}
|
||||
|
||||
fn load_from_config() -> Option<Self> {
|
||||
let mut loaded = false;
|
||||
let mut data = LedSupportFile::default();
|
||||
// Load user configs first so they are first to be checked
|
||||
if let Ok(mut file) = OpenOptions::new().read(true).open(&ASUS_LED_MODE_USER_CONF) {
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("{} is empty", ASUS_LED_MODE_USER_CONF);
|
||||
} else {
|
||||
if let Ok(mut tmp) = toml::from_str::<LedSupportFile>(&buf) {
|
||||
data.led_data.append(&mut tmp.led_data);
|
||||
}
|
||||
info!(
|
||||
"Loaded user-defined LED support data from {}",
|
||||
ASUS_LED_MODE_USER_CONF
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Load and append the default LED support data
|
||||
if let Ok(mut file) = OpenOptions::new().read(true).open(&ASUS_LED_MODE_CONF) {
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("{} is empty", ASUS_LED_MODE_CONF);
|
||||
} else {
|
||||
return Some(toml::from_str(&buf).unwrap_or_else(|_| {
|
||||
panic!("Could not deserialise {}", ASUS_LED_MODE_CONF)
|
||||
}));
|
||||
let mut tmp: LedSupportFile = toml::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", ASUS_LED_MODE_CONF));
|
||||
data.led_data.append(&mut tmp.led_data);
|
||||
loaded = true;
|
||||
info!(
|
||||
"Loaded default LED support data from {}",
|
||||
ASUS_LED_MODE_CONF
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("Does {} exist?", ASUS_LED_MODE_CONF);
|
||||
|
||||
if loaded {
|
||||
return Some(data);
|
||||
}
|
||||
|
||||
warn!("Does {} exist?", ASUS_LED_MODE_USER_CONF);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs::OpenOptions, io::Read, path::PathBuf};
|
||||
|
||||
use super::LaptopLedData;
|
||||
use rog_aura::{AuraModeNum, AuraZone};
|
||||
|
||||
#[test]
|
||||
fn check_data_parse() {
|
||||
let led = LaptopLedData {
|
||||
prod_family: "Test".to_owned(),
|
||||
board_names: vec!["Test".to_owned()],
|
||||
standard: vec![AuraModeNum::Static],
|
||||
multizone: vec![AuraZone::Key1, AuraZone::Logo, AuraZone::BarLeft],
|
||||
per_key: false,
|
||||
};
|
||||
|
||||
let toml = toml::to_string_pretty(&led).unwrap();
|
||||
println!("{toml}");
|
||||
|
||||
let mut data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
data.push("../data/asusd-ledmodes.toml");
|
||||
|
||||
let mut file = OpenOptions::new().read(true).open(&data).unwrap();
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf).unwrap();
|
||||
|
||||
let x = toml::to_string_pretty(&buf).unwrap();
|
||||
println!("{x}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#![deny(unused_must_use)]
|
||||
/// Configuration loading, saving
|
||||
pub mod config;
|
||||
pub mod config_aura;
|
||||
pub(crate) mod config_old;
|
||||
/// Control of AniMe matrix display
|
||||
pub mod ctrl_anime;
|
||||
/// Keyboard LED brightness control, RGB, and LED display modes
|
||||
pub mod ctrl_aura;
|
||||
/// Control of battery charge level
|
||||
pub mod ctrl_charge;
|
||||
/// Control CPU min/max freq and turbo, fan mode, fan curves
|
||||
@@ -18,11 +18,7 @@ pub mod ctrl_charge;
|
||||
/// - CPU turbo enable/disable
|
||||
/// - Fan mode (normal, boost, silent)
|
||||
/// - Fan min/max RPM curve
|
||||
pub mod ctrl_fan_cpu;
|
||||
/// GPU switching and power
|
||||
pub mod ctrl_gfx;
|
||||
/// Keyboard LED brightness control, RGB, and LED display modes
|
||||
pub mod ctrl_leds;
|
||||
pub mod ctrl_profiles;
|
||||
/// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode
|
||||
pub mod ctrl_rog_bios;
|
||||
/// Laptop matching to determine capabilities
|
||||
@@ -31,11 +27,17 @@ pub mod laptops;
|
||||
/// Fetch all supported functions for the laptop
|
||||
pub mod ctrl_supported;
|
||||
|
||||
mod error;
|
||||
pub mod error;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::error::RogError;
|
||||
use async_trait::async_trait;
|
||||
use config::Config;
|
||||
use zbus::ObjectServer;
|
||||
use log::warn;
|
||||
use smol::{stream::StreamExt, Executor, Timer};
|
||||
use zbus::Connection;
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -43,12 +45,48 @@ pub trait Reloadable {
|
||||
fn reload(&mut self) -> Result<(), RogError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ZbusAdd {
|
||||
fn add_to_server(self, server: &mut ObjectServer);
|
||||
async fn add_to_server(self, server: &mut Connection);
|
||||
|
||||
async fn add_to_server_helper(
|
||||
iface: impl zbus::Interface,
|
||||
path: &str,
|
||||
server: &mut Connection,
|
||||
) {
|
||||
server
|
||||
.object_server()
|
||||
.at(&ObjectPath::from_str_unchecked(path), iface)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("{}: add_to_server {}", path, err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up a task to run on the async executor
|
||||
#[async_trait]
|
||||
pub trait CtrlTask {
|
||||
fn do_task(&mut self) -> Result<(), RogError>;
|
||||
/// Implement to set up various tasks that may be required, using the `Executor`.
|
||||
/// No blocking loops are allowed, or they must be run on a separate thread.
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError>;
|
||||
|
||||
/// Create a timed repeating task
|
||||
async fn repeating_task(
|
||||
&self,
|
||||
millis: u64,
|
||||
executor: &mut Executor,
|
||||
mut task: impl FnMut() + Send + 'static,
|
||||
) {
|
||||
let timer = Timer::interval(Duration::from_millis(millis));
|
||||
executor
|
||||
.spawn(async move {
|
||||
timer.for_each(|_| task()).await;
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CtrlTaskComplex {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
|
||||
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
|
||||
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
|
||||
|
||||
# Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
|
||||
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
|
||||
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
|
||||
@@ -1,4 +0,0 @@
|
||||
Section "ServerLayout"
|
||||
Identifier "layout"
|
||||
Option "AllowNVIDIAGPUScreens"
|
||||
EndSection
|
||||
@@ -3,6 +3,7 @@ Description=ASUS Notebook Control
|
||||
After=basic.target syslog.target
|
||||
|
||||
[Service]
|
||||
Environment=IS_SERVICE=1
|
||||
ExecStart=/usr/bin/asusd
|
||||
Restart=on-failure
|
||||
Type=dbus
|
||||
|
||||
@@ -2,89 +2,168 @@
|
||||
prod_family = "Zephyrus S"
|
||||
board_names = ["GX502", "GX701", "G531", "GL531", "G532"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus M"
|
||||
board_names = ["GU502GV"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus M"
|
||||
board_names = ["GM501GS"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502LW"]
|
||||
board_names = ["GU502LW", "GU502LV"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502LU"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Pulse"]
|
||||
multizone = false
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus S17"
|
||||
board_names = ["GX703HS"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus"
|
||||
board_names = ["GM501GM", "GX531"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = true
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G531GW", "G733QS", "G733QR"]
|
||||
board_names = ["G531GW", "G533QR", "G533QS", "G733QS", "G733QR", "G513QR", "G713QR", "G513QM"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["GX531", "G512LV", "G712LV", "G712LW"]
|
||||
board_names = ["G513QE", "GX531", "G512LV", "G712LV", "G712LW", "G513IH", "G513QY", "G713QM", "G512"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = true
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G512LI", "G712LI", "G531GD"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = false
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G513IM"]
|
||||
standard = ["Flash", "Static", "Breathe", "Strobe", "Rainbow"]
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Strix"
|
||||
board_names = ["G731GV", "G731GW", "G531GV"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow"]
|
||||
multizone = true
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Strix"
|
||||
board_names = ["GL504G"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4", "Logo", "BarLeft", "BarRight"]
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Strix"
|
||||
board_names = ["G731GT", "G731GU", "G531GT", "G531GU"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow"]
|
||||
multizone = false
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Strix Scar"
|
||||
board_names = ["G531", "G731"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = true
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG"
|
||||
board_names = ["GL553VE"]
|
||||
standard = ["Static", "Breathe", "Strobe"]
|
||||
multizone = true
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus G14"
|
||||
board_names = ["GA401Q"]
|
||||
standard = ["Static", "Breathe", "Pulse"]
|
||||
multizone = false
|
||||
per_key = false
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus G14"
|
||||
board_names = ["GA402R"]
|
||||
standard = ["Static", "Breathe", "Pulse", "Rainbow"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
# GA503QE at higher priority (first match) than GA503Q
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus G15"
|
||||
board_names = ["GA503QE"]
|
||||
standard = ["Static", "Breathe", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus G15"
|
||||
board_names = ["GA503Q", "GA503R"]
|
||||
standard = ["Static", "Breathe", "Pulse", "Rainbow", "Strobe"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus"
|
||||
board_names = ["GX550L"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus Duo 15 SE"
|
||||
board_names = ["GX551Q"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Flow X13"
|
||||
board_names = ["GV301QH", "GV301QE"]
|
||||
standard = ["Static", "Breathe", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G513IC"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
Description=ASUS Notebook Control
|
||||
StartLimitInterval=200
|
||||
StartLimitBurst=2
|
||||
Before=display-manager.service
|
||||
Before=multi-user.target
|
||||
|
||||
[Service]
|
||||
Environment=IS_SERVICE=1
|
||||
ExecStartPre=/bin/sleep 2
|
||||
ExecStart=/usr/bin/asusd
|
||||
Restart=on-failure
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
Type=dbus
|
||||
BusName=org.asuslinux.Daemon
|
||||
BusName=org.asuslinux.Daemon
|
||||
SELinuxContext=system_u:system_r:unconfined_t:s0
|
||||
#SELinuxContext=system_u:object_r:modules_object_t:s0
|
||||
86
data/icons/scalable/gpu-compute.svg
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Gravit.io -->
|
||||
|
||||
<svg
|
||||
style="isolation:isolate"
|
||||
viewBox="0 0 82.733002 82.733002"
|
||||
width="82.733002pt"
|
||||
height="82.733002pt"
|
||||
version="1.1"
|
||||
id="svg19"
|
||||
sodipodi:docname="gpu-compute.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview21"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="pt"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.96875"
|
||||
inkscape:cx="15.597484"
|
||||
inkscape:cy="54.742138"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1083"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg19" />
|
||||
<defs
|
||||
id="defs5">
|
||||
<clipPath
|
||||
id="_clipPath_ZEBBDq6ZtbLgkRXocTHUWTcRbcURZIva">
|
||||
<rect
|
||||
width="128"
|
||||
height="128"
|
||||
id="rect2"
|
||||
x="0"
|
||||
y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
clip-path="url(#_clipPath_ZEBBDq6ZtbLgkRXocTHUWTcRbcURZIva)"
|
||||
id="g17"
|
||||
transform="translate(-22.624,-23)">
|
||||
<rect
|
||||
width="128"
|
||||
height="128"
|
||||
style="fill:#000000"
|
||||
fill-opacity="0"
|
||||
id="rect7"
|
||||
x="0"
|
||||
y="0" />
|
||||
<g
|
||||
id="g15">
|
||||
<path
|
||||
id="path9"
|
||||
style="opacity:1;mix-blend-mode:normal;fill:#ed1c24;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 13.789062,13.789062 v 6.894532 H 0 v 6.894531 H 13.789062 V 41.367188 H 0 v 6.894531 H 13.789062 V 62.048828 H 0 v 6.894531 H 13.789062 V 82.732422 H 0 v 6.894531 h 13.789062 v 6.894531 h 6.894532 v 13.789066 h 6.894531 V 96.521484 h 13.789063 v 13.789066 h 6.894531 V 96.521484 h 13.789062 v 13.789066 h 6.892578 V 96.521484 h 13.789063 v 13.789066 h 6.896484 V 96.521484 h 6.894532 z"
|
||||
transform="matrix(0.75,0,0,0.75,22.624,23)" />
|
||||
<path
|
||||
id="path9-3"
|
||||
style="isolation:isolate;mix-blend-mode:normal;fill:#76b900;fill-opacity:1;stroke:#000000;stroke-width:0.375;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 95.016578,95.391113 -0.0051,-5.170896 10.341782,-0.01017 -0.005,-5.170896 -10.341789,0.01017 -0.01017,-10.341791 10.341799,-0.01018 -0.005,-5.170895 -10.341787,0.01018 -0.01017,-10.340328 10.341797,-0.01017 -0.005,-5.170896 -10.341794,0.01017 -0.01018,-10.341792 10.341784,-0.01017 -0.005,-5.170895 -10.341791,0.01017 -0.0051,-5.170897 -5.170898,0.0051 -0.01017,-10.341796 -5.170896,0.0051 0.01017,10.341797 -10.341792,0.01017 -0.01017,-10.341795 -5.170897,0.0051 0.01017,10.341796 -10.341791,0.01017 -0.01017,-10.341796 -5.169431,0.0051 0.01017,10.341795 -10.341792,0.01018 -0.01017,-10.341797 -5.172361,0.0051 0.01017,10.341795 -5.170894,0.0051 z" />
|
||||
<rect
|
||||
x="38.146"
|
||||
y="38.512001"
|
||||
width="51.708"
|
||||
height="51.708"
|
||||
fill="rgb(77,77,77)"
|
||||
id="rect11"
|
||||
style="fill:none" />
|
||||
<path
|
||||
d="m 67.238,81.543 c 3.416,0 7.169,-0.866 10.056,-2.262 l -1.395,-4.474 c -2.165,0.818 -4.86,1.299 -7.169,1.299 -7.313,0 -11.884,-4.811 -11.884,-12.317 0,-7.073 4.138,-11.114 11.162,-11.114 2.646,0 5.678,0.529 7.843,1.395 l 1.732,-4.811 c -2.839,-1.347 -6.111,-2.069 -9.43,-2.069 -10.537,0 -17.754,6.976 -17.754,17.465 0,10.104 6.832,16.888 16.839,16.888 z"
|
||||
fill="#dddddd"
|
||||
id="path13"
|
||||
style="stroke:#000000;stroke-opacity:1;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;fill:#ffffff" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
82
data/icons/scalable/gpu-hybrid.svg
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Gravit.io -->
|
||||
|
||||
<svg
|
||||
style="isolation:isolate"
|
||||
viewBox="0 0 82.733002 82.733002"
|
||||
width="82.733002pt"
|
||||
height="82.733002pt"
|
||||
version="1.1"
|
||||
id="svg19"
|
||||
sodipodi:docname="gpu-hybrid.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview21"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="pt"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.96875"
|
||||
inkscape:cx="15.798742"
|
||||
inkscape:cy="54.742138"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1083"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg19" />
|
||||
<defs
|
||||
id="defs5">
|
||||
<clipPath
|
||||
id="_clipPath_uVSrFPVusaaYDISWhfCjIlGBeb9FaZAr">
|
||||
<rect
|
||||
width="128"
|
||||
height="128"
|
||||
id="rect2"
|
||||
x="0"
|
||||
y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
clip-path="url(#_clipPath_uVSrFPVusaaYDISWhfCjIlGBeb9FaZAr)"
|
||||
id="g17"
|
||||
transform="translate(-22.624,-23)">
|
||||
<rect
|
||||
width="128"
|
||||
height="128"
|
||||
style="fill:#000000"
|
||||
fill-opacity="0"
|
||||
id="rect7"
|
||||
x="0"
|
||||
y="0" />
|
||||
<g
|
||||
id="g15">
|
||||
<path
|
||||
d="m 38.137,23 v 10.342 h -5.171 v 5.17 H 22.624 v 5.171 H 32.966 V 54.025 H 22.624 v 5.171 H 32.966 V 69.537 H 22.624 v 5.171 H 32.966 V 85.05 H 22.624 v 5.17 h 10.342 v 5.171 h 5.171 v 10.342 h 5.171 V 95.391 h 10.341 v 10.342 H 58.82 V 95.391 h 10.342 v 10.342 h 5.17 V 95.391 h 10.342 v 10.342 h 5.171 V 95.391 h 5.171 V 90.22 h 10.341 V 85.05 H 95.016 V 74.708 h 10.341 V 69.537 H 95.016 V 59.196 h 10.341 V 54.025 H 95.016 V 43.683 h 10.341 V 38.512 H 95.016 v -5.17 H 89.845 V 23 H 84.674 V 33.342 H 74.332 V 23 h -5.17 V 33.342 H 58.82 V 23 H 53.649 V 33.342 H 43.308 V 23 Z"
|
||||
fill="rgb(77,77,77)"
|
||||
id="path9"
|
||||
style="fill:#ed1c24;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.75;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<rect
|
||||
x="38.146"
|
||||
y="38.512001"
|
||||
width="51.708"
|
||||
height="51.708"
|
||||
fill="rgb(77,77,77)"
|
||||
id="rect11"
|
||||
style="fill:#76b900;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
|
||||
<path
|
||||
d="m 51.464,79 h 5.663 V 66.118 H 70.916 V 79 h 5.62 V 49 h -5.62 V 61.147 H 57.127 V 49 h -5.663 z"
|
||||
fill="rgb(221,221,221)"
|
||||
id="path13"
|
||||
style="fill:#ececec;stroke:#000000;stroke-opacity:1;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
52
data/icons/scalable/gpu-integrated.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Gravit.io -->
|
||||
|
||||
<svg
|
||||
style="isolation:isolate"
|
||||
viewBox="0 0 82.410369 82.410377"
|
||||
width="82.41037pt"
|
||||
height="82.410378pt"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="gpu-integrated.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview19"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="pt"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.96875"
|
||||
inkscape:cx="14.389937"
|
||||
inkscape:cy="54.742138"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1083"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<defs
|
||||
id="defs5">
|
||||
<clipPath
|
||||
id="_clipPath_GAg6mcrkdqJgsmZOjvo6JT6R1d3r9FoI">
|
||||
<rect
|
||||
width="128"
|
||||
height="128"
|
||||
id="rect2"
|
||||
x="0"
|
||||
y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:#ed1c24;fill-opacity:1;stroke:#000000;stroke-width:0.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 15.92217,76.9033 V 71.771223 H 13.28066 10.63915 V 69.129715 66.488208 H 5.507075 0.375 v -2.037735 -2.037735 h 5.132075 5.132075 v -5.73585 -5.73585 H 5.507075 0.375 V 48.903302 46.865566 H 5.507075 10.63915 V 41.205189 35.544811 H 5.507075 0.375 V 33.431604 31.318396 H 5.507075 10.63915 V 25.658019 19.997641 H 5.507075 0.375 V 17.959906 15.92217 h 5.132075 5.132075 v -2.641509 -2.64151 h 2.64151 2.64151 V 5.5070763 0.375 h 2.037736 2.037735 v 5.1320763 5.1320747 h 5.660378 5.660377 V 5.5070763 0.375 h 2.113207 2.113208 v 5.1320763 5.1320747 h 5.660377 5.660378 V 5.5070763 0.375 h 1.947066 1.947063 l 0.09711,1.8490563 c 0.07996,1.52246 0.251242,6.717377 0.253724,7.695141 0.0014,0.5274167 -0.04609,0.5197837 3.905982,0.6286047 1.618867,0.04457 3.945285,0.123799 5.16981,0.176053 l 2.226412,0.09501 V 5.5969303 0.375 h 2.037735 2.037743 v 5.1320763 5.1320747 h 2.641507 2.641508 v 2.64151 2.641509 h 5.132077 5.13207 v 2.037736 2.037735 h -5.13207 -5.132077 v 5.660378 5.660377 h 5.132077 5.13207 v 2.113208 2.113207 h -5.13207 -5.132077 v 5.660378 5.660377 h 5.132077 5.13207 v 2.037736 2.037736 h -5.13207 -5.132077 v 5.73585 5.73585 h 5.132077 5.13207 v 2.037735 2.037735 h -5.13207 -5.132077 v 2.641507 2.641508 H 69.129718 66.488211 V 76.9033 82.035378 H 64.450468 62.412733 V 76.9033 71.771223 h -5.73585 -5.735846 V 76.9033 82.035378 H 48.903301 46.865566 V 76.9033 71.771223 H 41.205188 35.544811 V 76.9033 82.035378 H 33.431603 31.318396 V 76.9033 71.771223 H 25.658019 19.997641 V 76.9033 82.035378 H 17.959906 15.92217 Z"
|
||||
id="path1355" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
52
data/icons/scalable/gpu-nvidia.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Gravit.io -->
|
||||
|
||||
<svg
|
||||
style="isolation:isolate"
|
||||
viewBox="0 0 82.410369 82.410377"
|
||||
width="82.41037pt"
|
||||
height="82.410378pt"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="gpu-nvidia.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview19"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="pt"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.96875"
|
||||
inkscape:cx="14.389937"
|
||||
inkscape:cy="54.742138"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1083"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<defs
|
||||
id="defs5">
|
||||
<clipPath
|
||||
id="_clipPath_GAg6mcrkdqJgsmZOjvo6JT6R1d3r9FoI">
|
||||
<rect
|
||||
width="128"
|
||||
height="128"
|
||||
id="rect2"
|
||||
x="0"
|
||||
y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:#76b900;fill-opacity:1;stroke:#000000;stroke-width:0.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 15.92217,76.9033 V 71.771223 H 13.28066 10.63915 V 69.129715 66.488208 H 5.507075 0.375 v -2.037735 -2.037735 h 5.132075 5.132075 v -5.73585 -5.73585 H 5.507075 0.375 V 48.903302 46.865566 H 5.507075 10.63915 V 41.205189 35.544811 H 5.507075 0.375 V 33.431604 31.318396 H 5.507075 10.63915 V 25.658019 19.997641 H 5.507075 0.375 V 17.959906 15.92217 h 5.132075 5.132075 v -2.641509 -2.64151 h 2.64151 2.64151 V 5.5070763 0.375 h 2.037736 2.037735 v 5.1320763 5.1320747 h 5.660378 5.660377 V 5.5070763 0.375 h 2.113207 2.113208 v 5.1320763 5.1320747 h 5.660377 5.660378 V 5.5070763 0.375 h 1.947066 1.947063 l 0.09711,1.8490563 c 0.07996,1.52246 0.251242,6.717377 0.253724,7.695141 0.0014,0.5274167 -0.04609,0.5197837 3.905982,0.6286047 1.618867,0.04457 3.945285,0.123799 5.16981,0.176053 l 2.226412,0.09501 V 5.5969303 0.375 h 2.037735 2.037743 v 5.1320763 5.1320747 h 2.641507 2.641508 v 2.64151 2.641509 h 5.132077 5.13207 v 2.037736 2.037735 h -5.13207 -5.132077 v 5.660378 5.660377 h 5.132077 5.13207 v 2.113208 2.113207 h -5.13207 -5.132077 v 5.660378 5.660377 h 5.132077 5.13207 v 2.037736 2.037736 h -5.13207 -5.132077 v 5.73585 5.73585 h 5.132077 5.13207 v 2.037735 2.037735 h -5.13207 -5.132077 v 2.641507 2.641508 H 69.129718 66.488211 V 76.9033 82.035378 H 64.450468 62.412733 V 76.9033 71.771223 h -5.73585 -5.735846 V 76.9033 82.035378 H 48.903301 46.865566 V 76.9033 71.771223 H 41.205188 35.544811 V 76.9033 82.035378 H 33.431603 31.318396 V 76.9033 71.771223 H 25.658019 19.997641 V 76.9033 82.035378 H 17.959906 15.92217 Z"
|
||||
id="path1355" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
86
data/icons/scalable/gpu-vfio.svg
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Gravit.io -->
|
||||
|
||||
<svg
|
||||
style="isolation:isolate"
|
||||
viewBox="0 0 82.733002 82.733002"
|
||||
width="82.733002pt"
|
||||
height="82.733002pt"
|
||||
version="1.1"
|
||||
id="svg19"
|
||||
sodipodi:docname="gpu-vfio.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview21"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="pt"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.8686859"
|
||||
inkscape:cx="55.091791"
|
||||
inkscape:cy="55.155334"
|
||||
inkscape:window-width="2048"
|
||||
inkscape:window-height="1083"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg19" />
|
||||
<defs
|
||||
id="defs5">
|
||||
<clipPath
|
||||
id="_clipPath_ZEBBDq6ZtbLgkRXocTHUWTcRbcURZIva">
|
||||
<rect
|
||||
width="128"
|
||||
height="128"
|
||||
id="rect2"
|
||||
x="0"
|
||||
y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
clip-path="url(#_clipPath_ZEBBDq6ZtbLgkRXocTHUWTcRbcURZIva)"
|
||||
id="g17"
|
||||
transform="translate(-22.624,-23)">
|
||||
<rect
|
||||
width="128"
|
||||
height="128"
|
||||
style="fill:#000000"
|
||||
fill-opacity="0"
|
||||
id="rect7"
|
||||
x="0"
|
||||
y="0" />
|
||||
<g
|
||||
id="g15">
|
||||
<path
|
||||
id="path9"
|
||||
style="opacity:1;mix-blend-mode:normal;fill:#ed1c24;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 13.789062,13.789062 v 6.894532 H 0 v 6.894531 H 13.789062 V 41.367188 H 0 v 6.894531 H 13.789062 V 62.048828 H 0 v 6.894531 H 13.789062 V 82.732422 H 0 v 6.894531 h 13.789062 v 6.894531 h 6.894532 v 13.789066 h 6.894531 V 96.521484 h 13.789063 v 13.789066 h 6.894531 V 96.521484 h 13.789062 v 13.789066 h 6.892578 V 96.521484 h 13.789063 v 13.789066 h 6.896484 V 96.521484 h 6.894532 z"
|
||||
transform="matrix(0.75,0,0,0.75,22.624,23)" />
|
||||
<path
|
||||
id="path9-3"
|
||||
style="isolation:isolate;mix-blend-mode:normal;fill:#76b900;fill-opacity:1;stroke:#000000;stroke-width:0.375;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 95.016578,95.391113 -0.0051,-5.170896 10.341782,-0.01017 -0.005,-5.170896 -10.341789,0.01017 -0.01017,-10.341791 10.341799,-0.01018 -0.005,-5.170895 -10.341787,0.01018 -0.01017,-10.340328 10.341797,-0.01017 -0.005,-5.170896 -10.341794,0.01017 -0.01018,-10.341792 10.341784,-0.01017 -0.005,-5.170895 -10.341791,0.01017 -0.0051,-5.170897 -5.170898,0.0051 -0.01017,-10.341796 -5.170896,0.0051 0.01017,10.341797 -10.341792,0.01017 -0.01017,-10.341795 -5.170897,0.0051 0.01017,10.341796 -10.341791,0.01017 -0.01017,-10.341796 -5.169431,0.0051 0.01017,10.341795 -10.341792,0.01018 -0.01017,-10.341797 -5.172361,0.0051 0.01017,10.341795 -5.170894,0.0051 z" />
|
||||
<rect
|
||||
x="38.146"
|
||||
y="38.512001"
|
||||
width="51.708"
|
||||
height="51.708"
|
||||
fill="rgb(77,77,77)"
|
||||
id="rect11"
|
||||
style="fill:none" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
d="m 38.016104,57.595764 h 6.037 l 11.263,-31.267 h -5.947 l -8.064,24.374 -7.75,-24.374 h -6.758 z"
|
||||
fill="#dddddd"
|
||||
id="path849"
|
||||
style="isolation:isolate;stroke:#000000;stroke-opacity:1;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;fill:#ffffff" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
1
data/icons/scalable/notification-reboot.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="141 297.96 507.339 471.04" width="507.339pt" height="471.04pt"><g><g><g><g><path d=" M 537.075 443.463 L 537.075 443.463 L 564.723 427.335 C 504.903 324.571 375.966 285.313 269.027 337.304 L 269.027 297.96 L 237.027 297.96 L 237.027 393.96 L 333.027 393.96 L 333.027 361.96 L 292.147 361.96 C 382.665 323.563 487.612 358.486 537.075 443.463 Z " fill="rgb(204,64,64)"/><path d=" M 245.027 692.775 C 170.84 632.524 151.314 527.353 198.931 444.488 L 171.187 428.488 C 116.876 523.063 137.426 642.887 220.147 713.96 L 181.027 713.96 L 181.027 745.96 L 277.027 745.96 L 277.027 649.96 L 245.027 649.96 L 245.027 692.775 L 245.027 692.775 Z " fill="rgb(204,64,64)"/><path d=" M 580.451 499.048 L 512.579 566.936 L 535.203 589.56 L 562.403 562.36 C 551.734 661.49 468.201 736.724 368.499 737 L 368.499 769 C 487.289 768.688 585.833 677.011 594.707 558.552 L 625.715 589.56 L 648.339 566.936 L 580.451 499.048 Z " fill="rgb(204,64,64)"/></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"anime": [
|
||||
{
|
||||
"AsusAnimation": {
|
||||
"file": "/usr/share/asusd/anime/asus/rog/Sunset.gif",
|
||||
"time": {
|
||||
"Cycles": 1
|
||||
},
|
||||
"brightness": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ImageAnimation": {
|
||||
"file": "/usr/share/asusd/anime/custom/sonic-run.gif",
|
||||
"scale": 0.9,
|
||||
"angle": 0.65,
|
||||
"translation": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"time": {
|
||||
"Time": {
|
||||
"secs": 5,
|
||||
"nanos": 0
|
||||
}
|
||||
},
|
||||
"brightness": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"Image": {
|
||||
"file": "/usr/share/asusd/anime/custom/rust.png",
|
||||
"scale": 1.0,
|
||||
"angle": 0.0,
|
||||
"translation": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"brightness": 0.6
|
||||
}
|
||||
},
|
||||
{
|
||||
"Pause": {
|
||||
"secs": 6,
|
||||
"nanos": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"ImageAnimation": {
|
||||
"file": "/usr/share/asusd/anime/custom/sonic-wait.gif",
|
||||
"scale": 0.9,
|
||||
"angle": 0.0,
|
||||
"translation": [
|
||||
3.0,
|
||||
2.0
|
||||
],
|
||||
"time": {
|
||||
"Cycles": 2
|
||||
},
|
||||
"brightness": 0.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
117
design-patterns.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Daemon
|
||||
|
||||
## Controller pattern
|
||||
|
||||
There are a series of traits in the daemon for use with controller objects. Not all traits are required:
|
||||
|
||||
- `Reloadable`, for controllers that need the ability to reload (typically on start)
|
||||
- `ZbusAdd`, for controllers that have zbus derive. These need to run on the zbus server.
|
||||
- `CtrlTask`, for controllers that need to run tasks every loop.
|
||||
- `GetSupported`, see if the hardware/functions this controller requires are supported.
|
||||
|
||||
The first 3 trait objects get owned by the daemon methods that required them, which is why an `Arc<Mutex<T>>` is required.
|
||||
|
||||
Generally the actual controller object will need to live in its own world as its own struct.
|
||||
Then for each trait that is required a new struct is required that can have the trait implemented, and that struct would have a reference to the main controller via `Arc<Mutex<T>>`.
|
||||
|
||||
### Example
|
||||
|
||||
Main controller:
|
||||
|
||||
```rust
|
||||
pub struct CtrlAnime {
|
||||
<things the controller requires>
|
||||
}
|
||||
|
||||
impl CtrlAnime {
|
||||
<functions the controller exposes>
|
||||
}
|
||||
```
|
||||
|
||||
The task trait. There are three ways to implement this:
|
||||
|
||||
```rust
|
||||
pub struct CtrlAnimeTask(Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl crate::CtrlTask for CtrlAnimeTask {
|
||||
// This will run once only
|
||||
fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
if let Ok(lock) = self.inner.try_lock() {
|
||||
<some action>
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This will run until the notification stream closes (which in most cases will be never)
|
||||
fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let connection = Connection::system().await.unwrap();
|
||||
let manager = ManagerProxy::new(&connection).await.unwrap();
|
||||
|
||||
let inner = self.inner.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
// A notification from logind dbus interface
|
||||
if let Ok(p) = manager.receive_prepare_for_sleep().await {
|
||||
// A stream that will continuously output events
|
||||
p.for_each(|_| {
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
// Do stuff here
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
// This task will run every 500 milliseconds
|
||||
fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let inner = self.inner.clone();
|
||||
// This is a provided free trait to help set up a repeating task
|
||||
self.repeating_task(500, executor, move || {
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
// Do stuff here
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The reloader trait
|
||||
```rust
|
||||
pub struct CtrlAnimeReloader(Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl crate::Reloadable for CtrlAnimeReloader {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(lock) = self.inner.try_lock() {
|
||||
<some action>
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The Zbus requirements:
|
||||
```rust
|
||||
pub struct CtrlAnimeZbus(Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlAnimeZbus {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
// This is a provided free helper trait with pre-set body. It will move self in-to.
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Anime", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnimeZbus {
|
||||
fn <zbus method>() {
|
||||
if let Ok(lock) = self.inner.try_lock() {
|
||||
<some action>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The controller can then be added to the daemon parts as required.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rog_anime"
|
||||
version = "1.0.2"
|
||||
version = "1.3.5"
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
@@ -13,19 +13,23 @@ edition = "2018"
|
||||
exclude = ["data"]
|
||||
|
||||
[features]
|
||||
default = ["dbus"]
|
||||
dbus = ["zbus", "zvariant", "zvariant_derive"]
|
||||
default = ["dbus", "detect"]
|
||||
dbus = ["zvariant", "zbus"]
|
||||
detect = ["udev", "sysfs-class"]
|
||||
|
||||
[dependencies]
|
||||
png_pong = "^0.8.0"
|
||||
pix = "0.13"
|
||||
gif = "^0.11.2"
|
||||
log = "*"
|
||||
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
|
||||
glam = { version = "0.14.0", features = ["serde"] }
|
||||
glam = { version = "0.20.5", features = ["serde"] }
|
||||
|
||||
zbus = { version = "^1.9.1", optional = true }
|
||||
zvariant = { version = "^2.6", optional = true }
|
||||
zvariant_derive = { version = "^2.6", optional = true }
|
||||
zvariant = { version = "^3.0", optional = true }
|
||||
zbus = { version = "^2.2", optional = true }
|
||||
|
||||
udev = { version = "^0.6", optional = true }
|
||||
sysfs-class = { version = "^0.1", optional = true }
|
||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
BIN
rog-anime/data/anime/asus/festive/Love u mom.gif
Executable file
|
After Width: | Height: | Size: 153 KiB |
BIN
rog-anime/data/anime/asus/festive/Mother's day.gif
Executable file
|
After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
BIN
rog-anime/data/anime/asus/music/Diamond.gif
Executable file
|
After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |