Compare commits

...

158 Commits

Author SHA1 Message Date
Luke D. Jones
2584d69930 Update deps 2022-07-25 21:38:00 +12:00
Luke D. Jones
523f39cf9c Version bump for RC 2022-07-25 21:34:33 +12:00
Luke D. Jones
669760223e Clean up erroneously included files 2022-07-25 21:32:58 +12:00
Luke D. Jones
71ec13fa9f ROGCC: Attempt to add LED brightness 2022-07-25 21:21:32 +12:00
Luke D. Jones
409528b286 ROGCC: Better control of notifs, add panel_od 2022-07-25 20:55:30 +12:00
Luke D. Jones
17df3cf01d Add rog-control-center to the workspace 2022-07-25 16:43:48 +12:00
Luke D. Jones
808a1d2470 Fix misnamed led dbus method 2022-07-25 14:22:29 +12:00
Luke D. Jones
840c500b5e Switch a keyboard prod_id to enum 2022-07-25 14:07:29 +12:00
Luke D. Jones
42dc360d16 Bump daemon version 2022-07-25 12:43:17 +12:00
Luke D. Jones
f6183597c9 Trial BTreeMap<AuraModeNum, AuraEffect> return for led dbus 2022-07-25 10:26:47 +12:00
Luke D. Jones
79a45c4f10 Add to/from [f32;3] for Colour] 2022-07-25 09:51:25 +12:00
Luke D. Jones
19370215c0 Cleanup 2022-07-24 20:55:09 +12:00
Luke D. Jones
030dd661b8 Switch zbus led_mode to return AuraModeNum 2022-07-24 20:45:20 +12:00
Luke D. Jones
1fc12d9855 Make CurveData members public 2022-07-24 10:50:28 +12:00
Luke D. Jones
6c1b2b70ea Make FanCurveSet members public 2022-07-24 10:47:40 +12:00
Luke D. Jones
23f9af35bf Add Hash derive to Profile 2022-07-24 09:43:22 +12:00
Luke D. Jones
526626b80c Minor tweaks on derives 2022-07-24 09:28:00 +12:00
Luke Jones
10eaaac54b Merge branch 'sova/G713IC-led-support' into 'main'
Add LED support for G713IC

See merge request asus-linux/asusctl!124
2022-07-21 12:39:43 +00:00
SoVa
901a3ddcc9 Add LED support for G713IC 2022-07-21 14:18:33 +02:00
Luke Jones
e6ebf72a11 Merge branch 'main' into 'main'
Fix some typos.

See merge request asus-linux/asusctl!123
2022-07-21 07:59:16 +00:00
Luke D. Jones
cd7e748c88 Prep new release 2022-07-21 19:36:17 +12:00
成超(Cheng Chao)
a313359ef6 Fix some typos. 2022-07-21 12:46:44 +08:00
Luke Jones
f222eef6b7 Update CHANGELOG.md 2022-07-21 03:40:04 +00:00
Luke Jones
e6f3aeb851 Merge branch 'fluke/multi-led-power' into 'main'
Make LED power more universal

Closes #219

See merge request asus-linux/asusctl!122
2022-07-21 03:39:11 +00:00
Luke D. Jones
22605e57cc Properly set full defaults for LED power 2022-07-21 14:56:30 +12:00
Luke D. Jones
02fb7addf4 Make LED power more universal
Closes #219
2022-07-21 14:48:16 +12:00
Luke Jones
bdbb403a0e Merge branch 'fluke/errors' into 'main'
Improve error handling in some cases

See merge request asus-linux/asusctl!121
2022-07-20 09:01:55 +00:00
Luke D. Jones
7a8bede92f Return error if a pixel-gif is larger than the anime-display dimensions 2022-07-20 20:52:03 +12:00
Luke D. Jones
a71a40b509 Make rog-anime more tolerent of faults 2022-07-20 20:17:43 +12:00
Luke D. Jones
42fc5a5392 Minor doc comment change 2022-07-18 15:43:09 +12:00
Luke D. Jones
5017a0ea9b Add extra multizone test 2022-07-18 14:11:54 +12:00
Luke D. Jones
05f7b0060f Prep new release 2022-07-18 14:01:32 +12:00
Luke Jones
1043da5328 Merge branch 'fluke/multizone-defaults' into 'main'
Create defaults on missing zones

See merge request asus-linux/asusctl!120
2022-07-18 01:50:35 +00:00
Luke D. Jones
959ad35afa Create defaults on missing zones 2022-07-18 13:40:42 +12:00
Luke Jones
1e10255d01 Merge branch 'fluke/multizone-fixes' into 'main'
Fluke/multizone fixes

See merge request asus-linux/asusctl!119
2022-07-17 10:02:32 +00:00
Luke D. Jones
c72693bc53 Fix test for CI 2022-07-17 22:02:01 +12:00
Luke D. Jones
2297aad5e5 Fix test 2022-07-17 21:49:15 +12:00
Luke D. Jones
f39c0db680 Add more support detection for bios/system level components 2022-07-17 21:02:39 +12:00
Luke D. Jones
23353c77f3 Add panel_od support 2022-07-17 20:31:47 +12:00
Luke D. Jones
fee1486db6 Correctly save multizone config 2022-07-17 19:23:08 +12:00
Luke D. Jones
39c4253b24 Add GA401I Anime support 2022-07-17 15:08:18 +12:00
Luke D. Jones
84e924f46a Add doc comments for bytes of 0x1866 power control 2022-07-16 22:52:36 +12:00
Luke D. Jones
43364398b4 Add LED support for GX703HS
Closes #207
2022-07-16 15:11:37 +12:00
Luke D. Jones
3f09f221f4 Bump versions 2022-07-16 15:03:42 +12:00
Luke Jones
4ed922154b Merge branch 'validate-anime-brightness' into 'main'
Enforce valid image brightness in daemon and asusctl

See merge request asus-linux/asusctl!118
2022-07-16 03:00:22 +00:00
I Al Istannen
c0e36295b7 Enforce valid image brightness in daemon and asusctl 2022-07-16 03:00:22 +00:00
Luke D. Jones
ef04549c8e Bump versions 2022-07-16 13:45:23 +12:00
Luke D. Jones
a117c300d5 Add support for /etc/asusd/asusd-user-ledmodes.toml 2022-07-16 13:40:57 +12:00
Luke D. Jones
6956f7dffc Re-enable cargo test in pipeline 2022-07-15 23:54:30 +12:00
Luke D. Jones
fe49913550 Updated changelog 2022-07-15 23:54:20 +12:00
Luke D. Jones
89ddade2a3 Updated changelog 2022-07-15 23:54:07 +12:00
Luke Jones
bcb3d53ade Merge branch 'readme_cargo' into 'main'
cargo added to installation

See merge request asus-linux/asusctl!115
2022-07-15 11:31:05 +00:00
Luke Jones
9ab9028d79 Merge branch 'fluke/ga402-anime' into 'main'
First pass of Anime update for new matrix display

See merge request asus-linux/asusctl!113
2022-07-15 11:30:28 +00:00
Luke Jones
8de6447562 Merge branch 'fluke/ga402-anime' into 'fluke/ga402-anime'
AnimeImage patch for GA402

See merge request asus-linux/asusctl!116
2022-07-15 11:23:43 +00:00
I-Al-Istannen
2512cea19d Slightly adjust G402 scaling, add some more documentation 2022-07-15 13:12:34 +02:00
Luke D. Jones
eabe78af71 tmp 2022-07-15 23:00:01 +12:00
Luke D. Jones
16c4ee609e Add GA402 anime-matrix packet unit test 2022-07-15 20:40:16 +12:00
Luke D. Jones
37d586c5de Additional comments in animeimage 2022-07-15 20:38:07 +12:00
Luke D. Jones
e66847c263 Add commenting to AnimeImage to help with GA402 2022-07-15 20:37:30 +12:00
I-Al-Istannen
a2c8a226a4 Complete anime diagonal gif support for GA402 2022-07-15 20:37:30 +12:00
Luke D. Jones
f3f6fadfe2 Fix anime exampels 2022-07-15 20:37:30 +12:00
Luke D. Jones
ff76c356c5 First pass of Anime update for new matrix display
Co-authored with @I-Al-Istannen

Part of #189
2022-07-15 20:37:30 +12:00
Luke D. Jones
a22808aff3 Add note in cargo.toml re: lto 2022-07-15 10:21:11 +12:00
Luke D. Jones
f39fd6dfbb Use hashset in aura power config 2022-07-15 09:30:10 +12:00
Luke Jones
95598f2a76 Merge branch 'fluke/multizone' into 'main'
Multizone support plus rebuild of LED power control

Closes #213

See merge request asus-linux/asusctl!117
2022-07-14 11:20:22 +00:00
Luke D. Jones
535e104ccc Rebuild of LED power control 2022-07-14 23:05:33 +12:00
saswatasarkar13
522cee42e5 cargo added to installation 2022-07-14 14:57:04 +05:30
Luke D. Jones
51656dc13f Initial multizone fixup work 2022-07-14 15:09:28 +12:00
Luke D. Jones
abd412b5d5 Save and restore kb bright only on shutdown/sleep/boot/wak
Closes #213
2022-07-14 13:05:51 +12:00
Luke D. Jones
4d8c05cd92 Add more multizone support 2022-07-14 13:05:41 +12:00
Luke Jones
f2ca8081af Merge branch 'user.sv4.apps-main-patch-52927' into 'main'
Fan curve kernel requirement message update.

See merge request asus-linux/asusctl!112
2022-07-12 01:55:42 +00:00
User
7539011be5 Update main.rs 2022-07-09 10:41:29 +00:00
Luke D. Jones
5117427143 Add GA503R LED modes
Closes #205
2022-06-26 11:57:15 +12:00
Luke D. Jones
e95986b830 Update changelog 2022-06-24 23:12:04 +12:00
Luke D. Jones
5311972345 Set keyboard brightness on resume. Refactor some tasks 2022-06-24 23:09:45 +12:00
Luke D. Jones
967295fba7 Fixes to anime-matrix system thread handling 2022-06-21 23:29:44 +12:00
Luke D. Jones
5403c5fb4f Update changelog 2022-06-20 23:16:25 +12:00
Luke D. Jones
65986c3114 prep new release 2022-06-20 23:13:17 +12:00
Luke D. Jones
13a90b00f3 Adjust how thread exit is handled for anime controller 2022-06-20 22:43:12 +12:00
Luke Jones
2ee7fc9910 Merge branch '191-full-power-states-combinations' into 'main'
Combination for power state leds boot/sleep/all/keys/side LEDS

Closes #191

See merge request asus-linux/asusctl!111
2022-06-19 22:03:23 +00:00
mpiffault
a0a0efabbb Combination for power state leds boot/sleep/all/keys/side LEDS 2022-06-19 22:03:23 +00:00
Luke Jones
9a50278b98 Merge branch 'fluke/async-tasks' into 'main'
Fixes to tasks

See merge request asus-linux/asusctl!110
2022-06-12 03:42:09 +00:00
Luke D. Jones
9519a35e32 Fixes to tasks 2022-06-12 15:34:38 +12:00
Luke Jones
578d5fd541 Merge branch 'fix/199-multizone-commands-not-displayed' into 'main'
Fixes #199 multizone commands not displayed

Closes #199

See merge request asus-linux/asusctl!109
2022-06-12 03:32:11 +00:00
Martin Piffault
642bc5dda1 output multizone commands required 2022-06-07 22:31:53 +02:00
Martin Piffault
88274abdb5 init multizone_led_mode and per_key_led_mode from LaptopLedData 2022-06-07 22:30:37 +02:00
Luke Jones
aea65f5c5f Merge branch 'fluke/async-tasks' into 'main'
Re-enable notif for profile change

See merge request asus-linux/asusctl!108
2022-06-07 00:13:24 +00:00
Luke D. Jones
edfbfde13b Re-enable notif for profile change 2022-06-07 12:08:36 +12:00
Luke Jones
dcc676d60a Merge branch 'fluke/async-tasks' into 'main'
Added tasks for reload keyboard bright, and for charge control

See merge request asus-linux/asusctl!107
2022-06-06 23:53:41 +00:00
Luke D. Jones
561f61116c Added tasks for reload keyboard bright, and for charge control 2022-06-07 11:49:12 +12:00
Luke Jones
6a4594466b Merge branch 'fluke/async-tasks' into 'main'
Async tasks

See merge request asus-linux/asusctl!106
2022-06-06 13:00:10 +00:00
Luke D. Jones
af216ee08c Async tasks 2022-06-07 00:54:33 +12:00
Luke D. Jones
e493113450 update changelog 2022-06-06 18:25:27 +12:00
Luke D. Jones
74e1d5bdc4 Add brightness to anime zbus notif 2022-06-06 18:24:08 +12:00
Luke D. Jones
5c0ad3e590 Re-enable notification on anime power-state change 2022-06-06 18:18:35 +12:00
Luke D. Jones
6e872ecab9 Add diagonal-template.png to rog-anime/data/anime/custom/
Closes #163
2022-06-06 17:20:38 +12:00
Luke D. Jones
46a4cde77f Add G512 to LED support list
Closes #174
2022-06-06 17:02:41 +12:00
Luke D. Jones
fc7c444107 Add GU502LV LED support
Closes #187
2022-06-06 16:57:50 +12:00
Luke D. Jones
822438e0d2 Version bumps 2022-06-06 14:53:30 +12:00
Luke D. Jones
de43a37e9e Use smol async for daemon and daemon-user 2022-06-06 14:34:59 +12:00
Luke D. Jones
d4b2d2f403 Use smol async for asus-notify 2022-06-06 13:57:03 +12:00
Luke D. Jones
6e33eab136 Update anime examples 2022-06-06 13:11:54 +12:00
Luke D. Jones
1e4bc85fee Partial asusd-user update 2022-06-06 01:33:14 +12:00
Luke D. Jones
31fff75f08 Update more deps 2022-06-06 00:41:07 +12:00
Luke D. Jones
f0620154c8 Update changelog 2022-06-06 00:21:05 +12:00
Luke D. Jones
711aa1e4be Add support for GA402R
Closes #196
2022-06-06 00:14:28 +12:00
Luke D. Jones
854f2d75b3 Format 2022-06-06 00:09:23 +12:00
Luke D. Jones
a85e2f6130 Finalise zbus3 conversion 2022-06-06 00:08:59 +12:00
Luke Jones
bac2ba6f09 Merge branch 'mpiffault-main-patch-00521' into 'main'
Update asusd-ledmodes.toml to support Asus Rog Strix G15 G513QM

See merge request asus-linux/asusctl!103
2022-06-05 02:58:49 +00:00
Luke Jones
47e5270f9c Merge branch 'feat/side-leds-toggle-support' into 'main'
Adds support to enable/disable side leds #191

See merge request asus-linux/asusctl!104
2022-06-05 02:58:24 +00:00
Luke Jones
68cbf09e9f Merge branch 'fix/aura-help-options-display' into 'main'
Fixes all available led-mode commands not displaying with `asusctl led-mode --help`

See merge request asus-linux/asusctl!105
2022-06-05 02:54:31 +00:00
Martin Piffault
9f18c88153 fix all available options not being displayed in led-mode help 2022-06-04 14:47:40 +02:00
Martin Piffault
c6caafdcb7 adds support to enable/disable side leds 2022-05-31 17:00:21 +02:00
mpiffault
b22a3e1a59 Update asusd-ledmodes.toml to support Asus Rog Strix G15 G513QM 2022-05-30 08:06:26 +00:00
Luke Jones
b6934bbf63 Merge branch 'as/issue_176_1' into 'main'
Fix for fan_curve related integer divison bug

Closes #176

See merge request asus-linux/asusctl!102
2022-04-08 21:18:47 +00:00
Armas Spann
3cd6eb13a9 fixed interger division bug and related tests, as well as a comment bug 2022-04-07 01:16:06 +02:00
Luke Jones
272be2aaad Merge branch 'as/issue_176' into 'main'
fix for #176 - fancurve percentage check not handled correctly

Closes #176

See merge request asus-linux/asusctl!101
2022-04-05 21:37:23 +00:00
Armas Spann
99dd6ce77f fix for #176 - fancurve percentage check not handled correctly 2022-04-05 23:33:43 +02:00
Luke Jones
fc14455da4 Merge branch 'ankushmishra9-main-patch-66732' into 'main'
Fixed formatting of install instructions

See merge request asus-linux/asusctl!96
2022-02-07 22:59:34 +00:00
Luke Jones
26a52dae23 Merge branch 'main' into 'main'
add g513ic led

See merge request asus-linux/asusctl!94
2022-01-19 20:13:34 +00:00
Ankush Mishra
75864d33a6 Fixed formatting of install instructions 2022-01-19 17:57:06 +00:00
Luke Jones
63a97b6665 Merge branch 'main' into 'main'
fixed a small typo

See merge request asus-linux/asusctl!95
2022-01-15 20:51:52 +00:00
AlenPaulVarghese
21a37a3bb0 fixed a small typo 2022-01-14 18:49:15 +00:00
dada513
de586b5368 add g513ic led 2022-01-11 10:08:45 +00:00
Luke D. Jones
ba40c3f739 Update dependencies 2021-12-19 21:01:47 +13:00
Luke D. Jones
31eff037a2 Bump to version 4.0.7 2021-12-19 21:00:58 +13:00
Luke D. Jones
630dee0b2a Update notes in CLI tool 2021-12-19 20:57:50 +13:00
Luke Jones
9110f06ed5 Update README.md
Remove whitespace and rustup from fedora build section
2021-12-17 21:18:00 +00:00
Luke Jones
2b0eceaa9d Merge branch 'main' into 'main'
Update of README for building and installing

See merge request asus-linux/asusctl!93
2021-12-17 21:16:56 +00:00
Peter Ross
c96e1babe5 Update README.md 2021-12-16 15:23:08 +00:00
Peter Ross
9388cbde5d Update README.md 2021-12-16 15:16:35 +00:00
Luke D. Jones
e739cddd6a Update patch links 2021-12-04 16:04:56 +13:00
Luke Jones
8cee6e0fc4 Remove Ubuntu repo instructions 2021-11-14 21:51:56 +00:00
Luke Jones
bcf516afeb Merge branch 'sonnyp-main-patch-01923' into 'main'
Update kernel support for Linux 5.15

See merge request asus-linux/asusctl!88
2021-11-12 00:23:54 +00:00
Luke Jones
b0e3e81b7f Merge branch 'fix/platform-functions/profile-support' into 'main'
Fix incorrect power profile support validation.

See merge request asus-linux/asusctl!89
2021-11-12 00:23:22 +00:00
Luke Jones
ce6a1215a3 Merge branch 'LordVicky-main-patch-24845' into 'main'
Update asusd-ledmodes.toml to support Asus Rog Strix G15 G513QE

See merge request asus-linux/asusctl!90
2021-11-12 00:22:51 +00:00
LordVicky
f54c1dc7d0 Fix typo 2021-11-09 07:32:39 +00:00
LordVicky
43aaae8d47 Update asusd-ledmodes.toml to support Asus Rog Strix G15 G513QE 2021-11-09 07:28:57 +00:00
Alexander Narsudinov
ca463a2944 Fix incorrect power profile support validation.
Before this patch power profile support validation used wrong flag from PlatformProfileFunctions struct.
2021-11-07 15:11:31 +03:00
Sonny Piers
20e22589dc Update kernel support for Linux 5.15 2021-11-04 11:47:47 +00:00
Luke D. Jones
38d047cb8a Update changelog 2021-11-01 16:03:40 +13:00
Luke D. Jones
1d977199f3 Fix cli for bios/g-sync 2021-11-01 10:57:40 +13:00
Luke Jones
5041019d77 Merge branch 'fluke/anime-cli' into 'main'
Fluke/anime cli

See merge request asus-linux/asusctl!87
2021-10-30 20:28:36 +00:00
Luke D. Jones
aa3835d3b3 Bump versions 2021-10-31 09:24:07 +13:00
Luke D. Jones
678505811d Add additional anime cli commands for image types 2021-10-28 23:43:50 +13:00
Luke D. Jones
a925cbaed5 Bump version 2021-10-27 23:19:31 +13:00
Luke Jones
7d2201d873 Merge branch 'fluke/bugfixes' into 'main'
Add led modes for G513QR

Closes #144 and #143

See merge request asus-linux/asusctl!86
2021-10-27 10:18:31 +00:00
Luke D. Jones
c0c1608d44 Update deps 2021-10-27 23:15:25 +13:00
Luke D. Jones
7bc6c83a04 Check and pass error if charge limit not in 20-100 range
Closes #144
2021-10-27 23:04:44 +13:00
Luke D. Jones
3f0df82f2d Parse percentages in fan curve only if '%' provided otherwise range is 0-255 2021-10-27 22:42:32 +13:00
Luke D. Jones
328ff0251b Add led modes for G513QR
Closes #143
2021-10-27 22:35:48 +13:00
Luke Jones
c52582a413 Merge branch 'fluke/bugfixes' into 'main'
Bugfixes

Closes #139 and #140

See merge request asus-linux/asusctl!85
2021-10-02 07:34:55 +00:00
Luke D. Jones
3aa6eee306 Bugfixes
- Spawn tasks on individual threads
- Don't force a default of fan-curve on reload
- Add missing profile commands
- Begin obsoleting the graphics switch command in favour of supergfxctl
- Slim down the notification daemon to pure ASUS notifications

Bad behaviour in fan-curve new function that was forcing a re-init
to default on reload. Remove this and only save config again after
loading the config file and writing a curve (hidden side effect of
write is that a zeroed array is defaulted to read-from-system - this
needs to be changed too).

Closes #140, #139
2021-10-02 20:31:14 +13:00
Luke Jones
7d47faba0e Merge branch 'plasma-logout' into 'main'
Add kde logout prompt as fallback to gnome-session-quit

See merge request asus-linux/asusctl!82
2021-09-21 08:07:43 +00:00
Luke Jones
f6fb477898 Merge branch 'main' into 'main'
Updated asusd-ledmodes.toml with ROG Strix G713QM

See merge request asus-linux/asusctl!83
2021-09-21 08:06:59 +00:00
Abhijith M
8963960d4b Updated asusd-ledmodes.toml with ROG Strix G713QM 2021-09-21 02:09:53 +00:00
Janghyub Seo
ef973f676b Add kde logout prompt as fallback to gnome-session-quit 2021-09-19 02:13:11 +09:00
Luke D. Jones
812f9ea30e Add lock update 2021-09-16 22:38:09 +12:00
101 changed files with 8701 additions and 4002 deletions

View File

@@ -10,6 +10,7 @@ stages:
test:
script:
- cargo check #+nightly check --features "clippy"
- cargo test
build:
only:

View File

@@ -4,7 +4,101 @@ 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 ]
### Added
- `rog-control-center` has now been moved in to the main workspace due to
the heavy dependencies on most of the rog crates
## [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

2534
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,10 @@
[workspace]
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-supported", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles"]
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-supported", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles", "rog-control-center"]
[profile.release]
lto = true
# thin = 57s, asusd = 9.0M
# fat = 72s, asusd = 6.4M
lto = "fat"
debug = false
opt-level = 3
panic = "abort"

View File

@@ -60,17 +60,17 @@ The defaults are located at `/etc/asusd/asusd-ledmodes.toml`, and on `asusd` sta
Example:
```toml
[[led_data]]
prod_family = "ROG Zephyrus M15"
board_names = ["GU502LU"]
standard = ["Static", "Breathe", "Strobe", "Pulse"]
multizone = false
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 keyboards have 4 zones of LED control, this enables setting a colour in each zone. The keyboard must support this or it has no effect.
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
@@ -90,14 +90,14 @@ where the number is a percentage.
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
- POST sound: this is the sound you hear 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.9.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 included in the "rog" kernels we build for fedora and arch, and will hit kernel 5.15 upstream.
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
@@ -106,7 +106,7 @@ A common use of asusctl is to bind the `fn+f5` (fan) key to `asusctl profile -n`
#### 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/8/29/50) which is still in review as of 29/09/21. As with Profiles, this is included in the kernels we build, and will hit 5.15 kernel upstream.
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:
@@ -293,7 +293,7 @@ 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.
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. It 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.
@@ -356,4 +356,4 @@ Reference to any ASUS products, services, processes, or other information and/or
The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops.
---
---

View File

@@ -11,6 +11,7 @@ datarootdir = $(prefix)/share
libdir = $(exec_prefix)/lib
zshcpl = $(datarootdir)/zsh/site-functions
BIN_ROG := rog-control-center
BIN_C := asusctl
BIN_D := asusd
BIN_U := asusd-user
@@ -39,6 +40,10 @@ distclean:
rm -rf .cargo vendor vendor.tar.xz
install:
$(INSTALL_PROGRAM) "./target/release/$(BIN_ROG)" "$(DESTDIR)$(bindir)/$(BIN_ROG)"
$(INSTALL_DATA) "./rog-control-center/data/$(BIN_ROG).desktop" "$(DESTDIR)$(datarootdir)/applications/$(BIN_ROG).desktop"
$(INSTALL_DATA) "./rog-control-center/data/$(BIN_ROG).png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/$(BIN_ROG).png"
$(INSTALL_PROGRAM) "./target/release/$(BIN_C)" "$(DESTDIR)$(bindir)/$(BIN_C)"
$(INSTALL_PROGRAM) "./target/release/$(BIN_D)" "$(DESTDIR)$(bindir)/$(BIN_D)"
$(INSTALL_PROGRAM) "./target/release/$(BIN_U)" "$(DESTDIR)$(bindir)/$(BIN_U)"
@@ -68,6 +73,10 @@ install:
cd rog-anime/data && find "./anime" -type f -exec install -Dm 755 "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
uninstall:
rm -f "$(DESTDIR)$(bindir)/$(BIN_ROG)"
rm -r "$(DESTDIR)$(datarootdir)/applications/$(BIN_ROG).desktop"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/$(BIN_ROG).png"
rm -f "$(DESTDIR)$(bindir)/$(BIN_C)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_D)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_N)"

View File

@@ -5,10 +5,13 @@
`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 patches required
Now includes a GUI, `rog-control-center`.
1. https://lkml.org/lkml/2021/8/20/232
2. https://lkml.org/lkml/2021/8/18/1022
## 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
@@ -24,10 +27,6 @@ supported (while asusd might still run fine on them). For best support use fedor
Point 4? asusd currently uses a tiny fraction of cpu time, and less than 1Mb of ram, the way
a system-level daemon should.
**NOTICE:**
Various patches are required for keyboard support. See [this post](https://asus-linux.org/blog/updates-2021-05-06/) for details on status and which kernels will have which patches.
## Discord
[Discord server link](https://discord.gg/4ZKGd7Un5t)
@@ -61,27 +60,27 @@ will probably suffer another rename once it becomes generic enough to do so.
# BUILDING
Requirements are rust >= 1.47 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.
Download repositories are available [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/) for the latest versions of Fedora, Ubuntu, and openSUSE.
### Ubuntu
sudo su -c "echo 'deb https://download.opensuse.org/repositories/home:/luke_nukem:/asus/xUbuntu_21.04/ /' > /etc/apt/sources.list.d/luke_nukem.list"
curl -fsSL https://download.opensuse.org/repositories/home:/luke_nukem:/asus/xUbuntu_21.04/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/home_luke_nukem.gpg > /dev/null
sudo apt-get update
sudo apt-get install asusctl dkms-hid-asus-rog
---
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.

View File

@@ -1,20 +1,20 @@
[package]
name = "asus-notify"
version = "3.0.2"
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 = "^1.9"
zbus = "^2.2"
# serialisation
serde_json = "^1.0"
rog_dbus = { path = "../rog-dbus" }
rog_aura = { path = "../rog-aura" }
rog_supported = { path = "../rog-supported" }
rog_profiles = { path = "../rog-profiles" }
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", tag = "2.0.0" }
smol = "^1.2"
[dependencies.notify-rust]
version = "^4.3"

View File

@@ -1,15 +1,16 @@
use notify_rust::{Hint, Notification, NotificationHandle};
use rog_aura::AuraEffect;
use rog_dbus::{DbusProxies, Signals};
use rog_dbus::{
zbus_charge::ChargeProxy, zbus_led::LedProxy, zbus_profile::ProfileProxy,
zbus_rogbios::RogBiosProxy,
};
use rog_profiles::Profile;
use std::error::Error;
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration;
use std::{process, thread};
use supergfxctl::gfx_vendors::{GfxRequiredUserAction, GfxVendors};
use supergfxctl::zbus_proxy::GfxProxy;
use zbus::Connection;
use smol::{future, Executor};
use std::{
error::Error,
sync::{Arc, Mutex},
};
use zbus::export::futures_util::StreamExt;
const NOTIF_HEADER: &str = "ROG Control";
@@ -19,7 +20,7 @@ macro_rules! notify {
notif.close();
}
if let Ok(x) = $notifier($data) {
$last_notif = Some(x);
$last_notif.replace(x);
}
};
}
@@ -34,99 +35,99 @@ macro_rules! base_notification {
};
}
type SharedHandle = Arc<Mutex<Option<NotificationHandle>>>;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("asus-notify version {}", env!("CARGO_PKG_VERSION"));
println!(" rog-dbus version {}", rog_dbus::VERSION);
println!("supergfxctl version {}", supergfxctl::VERSION);
let (proxies, conn) = DbusProxies::new()?;
let signals = Signals::new(&proxies)?;
let last_notification: SharedHandle = Arc::new(Mutex::new(None));
let mut last_notification: Option<NotificationHandle> = None;
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();
let recv = proxies.setup_recv(conn);
let mut err_count = 0;
// 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();
gfx_thread()?;
// 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 {
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(data) = signals.led_mode.try_recv() {
notify!(do_led_notif, last_notification, &data);
}
if let Ok(data) = signals.profile.try_recv() {
notify!(do_thermal_notif, last_notification, &data);
}
if let Ok(data) = signals.charge.try_recv() {
notify!(do_charge_notif, last_notification, &data);
}
smol::block_on(executor.tick());
}
}
fn gfx_thread() -> Result<(), Box<dyn std::error::Error>> {
let mut last_notification: Option<NotificationHandle> = None;
let conn = Connection::new_system()?;
let proxy = GfxProxy::new(&conn)?;
let (tx1, rx1) = channel();
proxy.connect_notify_gfx(tx1)?;
let (tx2, rx2) = channel();
proxy.connect_notify_action(tx2)?;
thread::spawn(move || loop {
if proxy
.next_signal()
.map_err(|e| println!("Error: {}", e))
.is_err()
{
break;
}
if let Ok(data) = rx1.try_recv() {
notify!(do_gfx_notif, last_notification, &data);
}
if let Ok(data) = rx2.try_recv() {
match data {
GfxRequiredUserAction::Logout | GfxRequiredUserAction::Reboot => {
do_gfx_action_notif(&data)
.map_err(|e| {
println!("Error: {}", e);
})
.ok();
}
GfxRequiredUserAction::Integrated => {
base_notification!(
"You must be in integrated mode first to switch to the requested mode"
)
.map_err(|e| {
println!("Error: {}", e);
})
.ok();
}
GfxRequiredUserAction::None => {}
}
}
});
Ok(())
}
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
let icon = match profile {
Profile::Balanced => "asus_notif_yellow",
@@ -160,51 +161,6 @@ fn do_charge_notif(limit: &u8) -> Result<NotificationHandle, notify_rust::error:
base_notification!(&format!("Battery charge limit changed to {}", limit))
}
fn do_gfx_notif(vendor: &GfxVendors) -> Result<NotificationHandle, notify_rust::error::Error> {
let icon = match vendor {
GfxVendors::Nvidia => "/usr/share/icons/hicolor/scalable/status/gpu-nvidia.svg",
GfxVendors::Integrated => "/usr/share/icons/hicolor/scalable/status/gpu-integrated.svg",
GfxVendors::Compute => "/usr/share/icons/hicolor/scalable/status/gpu-compute.svg",
GfxVendors::Vfio => "/usr/share/icons/hicolor/scalable/status/gpu-vfio.svg",
GfxVendors::Hybrid => "/usr/share/icons/hicolor/scalable/status/gpu-hybrid.svg",
};
Notification::new()
.summary(NOTIF_HEADER)
.body(&format!(
"Graphics mode changed to {}",
<&str>::from(vendor)
))
.timeout(2000)
.icon(icon)
.show()
}
fn do_gfx_action_notif(vendor: &GfxRequiredUserAction) -> Result<(), notify_rust::error::Error> {
let mut notif = Notification::new()
.summary(NOTIF_HEADER)
.timeout(2000)
.urgency(notify_rust::Urgency::Critical)
.icon("/usr/share/icons/hicolor/scalable/status/notification-reboot.svg")
.finalize();
if matches!(vendor, GfxRequiredUserAction::Logout) {
notif.action("logout", "Logout now?");
} else if matches!(vendor, GfxRequiredUserAction::Reboot) {
notif.action("reboot", "Reboot now?");
}
notif.body("Graphics mode changed");
notif.show()?.wait_for_action(|action| match action {
"logout" => {
process::Command::new("gnome-session-quit").spawn().ok();
}
"reboot" => {
process::Command::new("systemctl")
.arg("reboot")
.spawn()
.ok();
}
_ => (),
});
Ok(())
fn do_post_sound_notif(on: &bool) -> Result<NotificationHandle, notify_rust::error::Error> {
base_notification!(&format!("BIOS Post sound {}", on))
}

View File

@@ -1,13 +1,13 @@
[package]
name = "asusctl"
version = "4.0.3"
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]
zbus = "^1.9.1"
zbus = "^2.2"
rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_dbus = { path = "../rog-dbus" }
@@ -15,13 +15,12 @@ rog_profiles = { path = "../rog-profiles" }
rog_supported = { path = "../rog-supported" }
daemon = { path = "../daemon" }
gumdrop = "^0.8"
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", tag = "2.0.0" }
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"

View File

@@ -1,10 +1,10 @@
use std::{env, error::Error, path::Path, process::exit};
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
use rog_dbus::RogDbusClient;
use rog_anime::{usb::get_anime_type, AnimeDiagonal, AnimeType};
use rog_dbus::RogDbusClientBlocking;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClient::new().unwrap();
let (client, _) = RogDbusClientBlocking::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 3 {
@@ -13,13 +13,19 @@ fn main() -> Result<(), Box<dyn Error>> {
exit(-1);
}
let matrix =
AnimeDiagonal::from_png(Path::new(&args[1]), None, args[2].parse::<f32>().unwrap())?;
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(<AnimeDataBuffer>::from(&matrix))
.write(matrix.into_data_buffer(anime_type)?)
.unwrap();
Ok(())

View File

@@ -1,7 +1,7 @@
use std::{thread::sleep, time::Duration};
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
use rog_dbus::RogDbusClient;
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::RogDbusClient;
// 74w x 36h diagonal used by the windows app
fn main() {
let (client, _) = RogDbusClient::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));
}
}

View File

@@ -1,10 +1,10 @@
use std::{env, path::Path, thread::sleep};
use rog_anime::{ActionData, ActionLoader, Sequences};
use rog_dbus::RogDbusClient;
use rog_anime::{usb::get_anime_type, ActionData, ActionLoader, Sequences};
use rog_dbus::RogDbusClientBlocking;
fn main() {
let (client, _) = RogDbusClient::new().unwrap();
let (client, _) = RogDbusClientBlocking::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 3 {
@@ -14,7 +14,8 @@ 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,
&ActionLoader::AsusAnimation {

View File

@@ -1,5 +1,6 @@
use rog_anime::{AnimeDataBuffer, AnimeGrid};
use rog_dbus::RogDbusClient;
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::RogDbusClient;
// 74w x 36h diagonal used by the windows app
fn main() {
let (client, _) = RogDbusClient::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();
}

View File

@@ -1,128 +1,129 @@
use rog_anime::AnimeDataBuffer;
use rog_dbus::RogDbusClient;
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, _) = RogDbusClient::new().unwrap();
let mut matrix = AnimeDataBuffer::new();
matrix.get_mut()[1] = 100; // start = 1
for n in matrix.get_mut()[2..32].iter_mut() {
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.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() {
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.get_mut()[1244] = 100; // end
matrix.data_mut()[1244] = 100; // end
println!("{:?}", &matrix);
client.proxies().anime().write(matrix).unwrap();

View File

@@ -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::RogDbusClient;
use rog_dbus::RogDbusClientBlocking;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClient::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(())

View File

@@ -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::RogDbusClient;
use rog_dbus::RogDbusClientBlocking;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClient::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));
}

View File

@@ -1,54 +1,4 @@
use gumdrop::Options;
use rog_aura::error::Error;
use std::str::FromStr;
#[derive(Copy, Clone, Debug)]
pub enum AnimeStatusValue {
On,
Off,
}
impl FromStr for AnimeStatusValue {
type Err = Error;
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(Error::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 image 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,
}

View File

@@ -2,6 +2,70 @@ use gumdrop::Options;
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>,
@@ -50,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")]
@@ -59,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)]
@@ -67,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)]
@@ -77,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)]
@@ -89,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")]
@@ -152,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 {
@@ -168,6 +259,7 @@ impl From<&SingleColour> for AuraEffect {
fn from(aura: &SingleColour) -> Self {
Self {
colour1: aura.colour,
zone: aura.zone,
..Default::default()
}
}
@@ -177,6 +269,7 @@ impl From<&SingleSpeed> for AuraEffect {
fn from(aura: &SingleSpeed) -> Self {
Self {
speed: aura.speed,
zone: aura.zone,
..Default::default()
}
}
@@ -187,6 +280,7 @@ impl From<&SingleColourSpeed> for AuraEffect {
Self {
colour1: aura.colour,
speed: aura.speed,
zone: aura.zone,
..Default::default()
}
}
@@ -197,6 +291,7 @@ impl From<&TwoColourSpeed> for AuraEffect {
Self {
colour1: aura.colour,
colour2: aura.colour2,
zone: aura.zone,
..Default::default()
}
}
@@ -207,6 +302,7 @@ impl From<&SingleSpeedDirection> for AuraEffect {
Self {
speed: aura.speed,
direction: aura.direction,
zone: aura.zone,
..Default::default()
}
}
@@ -275,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
}
}

View File

@@ -1,10 +1,9 @@
use crate::{
anime_cli::AnimeCommand,
aura_cli::{LedBrightness, SetAuraBuiltin},
aura_cli::{LedBrightness, LedPowerCommand1, LedPowerCommand2, SetAuraBuiltin},
profiles_cli::{FanCurveCommand, ProfileCommand},
};
use gumdrop::Options;
use supergfxctl::gfx_vendors::GfxVendors;
#[derive(Default, Options)]
pub struct CliStart {
@@ -20,7 +19,7 @@ pub struct CliStart {
pub next_kbd_bright: bool,
#[options(help = "Toggle to previous keyboard brightness")]
pub prev_kbd_bright: bool,
#[options(meta = "", help = "<20-100>")]
#[options(meta = "", help = "Set your battery charge limit <20-100>")]
pub chg_limit: Option<u8>,
#[options(command)]
pub command: Option<CliCommand>,
@@ -30,11 +29,15 @@ pub struct CliStart {
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 suported")]
#[options(help = "Set, select, or modify fan curves if supported")]
FanCurve(FanCurveCommand),
#[options(help = "Set the graphics mode")]
#[options(help = "Set the graphics mode (obsoleted by supergfxctl)")]
Graphics(GraphicsCommand),
#[options(name = "anime", help = "Manage AniMe Matrix")]
Anime(AnimeCommand),
@@ -50,16 +53,6 @@ pub struct LedModeCommand {
pub next_mode: bool,
#[options(help = "switch to previous aura mode")]
pub prev_mode: bool,
#[options(
meta = "",
help = "set the keyboard LED to enabled while the device is awake"
)]
pub awake_enable: Option<bool>,
#[options(
meta = "",
help = "set the keyboard LED suspend animation to enabled while the device is suspended"
)]
pub sleep_enable: Option<bool>,
#[options(command)]
pub command: Option<SetAuraBuiltin>,
}
@@ -68,17 +61,6 @@ pub struct LedModeCommand {
pub struct GraphicsCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(
meta = "",
help = "Set graphics mode: <nvidia, hybrid, compute, integrated>"
)]
pub mode: Option<GfxVendors>,
#[options(help = "Get the current mode")]
pub get: bool,
#[options(help = "Get the current power status")]
pub pow: bool,
#[options(help = "Do not ask for confirmation")]
pub force: bool,
}
#[derive(Options, Debug)]
@@ -87,18 +69,29 @@ pub struct BiosCommand {
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, help = "read bios POST sound")]
#[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, help = "get GPU mode")]
#[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,
}

View File

@@ -1,31 +1,32 @@
mod anime_cli;
mod aura_cli;
mod cli_opts;
mod profiles_cli;
use std::convert::TryFrom;
use std::process::Command;
use std::thread::sleep;
use std::{env::args, path::Path};
use crate::aura_cli::{LedBrightness, SetAuraBuiltin};
use crate::cli_opts::*;
use anime_cli::{AnimeActions, AnimeCommand};
use aura_cli::{LedPowerCommand1, LedPowerCommand2};
use gumdrop::{Opt, Options};
use anime_cli::{AnimeActions, AnimeCommand};
use profiles_cli::{FanCurveCommand, ProfileCommand};
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2, ANIME_DATA_LEN};
use rog_anime::usb::get_anime_type;
use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, Vec2};
use rog_aura::usb::{AuraDev1866, AuraDev19b6, AuraDevice, AuraPowerDev};
use rog_aura::{self, AuraEffect};
use rog_dbus::RogDbusClient;
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::error::ProfileError;
use rog_supported::SupportedFunctions;
use rog_supported::{
AnimeSupportedFunctions, LedSupportedFunctions, PlatformProfileFunctions,
RogBiosSupportedFunctions,
};
use std::process::Command;
use std::{env::args, path::Path, sync::mpsc::channel};
use supergfxctl::{
gfx_vendors::GfxRequiredUserAction,
special::{get_asus_gsync_gfx_mode, has_asus_gsync_gfx_mode},
zbus_proxy::GfxProxy,
};
use zbus::Connection;
const CONFIG_ADVICE: &str = "A config file need to be removed so a new one can be generated";
use crate::aura_cli::LedBrightness;
use crate::cli_opts::*;
mod anime_cli;
mod aura_cli;
mod cli_opts;
mod profiles_cli;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = args().skip(1).collect();
@@ -48,7 +49,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
let (dbus, _) = RogDbusClient::new()
let (dbus, _) = RogDbusClientBlocking::new()
.map_err(|e| {
print_error_help(Box::new(e), None);
std::process::exit(3);
@@ -58,7 +59,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let supported = dbus
.proxies()
.supported()
.get_supported_functions()
.supported_functions()
.map_err(|e| {
print_error_help(Box::new(e), None);
std::process::exit(4);
@@ -80,15 +81,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
fn print_error_help(err: Box<dyn std::error::Error>, supported: Option<&SupportedFunctions>) {
if do_diagnose("asusd") {
println!("\nError: {}\n", err);
print_versions();
check_service("asusd");
println!("\nError: {}\n", err);
print_versions();
println!();
print_laptop_info();
if let Some(supported) = supported {
println!();
print_laptop_info();
if let Some(supported) = supported {
println!();
println!("Supported laptop functions:\n\n{}", supported);
}
println!("Supported laptop functions:\n\n{}", supported);
}
}
@@ -102,7 +102,6 @@ fn print_versions() {
println!(" rog-dbus v{}", rog_dbus::VERSION);
println!(" rog-profiles v{}", rog_profiles::VERSION);
println!("rog-supported v{}", rog_supported::VERSION);
println!(" supergfxctl v{}", supergfxctl::VERSION);
}
fn print_laptop_info() {
@@ -114,7 +113,7 @@ fn print_laptop_info() {
println!("Board name: {}", board_name.trim());
}
fn do_diagnose(name: &str) -> bool {
fn check_service(name: &str) -> bool {
if name != "asusd" && !check_systemd_unit_enabled(name) {
println!(
"\n\x1b[0;31m{} is not enabled, enable it with `systemctl enable {}\x1b[0m",
@@ -127,13 +126,6 @@ fn do_diagnose(name: &str) -> bool {
name, name
);
return true;
} else {
println!("\nSome error happened (sorry)");
println!(
"Please use `systemctl status {}` and `journalctl -b -u {}` for more information",
name, name
);
println!("{}", CONFIG_ADVICE);
}
false
}
@@ -141,18 +133,17 @@ fn do_diagnose(name: &str) -> bool {
fn do_parsed(
parsed: &CliStart,
supported: &SupportedFunctions,
dbus: &RogDbusClient,
dbus: &RogDbusClientBlocking,
) -> Result<(), Box<dyn std::error::Error>> {
match &parsed.command {
Some(CliCommand::LedMode(mode)) => handle_led_mode(dbus, &supported.keyboard_led, mode)?,
Some(CliCommand::LedPow1(pow)) => handle_led_power1(dbus, &supported.keyboard_led, pow)?,
Some(CliCommand::LedPow2(pow)) => handle_led_power2(dbus, &supported.keyboard_led, pow)?,
Some(CliCommand::Profile(cmd)) => handle_profile(dbus, &supported.platform_profile, cmd)?,
Some(CliCommand::FanCurve(cmd)) => {
handle_fan_curve(dbus, &supported.platform_profile, cmd)?
}
Some(CliCommand::Graphics(cmd)) => do_gfx(cmd).map_err(|err| {
do_gfx_diagnose();
err
})?,
Some(CliCommand::Graphics(_)) => do_gfx()?,
Some(CliCommand::Anime(cmd)) => handle_anime(dbus, &supported.anime_ctrl, cmd)?,
Some(CliCommand::Bios(cmd)) => handle_bios_option(dbus, &supported.rog_bios_ctrl, cmd)?,
None => {
@@ -166,8 +157,29 @@ fn do_parsed(
println!("{}", CliStart::usage());
println!();
if let Some(cmdlist) = CliStart::command_list() {
println!("{}", cmdlist);
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_string()).collect();
for command in commands.iter().filter(|command| {
if !matches!(
supported.keyboard_led.prod_id,
AuraDevice::X1854 | AuraDevice::X1869 | AuraDevice::X1866
) && command.trim().starts_with("led-pow-1")
{
return false;
}
if supported.keyboard_led.prod_id != AuraDevice::X19B6
&& command.trim().starts_with("led-pow-2")
{
return false;
}
true
}) {
println!("{}", command);
}
}
println!("\nExtra help can be requested on any command or subcommand:");
println!(" asusctl led-mode --help");
println!(" asusctl led-mode static --help");
}
}
}
@@ -175,13 +187,13 @@ fn do_parsed(
if let Some(brightness) = &parsed.kbd_bright {
match brightness.level() {
None => {
let level = dbus.proxies().led().get_led_brightness()?;
println!("Current keyboard led brightness: {}", level.to_string());
let level = dbus.proxies().led().led_brightness()?;
println!("Current keyboard led brightness: {}", level);
}
Some(level) => dbus
.proxies()
.led()
.set_led_brightness(<rog_aura::LedBrightness>::from(level))?,
.set_brightness(<rog_aura::LedBrightness>::from(level))?,
}
}
@@ -198,107 +210,54 @@ fn do_parsed(
}
if let Some(chg_limit) = parsed.chg_limit {
dbus.proxies().charge().write_limit(chg_limit)?;
dbus.proxies().charge().set_limit(chg_limit)?;
}
Ok(())
}
fn do_gfx_diagnose() {
println!("\nGraphics mode change error.");
do_diagnose("supergfxd");
println!();
}
fn do_gfx(command: &GraphicsCommand) -> Result<(), Box<dyn std::error::Error>> {
if command.mode.is_none() && !command.get && !command.pow && !command.force || command.help {
println!("{}", command.self_usage());
}
let conn = Connection::new_system()?;
let proxy = GfxProxy::new(&conn)?;
let (tx, rx) = channel();
proxy.connect_notify_action(tx)?;
if let Some(mode) = command.mode {
if has_asus_gsync_gfx_mode() && get_asus_gsync_gfx_mode()? == 1 {
println!("You can not change modes until you turn dedicated/G-Sync off and reboot");
std::process::exit(-1);
}
println!(
"If anything fails check `journalctl -b -u asusd` and `journalctl -b -u supergfxd`\n"
);
proxy.gfx_write_mode(&mode)?;
loop {
proxy.next_signal()?;
if let Ok(res) = rx.try_recv() {
match res {
GfxRequiredUserAction::Integrated => {
println!(
"You must change to Integrated before you can change to {}",
<&str>::from(mode)
);
}
GfxRequiredUserAction::Logout | GfxRequiredUserAction::Reboot => {
println!(
"Graphics mode changed to {}. User action required is: {}",
<&str>::from(mode),
<&str>::from(&res)
);
}
GfxRequiredUserAction::None => {
println!("Graphics mode changed to {}", <&str>::from(mode));
}
}
}
std::process::exit(0)
}
}
if command.get {
let res = proxy.gfx_get_mode()?;
println!("Current graphics mode: {}", <&str>::from(res));
}
if command.pow {
let res = proxy.gfx_get_pwr()?;
println!("Current power status: {}", <&str>::from(&res));
}
fn do_gfx() -> Result<(), Box<dyn std::error::Error>> {
println!("Please use supergfxctl for graphics switching. supergfxctl is the result of making asusctl graphics switching generic so all laptops can use it");
println!("This command will be removed in future");
Ok(())
}
fn handle_anime(
dbus: &RogDbusClient,
dbus: &RogDbusClientBlocking,
_supported: &AnimeSupportedFunctions,
cmd: &AnimeCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if (cmd.command.is_none() && cmd.boot.is_none() && cmd.turn.is_none()) || cmd.help {
if (cmd.command.is_none()
&& cmd.enable.is_none()
&& cmd.boot_enable.is_none()
&& cmd.brightness.is_none())
|| cmd.help
{
println!("Missing arg or command\n\n{}", cmd.self_usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
}
if let Some(anime_turn) = cmd.turn {
dbus.proxies().anime().set_led_power(anime_turn.into())?
if let Some(anime_turn) = cmd.enable {
dbus.proxies().anime().set_on_off(anime_turn)?
}
if let Some(anime_boot) = cmd.boot {
dbus.proxies()
.anime()
.set_system_animations(anime_boot.into())?
if let Some(anime_boot) = cmd.boot_enable {
dbus.proxies().anime().set_boot_on_off(anime_boot)?
}
if let Some(bright) = cmd.brightness {
verify_brightness(bright);
dbus.proxies().anime().set_brightness(bright)?
}
if cmd.clear {
let anime_type = get_anime_type()?;
let data = vec![0u8; anime_type.data_length()];
let tmp = AnimeDataBuffer::from_vec(anime_type, data)?;
dbus.proxies().anime().write(tmp)?;
}
if let Some(action) = cmd.command.as_ref() {
let anime_type = get_anime_type()?;
match action {
AnimeActions::Leds(anime_leds) => {
let data = AnimeDataBuffer::from_vec(
[anime_leds.led_brightness(); ANIME_DATA_LEN].to_vec(),
);
dbus.proxies().anime().write(data)?;
}
AnimeActions::Image(image) => {
if image.help_requested() || image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage());
@@ -307,6 +266,7 @@ fn handle_anime(
}
std::process::exit(1);
}
verify_brightness(image.bright);
let matrix = AnimeImage::from_png(
Path::new(&image.path),
@@ -314,28 +274,120 @@ fn handle_anime(
image.angle,
Vec2::new(image.x_pos, image.y_pos),
image.bright,
anime_type,
)?;
dbus.proxies()
.anime()
.write(<AnimeDataBuffer>::from(&matrix))?;
.write(<AnimeDataBuffer>::try_from(&matrix)?)?;
}
AnimeActions::PixelImage(image) => {
if image.help_requested() || image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage());
if let Some(lst) = image.self_command_list() {
println!("\n{}", lst);
}
std::process::exit(1);
}
verify_brightness(image.bright);
let matrix = AnimeDiagonal::from_png(
Path::new(&image.path),
None,
image.bright,
anime_type,
)?;
dbus.proxies()
.anime()
.write(matrix.into_data_buffer(anime_type)?)?;
}
AnimeActions::Gif(gif) => {
if gif.help_requested() || gif.path.is_empty() {
println!("Missing arg or command\n\n{}", gif.self_usage());
if let Some(lst) = gif.self_command_list() {
println!("\n{}", lst);
}
std::process::exit(1);
}
verify_brightness(gif.bright);
let matrix = AnimeGif::from_gif(
Path::new(&gif.path),
gif.scale,
gif.angle,
Vec2::new(gif.x_pos, gif.y_pos),
AnimTime::Count(1),
gif.bright,
anime_type,
)?;
let mut loops = gif.loops as i32;
loop {
for frame in matrix.frames() {
dbus.proxies().anime().write(frame.frame().clone())?;
sleep(frame.delay());
}
if loops >= 0 {
loops -= 1;
}
if loops == 0 {
break;
}
}
}
AnimeActions::PixelGif(gif) => {
if gif.help_requested() || gif.path.is_empty() {
println!("Missing arg or command\n\n{}", gif.self_usage());
if let Some(lst) = gif.self_command_list() {
println!("\n{}", lst);
}
std::process::exit(1);
}
verify_brightness(gif.bright);
let matrix = AnimeGif::from_diagonal_gif(
Path::new(&gif.path),
AnimTime::Count(1),
gif.bright,
anime_type,
)?;
let mut loops = gif.loops as i32;
loop {
for frame in matrix.frames() {
dbus.proxies().anime().write(frame.frame().clone())?;
sleep(frame.delay());
}
if loops >= 0 {
loops -= 1;
}
if loops == 0 {
break;
}
}
}
}
}
Ok(())
}
fn verify_brightness(brightness: f32) {
if brightness < 0.0 || brightness > 1.0 {
println!(
"Image and global brightness must be between 0.0 and 1.0 (inclusive), was {}",
brightness
);
std::process::exit(1);
}
}
fn handle_led_mode(
dbus: &RogDbusClient,
dbus: &RogDbusClientBlocking,
supported: &LedSupportedFunctions,
mode: &LedModeCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if mode.command.is_none()
&& !mode.prev_mode
&& !mode.next_mode
&& mode.sleep_enable.is_none()
&& mode.awake_enable.is_none()
{
if mode.command.is_none() && !mode.prev_mode && !mode.next_mode {
if !mode.help {
println!("Missing arg or command\n");
}
@@ -346,11 +398,14 @@ fn handle_led_mode(
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_string()).collect();
for command in commands.iter().filter(|command| {
for mode in &supported.stock_led_modes {
if command.contains(&<&str>::from(mode).to_lowercase()) {
if command
.trim()
.starts_with(&<&str>::from(mode).to_lowercase())
{
return true;
}
}
if supported.multizone_led_mode {
if !supported.multizone_led_mode.is_empty() && command.trim().starts_with("multi") {
return true;
}
false
@@ -376,39 +431,173 @@ fn handle_led_mode(
println!("{}", mode.self_usage());
return Ok(());
}
match mode {
SetAuraBuiltin::MultiStatic(_) | SetAuraBuiltin::MultiBreathe(_) => {
let zones = <Vec<AuraEffect>>::from(mode);
for eff in zones {
dbus.proxies().led().set_led_mode(&eff)?
dbus.proxies()
.led()
.set_led_mode(&<AuraEffect>::from(mode))?;
}
Ok(())
}
fn handle_led_power1(
dbus: &RogDbusClientBlocking,
supported: &LedSupportedFunctions,
power: &LedPowerCommand1,
) -> Result<(), Box<dyn std::error::Error>> {
if power.awake.is_none()
&& power.sleep.is_none()
&& power.boot.is_none()
&& power.keyboard.is_none()
&& power.lightbar.is_none()
{
if !power.help {
println!("Missing arg or command\n");
}
println!("{}\n", power.self_usage());
return Ok(());
}
if !matches!(
supported.prod_id,
AuraDevice::X1854 | AuraDevice::X1869 | AuraDevice::X1866
) {
println!("These options are for keyboards of product ID 0x1866 only");
return Ok(());
}
let mut enabled: Vec<AuraDev1866> = Vec::new();
let mut disabled: Vec<AuraDev1866> = Vec::new();
let mut check = |e: Option<bool>, a: AuraDev1866| {
if let Some(arg) = e {
if arg {
enabled.push(a);
} else {
disabled.push(a);
}
}
};
check(power.awake, AuraDev1866::Awake);
check(power.boot, AuraDev1866::Boot);
check(power.sleep, AuraDev1866::Sleep);
check(power.keyboard, AuraDev1866::Keyboard);
check(power.lightbar, AuraDev1866::Lightbar);
let data = AuraPowerDev {
x1866: enabled,
x19b6: vec![],
};
dbus.proxies().led().set_leds_power(data, true)?;
let data = AuraPowerDev {
x1866: disabled,
x19b6: vec![],
};
dbus.proxies().led().set_leds_power(data, false)?;
Ok(())
}
fn handle_led_power2(
dbus: &RogDbusClientBlocking,
supported: &LedSupportedFunctions,
power: &LedPowerCommand2,
) -> Result<(), Box<dyn std::error::Error>> {
if power.command().is_none() {
if !power.help {
println!("Missing arg or command\n");
}
println!("{}\n", power.self_usage());
println!("Commands available");
if let Some(cmdlist) = LedPowerCommand2::command_list() {
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_string()).collect();
for command in commands.iter() {
println!("{}", command);
}
}
println!("\nHelp can also be requested on commands, e.g: boot --help");
return Ok(());
}
if let Some(pow) = power.command.as_ref() {
if pow.help_requested() {
println!("{}", pow.self_usage());
return Ok(());
}
if supported.prod_id != AuraDevice::X19B6 {
println!("This option applies only to keyboards with product ID 0x19b6")
}
let mut enabled: Vec<AuraDev19b6> = Vec::new();
let mut disabled: Vec<AuraDev19b6> = Vec::new();
let mut check = |e: Option<bool>, a: AuraDev19b6| {
if let Some(arg) = e {
if arg {
enabled.push(a);
} else {
disabled.push(a);
}
}
_ => dbus
.proxies()
.led()
.set_led_mode(&<AuraEffect>::from(mode))?,
};
match pow {
aura_cli::SetAuraEnabled::Boot(arg) => {
check(arg.keyboard, AuraDev19b6::BootKeyb);
check(arg.logo, AuraDev19b6::BootLogo);
check(arg.lightbar, AuraDev19b6::BootBar);
}
aura_cli::SetAuraEnabled::Sleep(arg) => {
check(arg.keyboard, AuraDev19b6::SleepKeyb);
check(arg.logo, AuraDev19b6::SleepLogo);
check(arg.lightbar, AuraDev19b6::SleepBar);
}
aura_cli::SetAuraEnabled::Awake(arg) => {
check(arg.keyboard, AuraDev19b6::AwakeKeyb);
check(arg.logo, AuraDev19b6::AwakeLogo);
check(arg.lightbar, AuraDev19b6::AwakeBar);
}
aura_cli::SetAuraEnabled::Shutdown(arg) => {
check(arg.keyboard, AuraDev19b6::ShutdownKeyb);
check(arg.logo, AuraDev19b6::ShutdownLogo);
check(arg.lightbar, AuraDev19b6::ShutdownBar);
}
}
}
if let Some(enable) = mode.awake_enable {
dbus.proxies().led().set_awake_enabled(enable)?;
}
if !enabled.is_empty() {
let data = AuraPowerDev {
x1866: vec![],
x19b6: enabled,
};
dbus.proxies().led().set_leds_power(data, true)?;
}
if let Some(enable) = mode.sleep_enable {
dbus.proxies().led().set_sleep_enabled(enable)?;
if !disabled.is_empty() {
let data = AuraPowerDev {
x1866: vec![],
x19b6: disabled,
};
dbus.proxies().led().set_leds_power(data, false)?;
}
}
Ok(())
}
fn handle_profile(
dbus: &RogDbusClient,
_supported: &PlatformProfileFunctions,
dbus: &RogDbusClientBlocking,
supported: &PlatformProfileFunctions,
cmd: &ProfileCommand,
) -> Result<(), Box<dyn std::error::Error>> {
println!("Warning: Profiles now depend on power-profiles-daemon v0.9+");
println!("Warning: Fan-curve support is coming in a 4.1.x release");
if !cmd.next && !cmd.list {
if !supported.platform_profile {
println!("Profiles not supported by either this kernel or by the laptop.");
return Err(ProfileError::NotSupported.into());
}
if !cmd.next && !cmd.list && cmd.profile_set.is_none() && !cmd.profile_get {
if !cmd.help {
println!("Missing arg or command\n");
}
@@ -417,28 +606,39 @@ fn handle_profile(
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
// println!("Note: turbo, frequency, fan preset and fan curve options will apply to");
// println!(" to the currently active profile unless a profile name is specified");
std::process::exit(1);
}
if cmd.next {
dbus.proxies().profile().next_profile()?;
} else if let Some(profile) = cmd.profile_set {
dbus.proxies().profile().set_active_profile(profile)?;
}
if cmd.list {
let res = dbus.proxies().profile().profiles()?;
res.iter().for_each(|p| println!("{:?}", p));
}
if cmd.profile_get {
let res = dbus.proxies().profile().active_profile()?;
println!("Active profile is {:?}", res);
}
Ok(())
}
fn handle_fan_curve(
dbus: &RogDbusClient,
_supported: &PlatformProfileFunctions,
dbus: &RogDbusClientBlocking,
supported: &PlatformProfileFunctions,
cmd: &FanCurveCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if !supported.fan_curves {
println!("Fan-curves not supported by either this kernel or by the laptop.");
println!("This requires kernel 5.17 or the fan curve patch listed in the readme.");
return Err(ProfileError::NotSupported.into());
}
if !cmd.get_enabled && !cmd.default && cmd.mod_profile.is_none() {
if !cmd.help {
println!("Missing arg or command\n");
@@ -451,7 +651,8 @@ fn handle_fan_curve(
std::process::exit(1);
}
if (cmd.enabled.is_some() || cmd.fan.is_some() || cmd.data.is_some()) && cmd.mod_profile.is_none()
if (cmd.enabled.is_some() || cmd.fan.is_some() || cmd.data.is_some())
&& cmd.mod_profile.is_none()
{
println!("--enabled, --fan, and --data options require --mod-profile");
std::process::exit(666);
@@ -482,7 +683,7 @@ fn handle_fan_curve(
if let Some(mut curve) = cmd.data.clone() {
let fan = cmd.fan.unwrap_or_default();
curve.set_fan(fan);
dbus.proxies().profile().set_fan_curve(curve, profile)?;
dbus.proxies().profile().set_fan_curve(profile, curve)?;
}
}
@@ -490,7 +691,7 @@ fn handle_fan_curve(
}
fn handle_bios_option(
dbus: &RogDbusClient,
dbus: &RogDbusClientBlocking,
supported: &RogBiosSupportedFunctions,
cmd: &BiosCommand,
) -> Result<(), Box<dyn std::error::Error>> {
@@ -498,7 +699,9 @@ fn handle_bios_option(
if (cmd.dedicated_gfx_set.is_none()
&& !cmd.dedicated_gfx_get
&& cmd.post_sound_set.is_none()
&& !cmd.post_sound_get)
&& !cmd.post_sound_get
&& cmd.panel_overdrive_set.is_none()
&& !cmd.panel_overdrive_get)
|| cmd.help
{
println!("Missing arg or command\n");
@@ -509,23 +712,25 @@ fn handle_bios_option(
.collect();
for line in usage.iter().filter(|line| {
!line.contains("sound") && !supported.post_sound_toggle
|| !line.contains("GPU") && !supported.dedicated_gfx_toggle
line.contains("sound") && supported.post_sound
|| line.contains("GPU") && supported.dedicated_gfx
|| line.contains("panel") && supported.panel_overdrive
}) {
println!("{}", line);
}
}
if let Some(opt) = cmd.post_sound_set {
dbus.proxies().rog_bios().set_post_sound(opt)?;
dbus.proxies().rog_bios().set_post_boot_sound(opt)?;
}
if cmd.post_sound_get {
let res = dbus.proxies().rog_bios().get_post_sound()? == 1;
let res = dbus.proxies().rog_bios().post_boot_sound()? == 1;
println!("Bios POST sound on: {}", res);
}
if let Some(opt) = cmd.dedicated_gfx_set {
println!("Rebuilding initrd to include drivers");
dbus.proxies().rog_bios().set_dedicated_gfx(opt)?;
dbus.proxies().rog_bios().set_dedicated_graphic_mode(opt)?;
println!("The mode change is not active until you reboot, on boot the bios will make the required change");
if opt {
println!(
@@ -536,9 +741,17 @@ fn handle_bios_option(
}
}
if cmd.dedicated_gfx_get {
let res = dbus.proxies().rog_bios().get_dedicated_gfx()? == 1;
let res = dbus.proxies().rog_bios().dedicated_graphic_mode()? == 1;
println!("Bios dedicated GPU on: {}", res);
}
if let Some(opt) = cmd.panel_overdrive_set {
dbus.proxies().rog_bios().set_panel_overdrive(opt)?;
}
if cmd.panel_overdrive_get {
let res = dbus.proxies().rog_bios().panel_overdrive()? == 1;
println!("Panel overdrive on: {}", res);
}
}
Ok(())
}

View File

@@ -12,7 +12,7 @@ pub struct ProfileCommand {
#[options(help = "get profile")]
pub profile_get: bool,
#[options(help = "set the active profile")]
#[options(meta = "", help = "set the active profile")]
pub profile_set: Option<Profile>,
}
@@ -44,7 +44,8 @@ pub struct FanCurveCommand {
#[options(
meta = "",
help = "data format = 30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%. `mod-profile` required"
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>,
}

View File

@@ -1,6 +1,6 @@
[package]
name = "daemon-user"
version = "1.2.0"
version = "1.3.0"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
description = "Usermode daemon for user settings, anime, per-key lighting"
@@ -23,8 +23,10 @@ rog_anime = { path = "../rog-anime" }
rog_dbus = { path = "../rog-dbus" }
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"

View File

@@ -1,6 +1,6 @@
use rog_anime::error::AnimeError;
use rog_anime::{ActionData, ActionLoader, AnimTime, Fade, Sequences, Vec2};
use rog_dbus::RogDbusClient;
use rog_dbus::RogDbusClientBlocking;
use serde_derive::{Deserialize, Serialize};
use std::time::Duration;
use std::{
@@ -66,14 +66,14 @@ pub enum TimeType {
/// and a zbus server behind `Arc<Mutex<T>>`
pub struct CtrlAnimeInner<'a> {
sequences: Sequences,
client: RogDbusClient<'a>,
client: RogDbusClientBlocking<'a>,
do_early_return: Arc<AtomicBool>,
}
impl<'a> CtrlAnimeInner<'static> {
pub fn new(
sequences: Sequences,
client: RogDbusClient<'static>,
client: RogDbusClientBlocking<'static>,
do_early_return: Arc<AtomicBool>,
) -> Result<Self, Error> {
Ok(Self {
@@ -91,12 +91,16 @@ impl<'a> CtrlAnimeInner<'static> {
for action in self.sequences.iter() {
match action {
ActionData::Animation(frames) => {
rog_anime::run_animation(frames, self.do_early_return.clone(), &|output| {
rog_anime::run_animation(frames, &|output| {
if self.do_early_return.load(Ordering::Acquire) {
return Ok(true); // Do safe exit
}
self.client
.proxies()
.anime()
.write(output)
.map_err(|e| AnimeError::Dbus(format!("{}", e)))
.map(|_| false)
})?;
}
ActionData::Image(image) => {
@@ -131,7 +135,7 @@ impl<'a> CtrlAnimeInner<'static> {
pub struct CtrlAnime<'a> {
config: Arc<Mutex<UserAnimeConfig>>,
client: RogDbusClient<'a>,
client: RogDbusClientBlocking<'a>,
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
/// Must be the same Atomic as in CtrlAnimeInner
inner_early_return: Arc<AtomicBool>,
@@ -141,7 +145,7 @@ impl<'a> CtrlAnime<'static> {
pub fn new(
config: Arc<Mutex<UserAnimeConfig>>,
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
client: RogDbusClient<'static>,
client: RogDbusClientBlocking<'static>,
inner_early_return: Arc<AtomicBool>,
) -> Result<Self, Error> {
Ok(CtrlAnime {
@@ -152,12 +156,14 @@ impl<'a> CtrlAnime<'static> {
})
}
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
@@ -353,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().set_led_power(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().set_led_power(on)?;
self.client.proxies().anime().set_on_off(on).ok();
}
Ok(())
}

View File

@@ -1,13 +1,14 @@
use rog_dbus::RogDbusClient;
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;
@@ -17,51 +18,55 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" rog-dbus v{}", rog_dbus::VERSION);
println!("rog-supported v{}", rog_supported::VERSION);
let (client, _) = RogDbusClient::new()?;
let supported = client.proxies().supported().get_supported_functions()?;
let (client, _) = RogDbusClientBlocking::new()?;
let supported = client.proxies().supported().supported_functions()?;
let mut config = UserConfig::new();
config.load_config()?;
let anime_config = UserAnimeConfig::load_config(config.active_anime)?;
let anime = anime_config.create_anime()?;
let anime_config = Arc::new(Mutex::new(anime_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 {
let early_return = Arc::new(AtomicBool::new(false));
// Inner behind mutex required for thread safety
let inner = Arc::new(Mutex::new(CtrlAnimeInner::new(
anime,
client,
early_return.clone(),
)?));
// Need new client object for dbus control part
let (client, _) = RogDbusClient::new()?;
let anime_control = CtrlAnime::new(anime_config, inner.clone(), client, 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().ok();
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());
}
}

View File

@@ -4,7 +4,7 @@ use std::{
time::Duration,
};
use rog_anime::{ActionLoader, AnimTime, Fade, Sequences, Vec2};
use rog_anime::{ActionLoader, AnimTime, AnimeType, Fade, Sequences, Vec2};
use serde_derive::{Deserialize, Serialize};
use crate::error::Error;
@@ -16,8 +16,8 @@ pub struct UserAnimeConfig {
}
impl UserAnimeConfig {
pub fn create_anime(&self) -> Result<Sequences, Error> {
let mut seq = Sequences::new();
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)?;

View File

@@ -1,6 +1,6 @@
[package]
name = "daemon"
version = "4.0.3"
version = "4.3.2"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -18,22 +18,25 @@ name = "asusd"
path = "src/daemon.rs"
[dependencies]
rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
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"
zvariant_derive = { version = "^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"

View File

@@ -2,10 +2,11 @@ use log::{error, warn};
use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::PathBuf;
pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, Serialize, Default)]
pub struct Config {
/// Save charge limit for restoring on boot
pub bat_charge_limit: u8,
@@ -24,8 +25,8 @@ impl Config {
.read(true)
.write(true)
.create(true)
.open(&CONFIG_PATH)
.unwrap_or_else(|_| panic!("The directory /etc/asusd/ is missing")); // 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) {

View File

@@ -1,7 +1,7 @@
use crate::VERSION;
use log::{error, info, warn};
use rog_anime::Fade;
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};
@@ -80,28 +80,32 @@ pub struct AnimeConfigCached {
}
impl AnimeConfigCached {
pub fn init_from_config(&mut self, config: &AnimeConfig) -> Result<(), AnimeError> {
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(ani)?);
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(ani)?);
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(ani)?);
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(ani)?);
shutdown.push(ActionData::from_anime_action(anime_type, ani)?);
}
self.shutdown = shutdown;
Ok(())
@@ -153,17 +157,20 @@ impl AnimeConfig {
if read_len == 0 {
return AnimeConfig::create_default(&mut file);
} else {
if let Ok(data) = serde_json::from_str(&buf) {
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 config = data.into_current();
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 config = data.into_current();
let mut config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
Self::clamp_config_brightness(&mut config);
return config;
}
warn!(
@@ -182,6 +189,16 @@ impl AnimeConfig {
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 {

View File

@@ -2,22 +2,25 @@ pub mod config;
pub mod zbus;
use ::zbus::Connection;
use async_trait::async_trait;
use log::{error, info, warn};
use logind_zbus::ManagerProxy;
use logind_zbus::manager::ManagerProxy;
use rog_anime::{
error::AnimeError,
usb::{
pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID,
VENDOR_ID,
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, ANIME_DATA_LEN,
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},
sync::{Arc, Mutex, MutexGuard},
thread::sleep,
};
use std::{
@@ -39,6 +42,7 @@ impl GetSupported for CtrlAnime {
pub struct CtrlAnime {
_node: String,
anime_type: AnimeType,
handle: RefCell<DeviceHandle<rusb::GlobalContext>>,
cache: AnimeConfigCached,
config: AnimeConfig,
@@ -51,15 +55,17 @@ pub struct CtrlAnime {
impl CtrlAnime {
#[inline]
pub fn new(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
let node = Self::find_node("193b")?;
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)?;
cache.init_from_config(&config, anime_type)?;
let ctrl = CtrlAnime {
_node: node,
anime_type,
handle: RefCell::new(device),
cache,
config,
@@ -71,34 +77,6 @@ impl CtrlAnime {
Ok(ctrl)
}
fn find_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("usb").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(attr) = device.attribute_value("idProduct") {
if attr == id_product {
if let Some(dev_node) = device.devnode() {
info!("Using device at: {:?} for AniMe control", dev_node);
return Ok(dev_node.to_string_lossy().to_string());
}
}
}
}
Err(RogError::MissingFunction(
"ASUS AniMe device node not found".into(),
))
}
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)?;
@@ -139,6 +117,7 @@ impl CtrlAnime {
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
@@ -148,65 +127,72 @@ impl CtrlAnime {
std::thread::Builder::new()
.name("AniMe system thread start".into())
.spawn(move || {
info!("AniMe system thread started");
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;
// 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.
let anime_type;
loop {
if let Ok(lock) = inner.try_lock() {
thread_exit = lock.thread_exit.clone();
thread_running = lock.thread_running.clone();
// Make any running loop exit first
thread_exit.store(true, Ordering::SeqCst);
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);
}
loop {
// wait for other threads to set not running so we know they exited
if !thread_running.load(Ordering::SeqCst) {
thread_exit.store(false, Ordering::SeqCst);
info!("AniMe forced a thread to exit");
break;
}
}
info!("AniMe no previous system thread running (now)");
thread_exit.store(false, Ordering::SeqCst);
'main: loop {
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
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,
thread_exit.clone(),
&|frame| {
inner
.try_lock()
.map(|lock| lock.write_data_buffer(frame))
.map_err(|err| {
warn!("rog_anime::run_animation: {}", err);
AnimeError::NoFrames
})
},
) {
warn!("rog_anime::run_animation: {}", err);
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;
};
if thread_exit.load(Ordering::SeqCst) {
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),
@@ -216,17 +202,27 @@ impl CtrlAnime {
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() {
let data = AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec());
lock.write_data_buffer(data);
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_exit.store(false, Ordering::SeqCst);
thread_running.store(false, Ordering::SeqCst);
info!("AniMe system thread exited");
})
@@ -275,19 +271,20 @@ impl CtrlAnime {
/// 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) {
for led in buffer.get_mut()[7..].iter_mut() {
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::from(buffer);
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) {
@@ -297,89 +294,83 @@ impl CtrlAnime {
}
}
pub struct CtrlAnimeTask<'a> {
pub struct CtrlAnimeTask {
inner: Arc<Mutex<CtrlAnime>>,
_c: Connection,
manager: ManagerProxy<'a>,
}
impl<'a> CtrlAnimeTask<'a> {
pub fn new(inner: Arc<Mutex<CtrlAnime>>) -> Self {
let connection =
Connection::new_system().expect("CtrlAnimeTask could not create dbus connection");
let manager =
ManagerProxy::new(&connection).expect("CtrlAnimeTask could not create ManagerProxy");
let c1 = inner.clone();
// Run this action when the system starts shutting down
manager
.connect_prepare_for_shutdown(move |shutdown| {
if shutdown {
'outer: loop {
if let Ok(lock) = c1.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(c1.clone(), lock.cache.shutdown.clone(), false);
break 'outer;
}
}
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
let c1 = inner.clone();
// Run this action when the system wakes up from sleep
manager
.connect_prepare_for_sleep(move |sleep| {
if !sleep {
// wait a fraction for things to wake up properly
std::thread::sleep(Duration::from_millis(100));
'outer: loop {
if let Ok(lock) = c1.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(c1.clone(), lock.cache.wake.clone(), true);
break 'outer;
}
}
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
Self {
inner,
_c: connection,
manager,
}
impl CtrlAnimeTask {
pub async fn new(inner: Arc<Mutex<CtrlAnime>>) -> CtrlAnimeTask {
Self { inner }
}
}
impl<'a> crate::CtrlTask for CtrlAnimeTask<'a> {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(mut lock) = self.inner.try_lock() {
// Refresh the config and cache incase the user has edited it
let config = AnimeConfig::load();
lock.cache
.init_from_config(&config)
.map_err(|err| {
warn!("CtrlAnimeTask: do_task {}", err);
err
})
.ok();
}
#[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();
// Check for signals on each task iteration, this will run the callbacks
// if any signal is recieved
self.manager.next_signal()?;
Ok(())
}
}

View File

@@ -1,12 +1,12 @@
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;
use zvariant::ObjectPath;
use zbus::{dbus_interface, Connection, SignalContext};
use std::sync::atomic::Ordering;
@@ -15,18 +15,10 @@ 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 {
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();
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Anime", server).await;
}
}
@@ -36,14 +28,18 @@ impl crate::ZbusAdd for CtrlAnimeZbus {
#[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) {
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);
lock.write_data_buffer(input).map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
err
})?;
break 'outer;
}
}
Ok(())
}
/// Set the global AniMe brightness
@@ -53,8 +49,8 @@ impl CtrlAnimeZbus {
let mut bright = bright;
if bright < 0.0 {
bright = 0.0
} else if bright > 254.0 {
bright = 254.0;
} else if bright > 1.0 {
bright = 1.0;
}
lock.config.brightness = bright;
lock.config.write();
@@ -64,26 +60,30 @@ impl CtrlAnimeZbus {
}
/// Set whether the AniMe is displaying images/data
fn set_on_off(&self, status: bool) {
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();
let states = AnimePowerStates {
states = Some(AnimePowerStates {
brightness: lock.config.brightness.floor() as u8,
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
});
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
fn set_boot_on_off(&self, on: bool) {
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));
@@ -91,15 +91,17 @@ impl CtrlAnimeZbus {
lock.config.boot_anim_enabled = on;
lock.config.write();
let states = AnimePowerStates {
states = Some(AnimePowerStates {
brightness: lock.config.brightness.floor() as u8,
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
});
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
@@ -116,7 +118,7 @@ impl CtrlAnimeZbus {
}
}
/// Get status of if the AniMe LEDs are on
/// 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() {
@@ -136,5 +138,8 @@ impl CtrlAnimeZbus {
/// Notify listeners of the status of AniMe LED power and factory system-status animations
#[dbus_interface(signal)]
fn notify_power_states(&self, data: &AnimePowerStates) -> zbus::Result<()>;
async fn notify_power_states(
ctxt: &SignalContext<'_>,
data: AnimePowerStates,
) -> zbus::Result<()>;
}

View File

@@ -1,74 +1,128 @@
use crate::laptops::LaptopLedData;
use log::{error, info, warn};
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, LedBrightness};
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;
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";
#[derive(Deserialize, Serialize)]
pub struct AuraConfigV320 {
pub brightness: u32,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
/// 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 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,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraConfigV352 {
pub brightness: LedBrightness,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
}
impl AuraConfigV352 {
pub(crate) fn into_current(self) -> AuraConfig {
AuraConfig {
brightness: self.brightness,
current_mode: self.current_mode,
builtins: self.builtins,
multizone: self.multizone,
awake_enabled: true,
sleep_anim_enabled: true,
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<AuraMultiZone>,
pub awake_enabled: bool,
pub sleep_anim_enabled: bool,
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,
awake_enabled: true,
sleep_anim_enabled: true,
multizone_on: false,
enabled,
}
}
}
@@ -94,16 +148,6 @@ impl AuraConfig {
} 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;
} else if let Ok(data) = serde_json::from_str::<AuraConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated AuraConfig version");
return config;
}
warn!(
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
@@ -129,6 +173,27 @@ impl AuraConfig {
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
@@ -162,115 +227,126 @@ impl AuraConfig {
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
/// Multipurpose, will accept AuraEffect with zones and put in the correct store
/// 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() {
multi.set(effect)
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; 4]> {
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect]> {
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());
}
return multi.get(&aura_type).map(|v| v.as_slice());
}
None
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraMultiZone {
static_: [AuraEffect; 4],
breathe: [AuraEffect; 4],
}
#[cfg(test)]
mod tests {
use super::AuraConfig;
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
impl AuraMultiZone {
pub fn set(&mut self, effect: AuraEffect) {
if effect.mode == AuraModeNum::Static {
match effect.zone {
AuraZone::None => {}
AuraZone::One => self.static_[0] = effect,
AuraZone::Two => self.static_[1] = effect,
AuraZone::Three => self.static_[2] = effect,
AuraZone::Four => self.static_[3] = effect,
}
} else if effect.mode == AuraModeNum::Breathe {
match effect.zone {
AuraZone::None => {}
AuraZone::One => self.breathe[0] = effect,
AuraZone::Two => self.breathe[1] = effect,
AuraZone::Three => self.breathe[2] = effect,
AuraZone::Four => self.breathe[3] = effect,
}
}
#[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));
}
pub fn static_(&self) -> &[AuraEffect; 4] {
&self.static_
}
#[test]
fn set_multizone_multimode_config() {
let mut config = AuraConfig::default();
pub fn breathe(&self) -> &[AuraEffect; 4] {
&self.breathe
}
}
impl Default for AuraMultiZone {
fn default() -> Self {
Self {
static_: [
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::One,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Two,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Three,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Four,
..Default::default()
},
],
breathe: [
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::One,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Two,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Three,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Four,
..Default::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);
}
}

View File

@@ -6,38 +6,50 @@ use crate::{
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
CtrlTask,
};
use log::{info, warn};
use logind_zbus::ManagerProxy;
use async_trait::async_trait;
use log::{error, info, warn};
use logind_zbus::manager::ManagerProxy;
use rog_aura::{
usb::{
LED_APPLY, LED_AWAKE_OFF_SLEEP_OFF, LED_AWAKE_OFF_SLEEP_ON, LED_AWAKE_ON_SLEEP_OFF,
LED_AWAKE_ON_SLEEP_ON, LED_SET,
},
usb::{AuraDevice, LED_APPLY, LED_SET},
AuraEffect, LedBrightness, LED_MSG_LEN,
};
use rog_aura::{AuraZone, Direction, Speed, GRADIENT};
use rog_supported::LedSupportedFunctions;
use std::io::{Read, Write};
use smol::{stream::StreamExt, Executor};
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use std::{fs::OpenOptions, thread::spawn};
use std::{
collections::BTreeMap,
io::{Read, Write},
};
use std::{fs::OpenOptions, sync::MutexGuard};
use zbus::Connection;
use crate::GetSupported;
use super::config::AuraConfig;
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 multizone_led_mode = false;
let per_key_led_mode = false;
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 = "";
for prod in ASUS_KEYBOARD_DEVICES.iter() {
if let Ok(_) = Self::find_led_node(prod) {
prod_id = *prod;
break;
}
}
LedSupportedFunctions {
prod_id: AuraDevice::from(prod_id),
brightness_set: CtrlKbdLed::get_kbd_bright_path().is_some(),
stock_led_modes,
multizone_led_mode,
@@ -47,6 +59,8 @@ impl GetSupported for CtrlKbdLed {
}
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,
@@ -54,50 +68,13 @@ pub struct CtrlKbdLed {
pub config: AuraConfig,
}
pub struct CtrlKbdLedTask<'a> {
pub struct CtrlKbdLedTask {
inner: Arc<Mutex<CtrlKbdLed>>,
_c: Connection,
manager: ManagerProxy<'a>,
}
impl<'a> CtrlKbdLedTask<'a> {
impl CtrlKbdLedTask {
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
let connection =
Connection::new_system().expect("CtrlKbdLedTask could not create dbus connection");
let manager =
ManagerProxy::new(&connection).expect("CtrlKbdLedTask could not create ManagerProxy");
let c1 = inner.clone();
// Run this action when the system wakes up from sleep
manager
.connect_prepare_for_sleep(move |sleep| {
if !sleep {
let c1 = c1.clone();
spawn(move || {
// wait a fraction for things to wake up properly
//std::thread::sleep(Duration::from_millis(100));
loop {
if let Ok(ref mut lock) = c1.try_lock() {
lock.set_brightness(lock.config.brightness).ok();
break;
}
}
});
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
Self {
inner,
_c: connection,
manager,
}
Self { inner }
}
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
@@ -125,12 +102,70 @@ impl<'a> CtrlKbdLedTask<'a> {
}
}
impl<'a> CtrlTask for CtrlKbdLedTask<'a> {
fn do_task(&self) -> Result<(), RogError> {
self.manager.next_signal()?;
if let Ok(ref mut lock) = self.inner.try_lock() {
return Self::update_config(lock);
}
#[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(())
}
}
@@ -140,13 +175,9 @@ 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() {
let current = ctrl.config.current_mode;
if let Some(mode) = ctrl.config.builtins.get(&current).cloned() {
ctrl.do_command(mode).ok();
}
ctrl.set_states_enabled(ctrl.config.awake_enabled, ctrl.config.sleep_anim_enabled)
.map_err(|err| warn!("{}", err))
ctrl.write_current_config_mode()?;
ctrl.set_power_states(&ctrl.config)
.map_err(|err| warn!("{err}"))
.ok();
}
Ok(())
@@ -162,27 +193,26 @@ impl CtrlKbdLedZbus {
}
impl CtrlKbdLed {
#[inline]
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) => warn!("led_node: {}", err),
Err(err) => info!("Looked for keyboard controller 0x{prod}: {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 led_node.is_none() {
return Err(RogError::NoAuraKeyboard);
}
if bright_node.is_none() {
@@ -192,6 +222,7 @@ impl CtrlKbdLed {
}
let ctrl = CtrlKbdLed {
led_prod,
led_node,
bright_node: bright_node.unwrap(), // If was none then we already returned above
supported_modes,
@@ -263,27 +294,19 @@ impl CtrlKbdLed {
self.set_brightness(self.config.brightness)
}
/// Set if awake/on LED active, and/or sleep animation active
pub(super) fn set_states_enabled(&self, awake: bool, sleep: bool) -> Result<(), RogError> {
let bytes = if awake && sleep {
LED_AWAKE_ON_SLEEP_ON
} else if awake && !sleep {
LED_AWAKE_ON_SLEEP_OFF
} else if !awake && sleep {
LED_AWAKE_OFF_SLEEP_ON
} else if !awake && !sleep {
LED_AWAKE_OFF_SLEEP_OFF
} else {
LED_AWAKE_ON_SLEEP_ON
};
self.write_bytes(&bytes)?;
/// 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(())
}
fn find_led_node(id_product: &str) -> Result<String, RogError> {
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)
@@ -321,12 +344,27 @@ impl CtrlKbdLed {
))
}
pub(crate) fn do_command(&mut self, mode: AuraEffect) -> Result<(), RogError> {
self.set_and_save(mode)
/// 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
#[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) {
@@ -336,11 +374,10 @@ impl CtrlKbdLed {
.map_err(|err| RogError::Write("write_bytes".into(), err));
}
}
Err(RogError::NotSupported)
Err(RogError::NoAuraNode)
}
/// Write an effect block
#[inline]
/// 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() {
@@ -355,20 +392,6 @@ impl CtrlKbdLed {
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]
pub(super) fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
let current = self.config.current_mode;
if let Some(idx) = self
@@ -394,21 +417,17 @@ impl CtrlKbdLed {
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;
}
// if self.config.builtins.contains_key(&next) {
self.config.current_mode = next;
self.write_current_config_mode()?;
// }
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)?;
@@ -416,4 +435,217 @@ impl CtrlKbdLed {
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);
}
}

View File

@@ -1,18 +1,16 @@
use log::{error, warn};
use rog_aura::{AuraEffect, LedBrightness, LedPowerStates};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use std::collections::BTreeMap;
use async_trait::async_trait;
use log::warn;
use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, LedBrightness};
use zbus::{dbus_interface, Connection, SignalContext};
use super::controller::CtrlKbdLedZbus;
#[async_trait]
impl crate::ZbusAdd for CtrlKbdLedZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Led"), self)
.map_err(|err| {
error!("DbusKbdLed: add_to_server {}", err);
})
.ok();
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Aura", server).await;
}
}
@@ -22,7 +20,7 @@ impl crate::ZbusAdd for CtrlKbdLedZbus {
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlKbdLedZbus {
/// Set the keyboard brightness level (0-3)
fn set_brightness(&mut self, brightness: LedBrightness) {
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))
@@ -30,140 +28,188 @@ impl CtrlKbdLedZbus {
}
}
/// Set the keyboard LED to enabled while the device is awake
fn set_awake_enabled(&mut self, enabled: bool) {
/// Set a variety of states, input is array of enum.
/// `enabled` sets if the sent array should be disabled or enabled
///
/// ```text
/// pub struct AuraPowerDev {
/// pub x1866: Vec<AuraDev1866>,
/// pub x19b6: Vec<AuraDev19b6>,
/// }
/// pub enum AuraDev1866 {
/// Awake,
/// Keyboard,
/// Lightbar,
/// Boot,
/// Sleep,
/// }
/// enum AuraDev19b6 {
/// 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() {
ctrl.set_states_enabled(enabled, ctrl.config.sleep_anim_enabled)
.map_err(|err| warn!("{}", err))
.ok();
ctrl.config.awake_enabled = enabled;
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();
let states = LedPowerStates {
enabled: ctrl.config.awake_enabled,
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
};
self.notify_power_states(&states)
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(())
}
/// Set the keyboard LED suspend animation to enabled while the device is suspended
fn set_sleep_enabled(&mut self, enabled: bool) {
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_states_enabled(ctrl.config.awake_enabled, enabled)
.map_err(|err| warn!("{}", err))
.ok();
ctrl.config.sleep_anim_enabled = enabled;
ctrl.config.write();
let states = LedPowerStates {
enabled: ctrl.config.awake_enabled,
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
}
}
fn set_led_mode(&mut self, effect: AuraEffect) {
if let Ok(mut ctrl) = self.0.try_lock() {
match ctrl.do_command(effect) {
Ok(_) => {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
Err(err) => {
warn!("{}", err);
}
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(())
}
fn next_led_mode(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.toggle_mode(false)
.unwrap_or_else(|err| warn!("{}", err));
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) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
led = Some(mode.clone());
}
}
if let Some(led) = led {
Self::notify_led(&ctxt, led)
.await
.unwrap_or_else(|err| warn!("{}", err));
}
Ok(())
}
fn prev_led_mode(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.toggle_mode(true)
.unwrap_or_else(|err| warn!("{}", err));
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) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
led = Some(mode.clone());
}
}
}
fn next_led_brightness(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.next_brightness()
if let Some(led) = led {
Self::notify_led(&ctxt, led)
.await
.unwrap_or_else(|err| warn!("{}", err));
}
Ok(())
}
fn prev_led_brightness(&self) {
async fn next_led_brightness(&self) -> zbus::fdo::Result<()> {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.prev_brightness()
.unwrap_or_else(|err| warn!("{}", err));
ctrl.next_brightness().map_err(|e| {
warn!("{}", e);
e
})?;
}
Ok(())
}
#[dbus_interface(property)]
fn awake_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.awake_enabled;
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
})?;
}
true
Ok(())
}
#[dbus_interface(property)]
fn sleep_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.sleep_anim_enabled;
// As property doesn't work for AuraPowerDev (complexity of serialization?)
// #[dbus_interface(property)]
async fn leds_enabled(&self) -> AuraPowerDev {
loop {
if let Ok(ctrl) = self.0.try_lock() {
return AuraPowerDev::from(&ctrl.config.enabled);
}
}
true
}
/// Return the current mode data
#[dbus_interface(property)]
fn led_mode(&self) -> String {
async fn led_mode(&self) -> AuraModeNum {
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;
}
}
return ctrl.config.current_mode;
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not deserialise".to_string()
AuraModeNum::Static
}
/// Return a list of available modes
#[dbus_interface(property)]
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;
async fn led_modes(&self) -> BTreeMap<AuraModeNum, AuraEffect> {
loop {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.builtins.clone();
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not serialise".to_string()
}
/// Return the current LED brightness
#[dbus_interface(property)]
fn led_brightness(&self) -> i8 {
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);
}
@@ -172,8 +218,11 @@ impl CtrlKbdLedZbus {
}
#[dbus_interface(signal)]
fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>;
async fn notify_led(signal_ctxt: &SignalContext<'_>, data: AuraEffect) -> zbus::Result<()>;
#[dbus_interface(signal)]
fn notify_power_states(&self, data: &LedPowerStates) -> zbus::Result<()>;
async fn notify_power_states(
signal_ctxt: &SignalContext<'_>,
data: &AuraPowerDev,
) -> zbus::Result<()>;
}

View File

@@ -1,14 +1,19 @@
use crate::CtrlTask;
use crate::{config::Config, error::RogError, GetSupported};
//use crate::dbus::DbusEvents;
use async_trait::async_trait;
use log::{info, warn};
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_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";
@@ -30,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;
}
@@ -55,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;
}
}
@@ -77,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(())
}
@@ -104,12 +104,9 @@ 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()?;
@@ -129,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(())
}
}

View File

@@ -2,10 +2,12 @@ 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;
@@ -21,8 +23,7 @@ impl GetSupported for CtrlPlatformProfile {
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 with the following patch applied:
https://lkml.org/lkml/2021/8/18/1022
To enable profile support you require a kernel version 5.15.2 minimum.
"#
);
}
@@ -38,8 +39,8 @@ https://lkml.org/lkml/2021/8/18/1022
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/8/20/232
Please note that as of 24/08/2021 this is not final.
https://lkml.org/lkml/2021/10/23/250
This patch has been accepted upstream for 5.17 kernel release.
"#
);
}
@@ -56,7 +57,10 @@ impl crate::Reloadable for CtrlPlatformProfile {
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(())
@@ -64,17 +68,13 @@ impl crate::Reloadable for CtrlPlatformProfile {
}
impl CtrlPlatformProfile {
pub fn new(mut config: ProfileConfig) -> Result<Self, RogError> {
pub fn new(config: ProfileConfig) -> Result<Self, RogError> {
if Profile::is_platform_profile_supported() {
info!("Device has profile control available");
if let Ok(ref device) = FanCurveProfiles::get_device() {
let profile = config.active_profile;
if let Some(curve) = config.fan_curves.as_mut() {
curve.read_from_dev_profile(profile, device);
}
if FanCurveProfiles::get_device().is_ok() {
info!("Device has fan curves available");
}
config.write();
return Ok(CtrlPlatformProfile { config });
}
@@ -137,15 +137,21 @@ impl CtrlProfileTask {
}
}
#[async_trait]
impl CtrlTask for CtrlProfileTask {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(ref mut lock) = self.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.save_config();
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(())
}
}

View File

@@ -1,12 +1,14 @@
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 zvariant::ObjectPath;
use super::controller::CtrlPlatformProfile;
@@ -37,13 +39,17 @@ impl ProfileZbus {
/// Toggle to next platform_profile. Names provided by `Profiles`.
/// If fan-curves are supported will also activate a fan curve for profile.
fn next_profile(&mut self) {
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();
}
self.do_notification();
}
/// Fetch the active profile name
@@ -58,7 +64,12 @@ impl ProfileZbus {
}
/// Set this platform_profile name as active
fn set_active_profile(&self, profile: Profile) {
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();
@@ -71,8 +82,11 @@ impl ProfileZbus {
.ok();
ctrl.save_config();
tmp = Some(ctrl.config.active_profile);
}
if let Some(profile) = tmp {
Self::notify_profile(&ctxt, profile).await.ok();
}
self.do_notification();
}
/// Get a list of profiles that have fan-curves enabled.
@@ -94,7 +108,7 @@ impl ProfileZbus {
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();
if let Some(curves) = &mut ctrl.config.fan_curves {
return if let Some(curves) = &mut ctrl.config.fan_curves {
curves.set_profile_curve_enabled(profile, enabled);
ctrl.write_profile_curve_to_platform()
@@ -102,10 +116,10 @@ impl ProfileZbus {
.ok();
ctrl.save_config();
return Ok(());
Ok(())
} else {
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
}
Err(Error::Failed(UNSUPPORTED_MSG.to_string()))
};
}
Err(Error::Failed(
"Failed to get enabled fan curve names".to_string(),
@@ -161,29 +175,13 @@ impl ProfileZbus {
}
#[dbus_interface(signal)]
fn notify_profile(&self, profile: &Profile) -> zbus::Result<()> {}
}
impl ProfileZbus {
fn do_notification(&self) {
if let Ok(ctrl) = self.inner.try_lock() {
self.notify_profile(&ctrl.config.active_profile)
.unwrap_or_else(|err| warn!("{}", err));
}
async fn notify_profile(signal_ctxt: &SignalContext<'_>, profile: Profile) -> zbus::Result<()> {
}
}
#[async_trait]
impl crate::ZbusAdd for ProfileZbus {
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();
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Profile", server).await;
}
}

View File

@@ -1,4 +1,5 @@
use crate::{config::Config, error::RogError, GetSupported};
use async_trait::async_trait;
use log::{error, info, warn};
use rog_supported::RogBiosSupportedFunctions;
use std::fs::OpenOptions;
@@ -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: Path::new(ASUS_POST_LOGO_SOUND).exists(),
dedicated_gfx_toggle: Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists(),
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,24 +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);
@@ -88,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;
}
}
@@ -308,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);
}
}

View File

@@ -1,8 +1,8 @@
use log::warn;
use async_trait::async_trait;
use serde_derive::{Deserialize, Serialize};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use zvariant_derive::Type;
use zbus::Connection;
use zvariant::Type;
use crate::{
ctrl_anime::CtrlAnime, ctrl_aura::controller::CtrlKbdLed, ctrl_charge::CtrlCharge,
@@ -30,18 +30,10 @@ impl SupportedFunctions {
}
}
#[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;
}
}

View File

@@ -1,3 +1,14 @@
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::*;
@@ -7,7 +18,7 @@ use daemon::ctrl_aura::controller::{
};
use daemon::ctrl_charge::CtrlCharge;
use daemon::ctrl_profiles::config::ProfileConfig;
use daemon::ctrl_profiles::controller::CtrlProfileTask;
use daemon::ctrl_rog_bios::CtrlRogBios;
use daemon::{
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
};
@@ -15,20 +26,9 @@ use daemon::{
ctrl_profiles::{controller::CtrlPlatformProfile, zbus::ProfileZbus},
laptops::LaptopLedData,
};
use ::zbus::{fdo, Connection, ObjectServer};
use daemon::{CtrlTask, Reloadable, ZbusAdd};
use log::LevelFilter;
use log::{error, info, warn};
use rog_dbus::DBUS_NAME;
use rog_profiles::Profile;
use std::env;
use std::error::Error;
use std::io::Write;
use std::sync::Arc;
use std::sync::Mutex;
use daemon::ctrl_rog_bios::CtrlRogBios;
static PROFILE_CONFIG_PATH: &str = "/etc/asusd/profile.conf";
@@ -61,27 +61,25 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
info!(" rog-profiles v{}", rog_profiles::VERSION);
info!("rog-supported v{}", rog_supported::VERSION);
start_daemon()?;
let mut executor = Executor::new();
smol::block_on(start_daemon(&mut executor))?;
Ok(())
}
/// The actual main loop for the daemon
fn start_daemon() -> Result<(), Box<dyn Error>> {
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)?);
// Collect tasks for task thread
let mut tasks: Vec<Box<dyn CtrlTask + Send>> = Vec::new();
// Start zbus server
let connection = Connection::new_system()?;
let fdo_connection = fdo::DBusProxy::new(&connection)?;
let mut object_server = ObjectServer::new(&connection);
let mut connection = Connection::system().await?;
let config = Config::load();
let config = Arc::new(Mutex::new(config));
supported.add_to_server(&mut object_server);
supported.add_to_server(&mut connection).await;
match CtrlRogBios::new(config.clone()) {
Ok(mut ctrl) => {
@@ -89,20 +87,23 @@ 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);
}
}
match CtrlCharge::new(config) {
match CtrlCharge::new(config.clone()) {
Ok(mut ctrl) => {
// Do a reload of any settings
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);
@@ -117,9 +118,11 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
ProfileZbus::new(tmp.clone()).add_to_server(&mut object_server);
let task = CtrlProfileTask::new(tmp.clone());
task.create_tasks(executor).await.ok();
tasks.push(Box::new(CtrlProfileTask::new(tmp)));
let task = ProfileZbus::new(tmp.clone());
task.add_to_server(&mut connection).await;
}
Err(err) => {
error!("Profile control: {}", err);
@@ -139,9 +142,10 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
.unwrap_or_else(|err| warn!("AniMe: {}", err));
let zbus = CtrlAnimeZbus(inner.clone());
zbus.add_to_server(&mut object_server);
zbus.add_to_server(&mut connection).await;
tasks.push(Box::new(CtrlAnimeTask::new(inner)));
let task = CtrlAnimeTask::new(inner).await;
task.create_tasks(executor).await.ok();
}
Err(err) => {
error!("AniMe control: {}", err);
@@ -159,42 +163,21 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
.reload()
.unwrap_or_else(|err| warn!("Keyboard LED control: {}", err));
CtrlKbdLedZbus::new(inner.clone()).add_to_server(&mut object_server);
CtrlKbdLedZbus::new(inner.clone())
.add_to_server(&mut connection)
.await;
let task = CtrlKbdLedTask::new(inner);
tasks.push(Box::new(task));
task.create_tasks(executor).await.ok();
}
Err(err) => {
error!("Keyboard control: {}", err);
}
}
// TODO: implement messaging between threads to check fails
// Run tasks
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() {
ctrl.do_task()
.map_err(|err| {
warn!("do_task error: {}", err);
})
.ok();
}
});
// Request dbus name after finishing initalizing all functions
fdo_connection.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
// Loop to check errors and iterate zbus server
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());
}
}

View File

@@ -1,3 +1,4 @@
use rog_anime::error::AnimeError;
use rog_profiles::error::ProfileError;
use std::convert::From;
use std::fmt;
@@ -22,6 +23,11 @@ pub enum RogError {
Modprobe(String),
Io(std::io::Error),
Zbus(zbus::Error),
ChargeLimit(u8),
AuraEffectNotSupported,
NoAuraKeyboard,
NoAuraNode,
Anime(AnimeError),
}
impl fmt::Display for RogError {
@@ -46,6 +52,11 @@ impl fmt::Display for RogError {
RogError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
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),
}
}
}
@@ -58,6 +69,12 @@ impl From<ProfileError> for RogError {
}
}
impl From<AnimeError> for RogError {
fn from(err: AnimeError) -> Self {
RogError::Anime(err)
}
}
impl From<zbus::Error> for RogError {
fn from(err: zbus::Error) -> Self {
RogError::Zbus(err)

View File

@@ -1,10 +1,11 @@
use log::{info, warn};
use rog_aura::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() {
@@ -32,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,
}
@@ -62,7 +64,7 @@ impl LaptopLedData {
prod_family,
board_names: vec![board_name],
standard: vec![],
multizone: false,
multizone: vec![],
per_key: false,
}
}
@@ -85,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}");
}
}

View File

@@ -27,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");
@@ -39,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(&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 {

View File

@@ -2,98 +2,127 @@
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 = true
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", "G533QR", "G533QS", "G733QS", "G733QR", "G713QR"]
board_names = ["G531GW", "G533QR", "G533QS", "G733QS", "G733QR", "G513QR", "G713QR", "G513QM", "G713IC"]
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", "G513IH", "G513QY"]
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
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
@@ -101,33 +130,40 @@ per_key = false
prod_family = "ROG Zephyrus G15"
board_names = ["GA503QE"]
standard = ["Static", "Breathe", "Pulse"]
multizone = false
multizone = []
per_key = false
[[led_data]]
prod_family = "ROG Zephyrus G15"
board_names = ["GA503Q"]
board_names = ["GA503Q", "GA503R"]
standard = ["Static", "Breathe", "Pulse", "Rainbow", "Strobe"]
multizone = false
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 = false
multizone = []
per_key = true
[[led_data]]
prod_family = "ROG Zephyrus Duo 15 SE"
board_names = ["GX551Q"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
multizone = false
multizone = []
per_key = true
[[led_data]]
prod_family = "ROG Flow X13"
board_names = ["GV301QH", "GV301QE"]
standard = ["Static", "Breathe", "Pulse"]
multizone = false
multizone = []
per_key = false
[[led_data]]
prod_family = "ROG Strix"
board_names = ["G513IC"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
multizone = []
per_key = false

View File

@@ -28,18 +28,53 @@ impl CtrlAnime {
}
```
The task trait:
The task trait. There are three ways to implement this:
```rust
pub struct CtrlAnimeTask(Arc<Mutex<CtrlAnime>>);
impl crate::CtrlTask for CtrlAnimeTask {
fn do_task(&self) -> Result<(), RogError> {
// 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;
}
}
```
@@ -61,18 +96,11 @@ 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) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
// 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;
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "rog_anime"
version = "1.1.0"
version = "1.3.5"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -13,18 +13,23 @@ edition = "2018"
exclude = ["data"]
[features]
default = ["dbus"]
dbus = ["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"] }
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 }

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -1,17 +1,18 @@
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
convert::TryFrom,
thread::sleep,
time::{Duration, Instant},
};
use log::info;
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "dbus")]
use zvariant_derive::Type;
use zvariant::Type;
use crate::{error::AnimeError, AnimTime, AnimeGif};
use crate::{
error::{AnimeError, Result},
AnimTime, AnimeGif,
};
/// The first 7 bytes of a USB packet are accounted for by `USB_PREFIX1` and `USB_PREFIX2`
const BLOCK_START: usize = 7;
@@ -19,48 +20,85 @@ const BLOCK_START: usize = 7;
const BLOCK_END: usize = 634;
/// Individual usable data length of each USB packet
const PANE_LEN: usize = BLOCK_END - BLOCK_START;
/// The length of usable data
pub const ANIME_DATA_LEN: usize = PANE_LEN * 2;
/// First packet is for GA401 + GA402
const USB_PREFIX1: [u8; 7] = [0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02];
/// Second packet is for GA401 + GA402
const USB_PREFIX2: [u8; 7] = [0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02];
/// Third packet is for GA402 matrix
const USB_PREFIX3: [u8; 7] = [0x5e, 0xc0, 0x02, 0xe7, 0x04, 0x73, 0x02];
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize)]
pub struct AnimePowerStates {
pub brightness: u8,
pub enabled: bool,
pub boot_anim_enabled: bool,
}
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub enum AnimeType {
GA401,
GA402,
}
impl AnimeType {
/// The width of diagonal images
pub fn width(&self) -> usize {
match self {
AnimeType::GA401 => 74,
AnimeType::GA402 => 74,
}
}
/// The height of diagonal images
pub fn height(&self) -> usize {
match self {
AnimeType::GA401 => 36,
AnimeType::GA402 => 39,
}
}
/// The length of usable data for this type
pub fn data_length(&self) -> usize {
match self {
AnimeType::GA401 => PANE_LEN * 2,
AnimeType::GA402 => PANE_LEN * 3,
}
}
}
/// The minimal serializable data that can be transferred over wire types.
/// Other data structures in `rog_anime` will convert to this.
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AnimeDataBuffer(Vec<u8>);
impl Default for AnimeDataBuffer {
fn default() -> Self {
Self::new()
}
pub struct AnimeDataBuffer {
data: Vec<u8>,
anime: AnimeType,
}
impl AnimeDataBuffer {
#[inline]
pub fn new() -> Self {
AnimeDataBuffer(vec![0u8; ANIME_DATA_LEN])
pub fn new(anime: AnimeType) -> Self {
let len = anime.data_length();
AnimeDataBuffer {
data: vec![0u8; len],
anime,
}
}
/// Get the inner data buffer
#[inline]
pub fn get(&self) -> &[u8] {
&self.0
pub fn data(&self) -> &[u8] {
&self.data
}
/// Get a mutable slice of the inner buffer
#[inline]
pub fn get_mut(&mut self) -> &mut [u8] {
&mut self.0
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.data
}
/// Create from a vector of bytes
@@ -68,35 +106,51 @@ impl AnimeDataBuffer {
/// # Panics
/// Will panic if the vector length is not `ANIME_DATA_LEN`
#[inline]
pub fn from_vec(input: Vec<u8>) -> Self {
assert_eq!(input.len(), ANIME_DATA_LEN);
Self(input)
pub fn from_vec(anime: AnimeType, data: Vec<u8>) -> Result<Self> {
if data.len() != anime.data_length() {
return Err(AnimeError::DataBufferLength);
}
Ok(Self { data, anime })
}
}
/// The two packets to be written to USB
pub type AnimePacketType = [[u8; 640]; 2];
pub type AnimePacketType = Vec<[u8; 640]>;
impl From<AnimeDataBuffer> for AnimePacketType {
#[inline]
fn from(anime: AnimeDataBuffer) -> Self {
assert!(anime.0.len() == ANIME_DATA_LEN);
let mut buffers = [[0; 640]; 2];
for (idx, chunk) in anime.0.as_slice().chunks(PANE_LEN).enumerate() {
impl TryFrom<AnimeDataBuffer> for AnimePacketType {
type Error = AnimeError;
fn try_from(anime: AnimeDataBuffer) -> std::result::Result<Self, Self::Error> {
if anime.data.len() != anime.anime.data_length() {
return Err(AnimeError::DataBufferLength);
}
let mut buffers = match anime.anime {
AnimeType::GA401 => vec![[0; 640]; 2],
AnimeType::GA402 => vec![[0; 640]; 3],
};
for (idx, chunk) in anime.data.as_slice().chunks(PANE_LEN).enumerate() {
buffers[idx][BLOCK_START..BLOCK_END].copy_from_slice(chunk);
}
buffers[0][..7].copy_from_slice(&USB_PREFIX1);
buffers[1][..7].copy_from_slice(&USB_PREFIX2);
buffers
if matches!(anime.anime, AnimeType::GA402) {
buffers[2][..7].copy_from_slice(&USB_PREFIX3);
}
Ok(buffers)
}
}
/// This runs the animations as a blocking loop by using the `callback` to write data
///
/// If `callback` is `Ok(true)` then `run_animation` will exit the animation loop early.
pub fn run_animation(
frames: &AnimeGif,
do_early_return: Arc<AtomicBool>,
callback: &dyn Fn(AnimeDataBuffer) -> Result<(), AnimeError>,
) -> Result<(), AnimeError> {
callback: &dyn Fn(AnimeDataBuffer) -> Result<bool>,
) -> Result<()> {
let mut count = 0;
let start = Instant::now();
@@ -140,14 +194,11 @@ pub fn run_animation(
'animation: loop {
for frame in frames.frames() {
let frame_start = Instant::now();
if do_early_return.load(Ordering::SeqCst) {
return Ok(());
}
let mut output = frame.frame().clone();
if let AnimTime::Fade(_) = frames.duration() {
if frame_start <= start + fade_in {
for pixel in output.get_mut() {
for pixel in output.data_mut() {
*pixel = (*pixel as f32 * fade_in_accum) as u8;
}
fade_in_accum = fade_in_step * (frame_start - start).as_secs_f32();
@@ -158,18 +209,20 @@ pub fn run_animation(
} else {
fade_out_accum = 0.0;
}
for pixel in output.get_mut() {
for pixel in output.data_mut() {
*pixel = (*pixel as f32 * fade_out_accum) as u8;
}
}
}
callback(output)?;
if matches!(callback(output), Ok(true)) {
info!("rog-anime: frame-loop callback asked to exit early");
return Ok(());
}
if timed && Instant::now().duration_since(start) > run_time {
break 'animation;
}
sleep(frame.delay());
}
if let AnimTime::Count(times) = frames.duration() {

View File

@@ -1,40 +1,36 @@
use std::{path::Path, time::Duration};
use crate::{
data::{AnimeDataBuffer, ANIME_DATA_LEN},
error::AnimeError,
data::AnimeDataBuffer,
error::{AnimeError, Result},
AnimeType,
};
const WIDTH: usize = 74;
const HEIGHT: usize = 36;
/// Mostly intended to be used with ASUS gifs, but can be used for other purposes (like images)
#[derive(Debug, Clone)]
pub struct AnimeDiagonal([[u8; WIDTH]; HEIGHT], Option<Duration>);
impl Default for AnimeDiagonal {
#[inline]
fn default() -> Self {
Self::new(None)
}
}
pub struct AnimeDiagonal(AnimeType, Vec<Vec<u8>>, Option<Duration>);
impl AnimeDiagonal {
#[inline]
pub fn new(duration: Option<Duration>) -> Self {
Self([[0u8; WIDTH]; HEIGHT], duration)
pub fn new(anime_type: AnimeType, duration: Option<Duration>) -> Self {
Self(
anime_type,
vec![vec![0; anime_type.width()]; anime_type.height()],
duration,
)
}
#[inline]
pub fn get_mut(&mut self) -> &mut [[u8; WIDTH]; HEIGHT] {
&mut self.0
pub fn get_mut(&mut self) -> &mut Vec<Vec<u8>> {
&mut self.1
}
/// Get a full diagonal row where `x` `y` is the starting point and `len` is the length of data.
fn get_row(&self, x: usize, y: usize, len: usize) -> Vec<u8> {
let mut buf = Vec::with_capacity(len);
for i in 0..len {
let val = self.0[HEIGHT - y - i - 1][x + i];
let y = self.0.height() - y - i - 1;
let val = self.1[y][x + i];
buf.push(val);
}
buf
@@ -47,13 +43,14 @@ impl AnimeDiagonal {
path: &Path,
duration: Option<Duration>,
bright: f32,
) -> Result<Self, AnimeError> {
anime_type: AnimeType,
) -> Result<Self> {
let data = std::fs::read(path)?;
let data = std::io::Cursor::new(data);
let decoder = png_pong::Decoder::new(data)?.into_steps();
let png_pong::Step { raster, delay: _ } = decoder.last().ok_or(AnimeError::NoFrames)??;
let mut matrix = AnimeDiagonal::new(duration);
let mut matrix = AnimeDiagonal::new(anime_type, duration);
match raster {
png_pong::PngRaster::Gray8(ras) => {
@@ -100,7 +97,9 @@ impl AnimeDiagonal {
+ (<u8>::from(px.two()) / 3) as f32
+ (<u8>::from(px.three()) / 3) as f32
};
matrix.0[y][x] = (v * bright) as u8;
if y < matrix.1.len() && x < matrix.1[y].len() {
matrix.1[y][x] = (v * bright) as u8;
}
}
}
}
@@ -123,75 +122,547 @@ impl AnimeDiagonal {
+ ((<u16>::from(px.two()) / 3) >> 8) as f32
+ ((<u16>::from(px.three()) / 3) >> 8) as f32
};
matrix.0[y][x] = (v * bright) as u8;
matrix.1[y][x] = (v * bright) as u8;
}
}
}
}
impl From<&AnimeDiagonal> for AnimeDataBuffer {
/// Convert to a data buffer that can be sent over dbus
#[inline]
pub fn into_data_buffer(&self, anime_type: AnimeType) -> Result<AnimeDataBuffer> {
match anime_type {
AnimeType::GA401 => self.into_ga401_packets(),
AnimeType::GA402 => self.into_ga402_packets(),
}
}
/// Do conversion from the nested Vec in AnimeMatrix to the two required
/// packets suitable for sending over USB
#[inline]
fn from(anime: &AnimeDiagonal) -> Self {
let mut buf = vec![0u8; ANIME_DATA_LEN];
fn into_ga401_packets(&self) -> Result<AnimeDataBuffer> {
let mut buf = vec![0u8; AnimeType::GA401.data_length()];
buf[1..=32].copy_from_slice(&anime.get_row(0, 3, 32));
buf[34..=66].copy_from_slice(&anime.get_row(0, 2, 33));
buf[69..=101].copy_from_slice(&anime.get_row(1, 2, 33)); // ?!
buf[102..=134].copy_from_slice(&anime.get_row(1, 1, 33));
buf[137..=169].copy_from_slice(&anime.get_row(2, 1, 33));
buf[170..=202].copy_from_slice(&anime.get_row(2, 0, 33));
buf[204..=236].copy_from_slice(&anime.get_row(3, 0, 33));
buf[237..=268].copy_from_slice(&anime.get_row(4, 0, 32));
buf[270..=301].copy_from_slice(&anime.get_row(5, 0, 32));
buf[302..=332].copy_from_slice(&anime.get_row(6, 0, 31));
buf[334..=364].copy_from_slice(&anime.get_row(7, 0, 31));
buf[365..=394].copy_from_slice(&anime.get_row(8, 0, 30));
buf[396..=425].copy_from_slice(&anime.get_row(9, 0, 30));
buf[426..=454].copy_from_slice(&anime.get_row(10, 0, 29));
buf[456..=484].copy_from_slice(&anime.get_row(11, 0, 29));
buf[485..=512].copy_from_slice(&anime.get_row(12, 0, 28));
buf[514..=541].copy_from_slice(&anime.get_row(13, 0, 28));
buf[542..=568].copy_from_slice(&anime.get_row(14, 0, 27));
buf[570..=596].copy_from_slice(&anime.get_row(15, 0, 27));
buf[597..=622].copy_from_slice(&anime.get_row(16, 0, 26));
buf[624..=649].copy_from_slice(&anime.get_row(17, 0, 26));
buf[650..=674].copy_from_slice(&anime.get_row(18, 0, 25));
buf[676..=700].copy_from_slice(&anime.get_row(19, 0, 25));
buf[701..=724].copy_from_slice(&anime.get_row(20, 0, 24));
buf[726..=749].copy_from_slice(&anime.get_row(21, 0, 24));
buf[750..=772].copy_from_slice(&anime.get_row(22, 0, 23));
buf[774..=796].copy_from_slice(&anime.get_row(23, 0, 23));
buf[797..=818].copy_from_slice(&anime.get_row(24, 0, 22));
buf[820..=841].copy_from_slice(&anime.get_row(25, 0, 22));
buf[842..=862].copy_from_slice(&anime.get_row(26, 0, 21));
buf[864..=884].copy_from_slice(&anime.get_row(27, 0, 21));
buf[885..=904].copy_from_slice(&anime.get_row(28, 0, 20));
buf[906..=925].copy_from_slice(&anime.get_row(29, 0, 20));
buf[926..=944].copy_from_slice(&anime.get_row(30, 0, 19));
buf[946..=964].copy_from_slice(&anime.get_row(31, 0, 19));
buf[965..=982].copy_from_slice(&anime.get_row(32, 0, 18));
buf[984..=1001].copy_from_slice(&anime.get_row(33, 0, 18));
buf[1002..=1018].copy_from_slice(&anime.get_row(34, 0, 17));
buf[1020..=1036].copy_from_slice(&anime.get_row(35, 0, 17));
buf[1037..=1052].copy_from_slice(&anime.get_row(36, 0, 16));
buf[1054..=1069].copy_from_slice(&anime.get_row(37, 0, 16));
buf[1070..=1084].copy_from_slice(&anime.get_row(38, 0, 15));
buf[1086..=1100].copy_from_slice(&anime.get_row(39, 0, 15));
buf[1101..=1114].copy_from_slice(&anime.get_row(40, 0, 14));
buf[1116..=1129].copy_from_slice(&anime.get_row(41, 0, 14));
buf[1130..=1142].copy_from_slice(&anime.get_row(42, 0, 13));
buf[1144..=1156].copy_from_slice(&anime.get_row(43, 0, 13));
buf[1157..=1168].copy_from_slice(&anime.get_row(44, 0, 12));
buf[1170..=1181].copy_from_slice(&anime.get_row(45, 0, 12));
buf[1182..=1192].copy_from_slice(&anime.get_row(46, 0, 11));
buf[1194..=1204].copy_from_slice(&anime.get_row(47, 0, 11));
buf[1205..=1214].copy_from_slice(&anime.get_row(48, 0, 10));
buf[1216..=1225].copy_from_slice(&anime.get_row(49, 0, 10));
buf[1226..=1234].copy_from_slice(&anime.get_row(50, 0, 9));
buf[1236..=1244].copy_from_slice(&anime.get_row(51, 0, 9));
buf[1..=32].copy_from_slice(&self.get_row(0, 3, 32));
buf[34..=66].copy_from_slice(&self.get_row(0, 2, 33));
buf[69..=101].copy_from_slice(&self.get_row(1, 2, 33)); // ?!
buf[102..=134].copy_from_slice(&self.get_row(1, 1, 33));
buf[137..=169].copy_from_slice(&self.get_row(2, 1, 33));
buf[170..=202].copy_from_slice(&self.get_row(2, 0, 33));
buf[204..=236].copy_from_slice(&self.get_row(3, 0, 33)); // This and above cause overflow?
buf[237..=268].copy_from_slice(&self.get_row(4, 0, 32));
buf[270..=301].copy_from_slice(&self.get_row(5, 0, 32));
buf[302..=332].copy_from_slice(&self.get_row(6, 0, 31));
buf[334..=364].copy_from_slice(&self.get_row(7, 0, 31));
buf[365..=394].copy_from_slice(&self.get_row(8, 0, 30));
buf[396..=425].copy_from_slice(&self.get_row(9, 0, 30));
buf[426..=454].copy_from_slice(&self.get_row(10, 0, 29));
buf[456..=484].copy_from_slice(&self.get_row(11, 0, 29));
buf[485..=512].copy_from_slice(&self.get_row(12, 0, 28));
buf[514..=541].copy_from_slice(&self.get_row(13, 0, 28));
buf[542..=568].copy_from_slice(&self.get_row(14, 0, 27));
buf[570..=596].copy_from_slice(&self.get_row(15, 0, 27));
buf[597..=622].copy_from_slice(&self.get_row(16, 0, 26));
buf[624..=649].copy_from_slice(&self.get_row(17, 0, 26));
buf[650..=674].copy_from_slice(&self.get_row(18, 0, 25));
buf[676..=700].copy_from_slice(&self.get_row(19, 0, 25));
buf[701..=724].copy_from_slice(&self.get_row(20, 0, 24));
buf[726..=749].copy_from_slice(&self.get_row(21, 0, 24));
buf[750..=772].copy_from_slice(&self.get_row(22, 0, 23));
buf[774..=796].copy_from_slice(&self.get_row(23, 0, 23));
buf[797..=818].copy_from_slice(&self.get_row(24, 0, 22));
buf[820..=841].copy_from_slice(&self.get_row(25, 0, 22));
buf[842..=862].copy_from_slice(&self.get_row(26, 0, 21));
buf[864..=884].copy_from_slice(&self.get_row(27, 0, 21));
buf[885..=904].copy_from_slice(&self.get_row(28, 0, 20));
buf[906..=925].copy_from_slice(&self.get_row(29, 0, 20));
buf[926..=944].copy_from_slice(&self.get_row(30, 0, 19));
buf[946..=964].copy_from_slice(&self.get_row(31, 0, 19));
buf[965..=982].copy_from_slice(&self.get_row(32, 0, 18));
buf[984..=1001].copy_from_slice(&self.get_row(33, 0, 18));
buf[1002..=1018].copy_from_slice(&self.get_row(34, 0, 17));
buf[1020..=1036].copy_from_slice(&self.get_row(35, 0, 17));
buf[1037..=1052].copy_from_slice(&self.get_row(36, 0, 16));
buf[1054..=1069].copy_from_slice(&self.get_row(37, 0, 16));
buf[1070..=1084].copy_from_slice(&self.get_row(38, 0, 15));
buf[1086..=1100].copy_from_slice(&self.get_row(39, 0, 15));
buf[1101..=1114].copy_from_slice(&self.get_row(40, 0, 14));
buf[1116..=1129].copy_from_slice(&self.get_row(41, 0, 14));
buf[1130..=1142].copy_from_slice(&self.get_row(42, 0, 13));
buf[1144..=1156].copy_from_slice(&self.get_row(43, 0, 13));
buf[1157..=1168].copy_from_slice(&self.get_row(44, 0, 12));
buf[1170..=1181].copy_from_slice(&self.get_row(45, 0, 12));
buf[1182..=1192].copy_from_slice(&self.get_row(46, 0, 11));
buf[1194..=1204].copy_from_slice(&self.get_row(47, 0, 11));
buf[1205..=1214].copy_from_slice(&self.get_row(48, 0, 10));
buf[1216..=1225].copy_from_slice(&self.get_row(49, 0, 10));
buf[1226..=1234].copy_from_slice(&self.get_row(50, 0, 9));
buf[1236..=1244].copy_from_slice(&self.get_row(51, 0, 9));
AnimeDataBuffer::from_vec(buf)
AnimeDataBuffer::from_vec(crate::AnimeType::GA401, buf)
}
fn into_ga402_packets(&self) -> Result<AnimeDataBuffer> {
let mut buf = vec![0u8; AnimeType::GA402.data_length()];
let mut start_index: usize = 0;
fn copy_slice(
buf: &mut Vec<u8>,
anime: &AnimeDiagonal,
x: usize,
y: usize,
start_index: &mut usize,
len: usize,
) {
buf[*start_index..*start_index + len].copy_from_slice(&anime.get_row(x, y, len));
*start_index += len;
}
let b = &mut buf;
let a = &self;
copy_slice(b, a, 0, 5, &mut start_index, 34);
copy_slice(b, a, 1, 5, &mut start_index, 34);
copy_slice(b, a, 1, 4, &mut start_index, 34);
copy_slice(b, a, 2, 4, &mut start_index, 34);
copy_slice(b, a, 2, 3, &mut start_index, 34);
copy_slice(b, a, 3, 3, &mut start_index, 34);
copy_slice(b, a, 3, 2, &mut start_index, 34);
copy_slice(b, a, 4, 2, &mut start_index, 34);
copy_slice(b, a, 4, 1, &mut start_index, 34);
copy_slice(b, a, 5, 1, &mut start_index, 34);
copy_slice(b, a, 5, 0, &mut start_index, 34);
copy_slice(b, a, 6, 0, &mut start_index, 34);
copy_slice(b, a, 7, 0, &mut start_index, 33);
copy_slice(b, a, 8, 0, &mut start_index, 33);
copy_slice(b, a, 9, 0, &mut start_index, 32);
copy_slice(b, a, 10, 0, &mut start_index, 32);
copy_slice(b, a, 11, 0, &mut start_index, 31);
copy_slice(b, a, 12, 0, &mut start_index, 31);
copy_slice(b, a, 13, 0, &mut start_index, 30);
copy_slice(b, a, 14, 0, &mut start_index, 30);
copy_slice(b, a, 15, 0, &mut start_index, 29);
copy_slice(b, a, 16, 0, &mut start_index, 29);
copy_slice(b, a, 17, 0, &mut start_index, 28);
copy_slice(b, a, 18, 0, &mut start_index, 28);
copy_slice(b, a, 19, 0, &mut start_index, 27);
copy_slice(b, a, 20, 0, &mut start_index, 27);
copy_slice(b, a, 21, 0, &mut start_index, 26);
copy_slice(b, a, 22, 0, &mut start_index, 26);
copy_slice(b, a, 23, 0, &mut start_index, 25);
copy_slice(b, a, 24, 0, &mut start_index, 25);
copy_slice(b, a, 25, 0, &mut start_index, 24);
copy_slice(b, a, 26, 0, &mut start_index, 24);
copy_slice(b, a, 27, 0, &mut start_index, 23);
copy_slice(b, a, 28, 0, &mut start_index, 23);
copy_slice(b, a, 29, 0, &mut start_index, 22);
copy_slice(b, a, 30, 0, &mut start_index, 22);
copy_slice(b, a, 31, 0, &mut start_index, 21);
copy_slice(b, a, 32, 0, &mut start_index, 21);
copy_slice(b, a, 33, 0, &mut start_index, 20);
copy_slice(b, a, 34, 0, &mut start_index, 20);
copy_slice(b, a, 35, 0, &mut start_index, 19);
copy_slice(b, a, 36, 0, &mut start_index, 19);
copy_slice(b, a, 37, 0, &mut start_index, 18);
copy_slice(b, a, 38, 0, &mut start_index, 18);
copy_slice(b, a, 39, 0, &mut start_index, 17);
copy_slice(b, a, 40, 0, &mut start_index, 17);
copy_slice(b, a, 41, 0, &mut start_index, 16);
copy_slice(b, a, 42, 0, &mut start_index, 16);
copy_slice(b, a, 43, 0, &mut start_index, 15);
copy_slice(b, a, 44, 0, &mut start_index, 15);
copy_slice(b, a, 45, 0, &mut start_index, 14);
copy_slice(b, a, 46, 0, &mut start_index, 14);
copy_slice(b, a, 47, 0, &mut start_index, 13);
copy_slice(b, a, 48, 0, &mut start_index, 13);
copy_slice(b, a, 49, 0, &mut start_index, 12);
copy_slice(b, a, 50, 0, &mut start_index, 12);
copy_slice(b, a, 51, 0, &mut start_index, 11);
copy_slice(b, a, 52, 0, &mut start_index, 11);
copy_slice(b, a, 53, 0, &mut start_index, 10);
copy_slice(b, a, 54, 0, &mut start_index, 10);
copy_slice(b, a, 55, 0, &mut start_index, 9);
AnimeDataBuffer::from_vec(crate::AnimeType::GA402, buf)
}
}
#[cfg(test)]
mod tests {
use std::{convert::TryFrom, path::PathBuf};
use crate::{AnimeDiagonal, AnimePacketType, AnimeType};
#[test]
fn ga401_diagonal_packet_check() {
let pkt0_check = [
0x5e, 0xc0, 0x2, 0x1, 0x0, 0x73, 0x2, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0,
];
let pkt1_check = [
0x5e, 0xc0, 0x2, 0x74, 0x2, 0x73, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("test/ga401-diagonal.png");
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA401).unwrap();
let data = matrix.into_data_buffer(crate::AnimeType::GA401).unwrap();
let pkt = AnimePacketType::try_from(data).unwrap();
assert_eq!(pkt[0], pkt0_check);
assert_eq!(pkt[1], pkt1_check);
}
#[test]
fn ga402_diagonal_packet_check() {
let pkt0_check = [
0x5e, 0xc0, 0x2, 0x1, 0x0, 0x73, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0,
];
let pkt1_check = [
0x5e, 0xc0, 0x2, 0x74, 0x2, 0x73, 0x2, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let pkt2_check = [
0x5e, 0xc0, 0x2, 0xe7, 0x4, 0x73, 0x2, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("test/ga402-diagonal.png");
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA402).unwrap();
let data = matrix.into_data_buffer(crate::AnimeType::GA402).unwrap();
let pkt = AnimePacketType::try_from(data).unwrap();
assert_eq!(pkt[0], pkt0_check);
assert_eq!(pkt[1], pkt1_check);
assert_eq!(pkt[2], pkt2_check);
}
#[test]
#[ignore = "Needs the packets verified with capture"]
fn ga402_diagonal_fullbright_packet_check() {
let pkt0_check = [
0x5e, 0xc0, 0x2, 0x1, 0x0, 0x73, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x67, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let pkt1_check = [
0x5e, 0xc0, 0x2, 0x74, 0x2, 0x73, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let pkt2_check = [
0x5e, 0xc0, 0x2, 0xe7, 0x4, 0x73, 0x2, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("test/ga402-diagonal-fullbright.png");
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA402).unwrap();
let data = matrix.into_data_buffer(crate::AnimeType::GA402).unwrap();
let pkt = AnimePacketType::try_from(data).unwrap();
assert_eq!(pkt[0], pkt0_check);
assert_eq!(pkt[1], pkt1_check);
assert_eq!(pkt[2], pkt2_check);
}
}

View File

@@ -3,6 +3,8 @@ use png_pong::decode::Error as PngError;
use std::error::Error;
use std::fmt;
pub type Result<T> = std::result::Result<T, AnimeError>;
#[derive(Debug)]
pub enum AnimeError {
NoFrames,
@@ -13,6 +15,13 @@ pub enum AnimeError {
/// The input was incorrect size, expected size is `IncorrectSize(width, height)`
IncorrectSize(u32, u32),
Dbus(String),
Udev(String, std::io::Error),
NoDevice,
UnsupportedDevice,
InvalidBrightness(f32),
DataBufferLength,
PixelGifWidth(usize),
PixelGifHeight(usize),
}
impl fmt::Display for AnimeError {
@@ -30,6 +39,25 @@ impl fmt::Display for AnimeError {
width, height
),
AnimeError::Dbus(detail) => write!(f, "{}", detail),
AnimeError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
AnimeError::NoDevice => write!(f, "No AniMe Matrix device found"),
AnimeError::DataBufferLength => write!(
f,
"The data buffer was incorrect length for generating USB packets"
),
AnimeError::UnsupportedDevice => write!(f, "Unsupported AniMe Matrix device found"),
AnimeError::InvalidBrightness(bright) => write!(
f,
"Image brightness must be between 0.0 and 1.0 (inclusive), was {}",
bright
),
AnimeError::PixelGifWidth(n) => {
write!(f, "The gif used for pixel-perfect gif is is wider than {n}")
}
AnimeError::PixelGifHeight(n) => write!(
f,
"The gif used for pixel-perfect gif is is taller than {n}"
),
}
}
}
@@ -56,3 +84,10 @@ impl From<DecodingError> for AnimeError {
AnimeError::Gif(err)
}
}
impl From<AnimeError> for zbus::fdo::Error {
#[inline]
fn from(err: AnimeError) -> Self {
zbus::fdo::Error::Failed(format!("{}", err))
}
}

View File

@@ -1,8 +1,10 @@
use glam::Vec2;
use serde_derive::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::{fs::File, path::Path, time::Duration};
use crate::{error::AnimeError, AnimeDataBuffer, AnimeDiagonal, AnimeImage, Pixel};
use crate::error::AnimeError;
use crate::{error::Result, AnimeDataBuffer, AnimeDiagonal, AnimeImage, AnimeType, Pixel};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AnimeFrame {
@@ -88,12 +90,13 @@ pub struct AnimeGif(Vec<AnimeFrame>, AnimTime);
impl AnimeGif {
/// Create an animation using the 74x36 ASUS gif format
#[inline]
pub fn create_diagonal_gif(
pub fn from_diagonal_gif(
file_name: &Path,
duration: AnimTime,
brightness: f32,
) -> Result<Self, AnimeError> {
let mut matrix = AnimeDiagonal::new(None);
anime_type: AnimeType,
) -> Result<Self> {
let mut matrix = AnimeDiagonal::new(anime_type, None);
let mut decoder = gif::DecodeOptions::new();
// Configure the decoder such that it will expand the image to RGBA.
@@ -115,13 +118,22 @@ impl AnimeGif {
// should be t but not in some gifs? What, ASUS, what?
continue;
}
matrix.get_mut()[y + frame.top as usize][x + frame.left as usize] =
(px[0] as f32 * brightness) as u8;
let tmp = matrix.get_mut();
let y = y + frame.top as usize;
if y >= tmp.len() {
return Err(AnimeError::PixelGifHeight(tmp.len()));
}
let x = x + frame.left as usize;
if x >= tmp[y].len() {
return Err(AnimeError::PixelGifWidth(tmp[y].len()));
}
matrix.get_mut()[y][x] = (px[0] as f32 * brightness) as u8;
}
}
frames.push(AnimeFrame {
data: <AnimeDataBuffer>::from(&matrix),
data: matrix.into_data_buffer(anime_type)?,
delay: Duration::from_millis(wait as u64),
});
}
@@ -130,12 +142,13 @@ impl AnimeGif {
/// Create an animation using the 74x36 ASUS gif format from a png
#[inline]
pub fn create_diagonal_png(
pub fn from_diagonal_png(
file_name: &Path,
anime_type: AnimeType,
duration: AnimTime,
brightness: f32,
) -> Result<Self, AnimeError> {
let image = AnimeDiagonal::from_png(file_name, None, brightness)?;
) -> Result<Self> {
let image = AnimeDiagonal::from_png(file_name, None, brightness, anime_type)?;
let mut total = Duration::from_millis(1000);
if let AnimTime::Fade(fade) = duration {
@@ -148,7 +161,7 @@ impl AnimeGif {
let frame_count = total.as_millis() / 30;
let single = AnimeFrame {
data: <AnimeDataBuffer>::from(&image),
data: image.into_data_buffer(anime_type)?,
delay: Duration::from_millis(30),
};
let frames = vec![single; frame_count as usize];
@@ -159,14 +172,15 @@ impl AnimeGif {
/// Create an animation using a gif of any size. This method must precompute the
/// result.
#[inline]
pub fn create_png_gif(
pub fn from_gif(
file_name: &Path,
scale: f32,
angle: f32,
translation: Vec2,
duration: AnimTime,
brightness: f32,
) -> Result<Self, AnimeError> {
anime_type: AnimeType,
) -> Result<Self> {
let mut frames = Vec::new();
let mut decoder = gif::DecodeOptions::new();
@@ -187,7 +201,8 @@ impl AnimeGif {
brightness,
pixels,
decoder.width() as u32,
);
anime_type,
)?;
while let Some(frame) = decoder.read_next_frame()? {
let wait = frame.delay * 10;
@@ -201,7 +216,8 @@ impl AnimeGif {
brightness,
pixels,
width as u32,
);
anime_type,
)?;
}
for (y, row) in frame.buffer.chunks(frame.width as usize * 4).enumerate() {
for (x, px) in row.chunks(4).enumerate() {
@@ -220,7 +236,7 @@ impl AnimeGif {
image.update();
frames.push(AnimeFrame {
data: <AnimeDataBuffer>::from(&image),
data: <AnimeDataBuffer>::try_from(&image)?,
delay: Duration::from_millis(wait as u64),
});
}
@@ -231,15 +247,17 @@ impl AnimeGif {
/// will be 1 second long. If `AnimTime::Cycles` is specified for `duration` then this can
/// be considered how many seconds the image will show for.
#[inline]
pub fn create_png_static(
pub fn from_png(
file_name: &Path,
scale: f32,
angle: f32,
translation: Vec2,
duration: AnimTime,
brightness: f32,
) -> Result<Self, AnimeError> {
let image = AnimeImage::from_png(file_name, scale, angle, translation, brightness)?;
anime_type: AnimeType,
) -> Result<Self> {
let image =
AnimeImage::from_png(file_name, scale, angle, translation, brightness, anime_type)?;
let mut total = Duration::from_millis(1000);
if let AnimTime::Fade(fade) = duration {
@@ -252,7 +270,7 @@ impl AnimeGif {
let frame_count = total.as_millis() / 30;
let single = AnimeFrame {
data: <AnimeDataBuffer>::from(&image),
data: <AnimeDataBuffer>::try_from(&image)?,
delay: Duration::from_millis(30),
};
let frames = vec![single; frame_count as usize];

View File

@@ -1,8 +1,10 @@
use std::time::Duration;
use std::convert::TryFrom;
use crate::data::{AnimeDataBuffer, ANIME_DATA_LEN};
use crate::image::LED_IMAGE_POSITIONS;
use crate::data::AnimeDataBuffer;
use crate::error::{AnimeError, Result};
use crate::{AnimeImage, AnimeType};
// TODO: Adjust these sizes as WIDTH_GA401 WIDTH_GA402
const WIDTH: usize = 33;
const HEIGHT: usize = 55;
@@ -14,41 +16,40 @@ const HEIGHT: usize = 55;
///
/// **Note:** the columns in each odd row are offset by half a pixel left
#[derive(Debug, Clone)]
pub struct AnimeGrid([[u8; WIDTH]; HEIGHT], Option<Duration>);
impl Default for AnimeGrid {
#[inline]
fn default() -> Self {
Self::new(None)
}
pub struct AnimeGrid {
anime_type: AnimeType,
data: [[u8; WIDTH]; HEIGHT],
}
impl AnimeGrid {
#[inline]
pub fn new(duration: Option<Duration>) -> Self {
AnimeGrid([[0u8; WIDTH]; HEIGHT], duration)
pub fn new(anime_type: AnimeType) -> Self {
Self {
anime_type,
data: [[0u8; WIDTH]; HEIGHT],
}
}
/// Set a position in the grid with a brightness value
#[inline]
pub fn set(&mut self, x: usize, y: usize, b: u8) {
self.0[y][x] = b;
self.data[y][x] = b;
}
#[inline]
pub fn get(&self) -> &[[u8; WIDTH]; HEIGHT] {
&self.0
&self.data
}
#[inline]
pub fn get_mut(&mut self) -> &mut [[u8; WIDTH]; HEIGHT] {
&mut self.0
&mut self.data
}
/// Fill the grid with a value
#[inline]
pub fn fill_with(&mut self, fill: u8) {
for row in self.0.iter_mut() {
for row in self.data.iter_mut() {
for x in row.iter_mut() {
*x = fill;
}
@@ -89,21 +90,25 @@ impl AnimeGrid {
// }
}
impl From<AnimeGrid> for AnimeDataBuffer {
impl TryFrom<AnimeGrid> for AnimeDataBuffer {
type Error = AnimeError;
/// Do conversion from the nested Vec in AniMeMatrix to the two required
/// packets suitable for sending over USB
#[inline]
fn from(anime: AnimeGrid) -> Self {
let mut buf = vec![0u8; ANIME_DATA_LEN];
fn try_from(anime: AnimeGrid) -> Result<Self> {
let mut buf = vec![0u8; anime.anime_type.data_length()];
for (idx, pos) in LED_IMAGE_POSITIONS.iter().enumerate() {
for (idx, pos) in AnimeImage::generate_image_positioning(anime.anime_type)
.iter()
.enumerate()
{
if let Some(pos) = pos {
let x = pos.x().ceil() as usize;
let y = pos.y().ceil() as usize;
buf[idx + 1] = anime.0[y][x];
buf[idx + 1] = anime.data[y][x];
}
}
AnimeDataBuffer::from_vec(buf)
AnimeDataBuffer::from_vec(anime.anime_type, buf)
}
}
@@ -113,7 +118,7 @@ mod tests {
#[test]
fn check_data_alignment() {
let mut matrix = AnimeGrid::new(None);
let mut matrix = AnimeGrid::new(AnimeType::GA401);
{
let tmp = matrix.get_mut();
for row in tmp.iter_mut() {
@@ -122,7 +127,7 @@ mod tests {
}
}
let matrix = <AnimeDataBuffer>::from(matrix);
let matrix = <AnimeDataBuffer>::try_from(matrix).unwrap();
let data_cmp = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -171,6 +176,6 @@ mod tests {
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0,
0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
assert_eq!(matrix.get(), &data_cmp);
assert_eq!(matrix.data(), &data_cmp);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,11 @@
use std::{path::PathBuf, time::Duration};
use glam::Vec2;
use serde_derive::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::{path::PathBuf, time::Duration};
use crate::{error::AnimeError, AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage};
use crate::{
error::Result, AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType,
};
/// All the possible AniMe actions that can be used. This enum is intended to be
/// a helper for loading up `ActionData`.
@@ -63,26 +65,34 @@ pub enum ActionData {
}
impl ActionData {
pub fn from_anime_action(action: &ActionLoader) -> Result<ActionData, AnimeError> {
pub fn from_anime_action(anime_type: AnimeType, action: &ActionLoader) -> Result<ActionData> {
let a = match action {
ActionLoader::AsusAnimation {
file,
time,
brightness,
} => ActionData::Animation(AnimeGif::create_diagonal_gif(file, *time, *brightness)?),
} => ActionData::Animation(AnimeGif::from_diagonal_gif(
file,
*time,
*brightness,
anime_type,
)?),
ActionLoader::AsusImage {
file,
time,
brightness,
} => match time {
AnimTime::Infinite => {
let image = AnimeDiagonal::from_png(file, None, *brightness)?;
let data = <AnimeDataBuffer>::from(&image);
let image = AnimeDiagonal::from_png(file, None, *brightness, anime_type)?;
let data = image.into_data_buffer(anime_type)?;
ActionData::Image(Box::new(data))
}
_ => {
ActionData::Animation(AnimeGif::create_diagonal_png(file, *time, *brightness)?)
}
_ => ActionData::Animation(AnimeGif::from_diagonal_png(
file,
anime_type,
*time,
*brightness,
)?),
},
ActionLoader::ImageAnimation {
file,
@@ -94,23 +104,25 @@ impl ActionData {
} => {
if let Some(ext) = file.extension() {
if ext.to_string_lossy().to_lowercase() == "png" {
return Ok(ActionData::Animation(AnimeGif::create_png_static(
return Ok(ActionData::Animation(AnimeGif::from_png(
file,
*scale,
*angle,
*translation,
*time,
*brightness,
anime_type,
)?));
}
}
ActionData::Animation(AnimeGif::create_png_gif(
ActionData::Animation(AnimeGif::from_gif(
file,
*scale,
*angle,
*translation,
*time,
*brightness,
anime_type,
)?)
}
ActionLoader::Image {
@@ -124,18 +136,25 @@ impl ActionData {
match time {
AnimTime::Infinite => {
// If no time then create a plain static image
let image =
AnimeImage::from_png(file, *scale, *angle, *translation, *brightness)?;
let data = <AnimeDataBuffer>::from(&image);
let image = AnimeImage::from_png(
file,
*scale,
*angle,
*translation,
*brightness,
anime_type,
)?;
let data = <AnimeDataBuffer>::try_from(&image)?;
ActionData::Image(Box::new(data))
}
_ => ActionData::Animation(AnimeGif::create_png_static(
_ => ActionData::Animation(AnimeGif::from_png(
file,
*scale,
*angle,
*translation,
*time,
*brightness,
anime_type,
)?),
}
}
@@ -146,20 +165,21 @@ impl ActionData {
}
/// An optimised precomputed set of actions that the user can cycle through
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct Sequences(Vec<ActionData>);
#[derive(Debug, Deserialize, Serialize)]
pub struct Sequences(Vec<ActionData>, AnimeType);
impl Sequences {
#[inline]
pub fn new() -> Self {
Self(Vec::new())
pub fn new(anime_type: AnimeType) -> Self {
Self(Vec::new(), anime_type)
}
/// Use a base `AnimeAction` to generate the precomputed data and insert in to
/// the run buffer
#[inline]
pub fn insert(&mut self, index: usize, action: &ActionLoader) -> Result<(), AnimeError> {
self.0.insert(index, ActionData::from_anime_action(action)?);
pub fn insert(&mut self, index: usize, action: &ActionLoader) -> Result<()> {
self.0
.insert(index, ActionData::from_anime_action(self.1, action)?);
Ok(())
}

View File

@@ -7,6 +7,8 @@
//!
//! Step 1 need to applied only on fresh system boot.
use crate::{error::AnimeError, AnimeType};
const INIT_STR: [u8; 15] = [
0x5e, b'A', b'S', b'U', b'S', b' ', b'T', b'e', b'c', b'h', b'.', b'I', b'n', b'c', b'.',
];
@@ -15,6 +17,49 @@ const DEV_PAGE: u8 = 0x5e;
pub const VENDOR_ID: u16 = 0x0b05;
pub const PROD_ID: u16 = 0x193b;
/// `get_anime_type` is very broad, matching on part of the laptop board name only. For this
/// reason `find_node()` must be used also to verify if the USB device is available.
///
/// The currently known USB device is `19b6`.
#[inline]
pub fn get_anime_type() -> Result<AnimeType, AnimeError> {
let dmi = sysfs_class::DmiId::default();
let board_name = dmi.board_name()?;
if board_name.contains("GA401I") {
return Ok(AnimeType::GA401);
} else if board_name.contains("GA401Q") {
return Ok(AnimeType::GA401);
} else if board_name.contains("GA402R") {
return Ok(AnimeType::GA402);
}
Err(AnimeError::UnsupportedDevice)
}
/// Find the USB device node - known devices so far: `19b6`
#[inline]
pub fn find_node(id_product: &str) -> Result<String, AnimeError> {
let mut enumerator =
udev::Enumerator::new().map_err(|err| AnimeError::Udev("enumerator failed".into(), err))?;
enumerator
.match_subsystem("usb")
.map_err(|err| AnimeError::Udev("match_subsystem failed".into(), err))?;
for device in enumerator
.scan_devices()
.map_err(|err| AnimeError::Udev("scan_devices failed".into(), err))?
{
if let Some(attr) = device.attribute_value("idProduct") {
if attr == id_product {
if let Some(dev_node) = device.devnode() {
return Ok(dev_node.to_string_lossy().to_string());
}
}
}
}
Err(AnimeError::NoDevice)
}
/// Get the two device initialization packets. These are required for device start
/// after the laptop boots.
#[inline]

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -1,6 +1,6 @@
[package]
name = "rog_aura"
version = "1.1.0"
version = "1.2.0"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -14,11 +14,10 @@ exclude = ["data"]
[features]
default = ["dbus"]
dbus = ["zvariant", "zvariant_derive"]
dbus = ["zvariant"]
[dependencies]
serde = "^1.0"
serde_derive = "^1.0"
zvariant = { version = "^2.6", optional = true }
zvariant_derive = { version = "^2.6", optional = true }
zvariant = { version = "^3.0", optional = true }

View File

@@ -7,17 +7,10 @@ pub const LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
use serde_derive::{Deserialize, Serialize};
use std::str::FromStr;
#[cfg(feature = "dbus")]
use zvariant_derive::Type;
use zvariant::Type;
use crate::{error::Error, LED_MSG_LEN};
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize)]
pub struct LedPowerStates {
pub enabled: bool,
pub sleep_anim_enabled: bool,
}
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum LedBrightness {
@@ -70,6 +63,22 @@ impl FromStr for Colour {
}
}
impl From<&[f32; 3]> for Colour {
fn from(c: &[f32; 3]) -> Self {
Self(
(255.0 * c[0]) as u8,
(255.0 * c[1]) as u8,
(255.0 * c[2]) as u8,
)
}
}
impl From<Colour> for [f32; 3] {
fn from(c: Colour) -> Self {
[c.0 as f32 / 255.0, c.1 as f32 / 255.0, c.2 as f32 / 255.0]
}
}
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum Speed {
@@ -145,18 +154,38 @@ pub enum AuraModeNum {
Flash = 12,
}
impl From<AuraModeNum> for String {
fn from(mode: AuraModeNum) -> Self {
match mode {
AuraModeNum::Static => "Static",
AuraModeNum::Breathe => "Breathe",
AuraModeNum::Strobe => "Strobe",
AuraModeNum::Rainbow => "Rainbow",
AuraModeNum::Star => "Stars",
AuraModeNum::Rain => "Rain",
AuraModeNum::Highlight => "Highlight",
AuraModeNum::Laser => "Laser",
AuraModeNum::Ripple => "Ripple",
AuraModeNum::Pulse => "Pulse",
AuraModeNum::Comet => "Comet",
AuraModeNum::Flash => "Flash",
}
.to_string()
}
}
impl From<&AuraModeNum> for &str {
fn from(mode: &AuraModeNum) -> Self {
match mode {
AuraModeNum::Static => "Static",
AuraModeNum::Breathe => "Breathing",
AuraModeNum::Strobe => "Strobing",
AuraModeNum::Breathe => "Breathe",
AuraModeNum::Strobe => "Strobe",
AuraModeNum::Rainbow => "Rainbow",
AuraModeNum::Star => "Stars",
AuraModeNum::Rain => "Rain",
AuraModeNum::Highlight => "Keypress Highlight",
AuraModeNum::Laser => "Keypress Laser",
AuraModeNum::Ripple => "Keypress Ripple",
AuraModeNum::Highlight => "Highlight",
AuraModeNum::Laser => "Laser",
AuraModeNum::Ripple => "Ripple",
AuraModeNum::Pulse => "Pulse",
AuraModeNum::Comet => "Comet",
AuraModeNum::Flash => "Flash",
@@ -167,14 +196,14 @@ impl From<&str> for AuraModeNum {
fn from(mode: &str) -> Self {
match mode {
"Static" => AuraModeNum::Static,
"Breathing" => AuraModeNum::Breathe,
"Strobing" => AuraModeNum::Strobe,
"Breathe" => AuraModeNum::Breathe,
"Strobe" => AuraModeNum::Strobe,
"Rainbow" => AuraModeNum::Rainbow,
"Stars" => AuraModeNum::Star,
"Rain" => AuraModeNum::Rain,
"Keypress Highlight" => AuraModeNum::Highlight,
"Keypress Laser" => AuraModeNum::Laser,
"Keypress Ripple" => AuraModeNum::Ripple,
"Highlight" => AuraModeNum::Highlight,
"Laser" => AuraModeNum::Laser,
"Ripple" => AuraModeNum::Ripple,
"Pulse" => AuraModeNum::Pulse,
"Comet" => AuraModeNum::Comet,
"Flash" => AuraModeNum::Flash,
@@ -207,16 +236,60 @@ impl From<u8> for AuraModeNum {
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum AuraZone {
/// Used if keyboard has no zones, or if setting all
None,
One,
Two,
Three,
Four,
/// Leftmost zone
Key1,
/// Zone after leftmost
Key2,
/// Zone second from right
Key3,
/// Rightmost zone
Key4,
/// Logo on the lid (or elsewhere?)
Logo,
/// The left part of a lightbar (typically on the front of laptop)
BarLeft,
/// The right part of a lightbar
BarRight,
}
impl Default for AuraZone {
fn default() -> Self {
AuraZone::None
}
}
impl FromStr for AuraZone {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.to_ascii_lowercase().as_str() {
"0" => Ok(AuraZone::None),
"none" => Ok(AuraZone::None),
"1" => Ok(AuraZone::Key1),
"one" => Ok(AuraZone::Key1),
"2" => Ok(AuraZone::Key2),
"two" => Ok(AuraZone::Key2),
"3" => Ok(AuraZone::Key3),
"three" => Ok(AuraZone::Key3),
"4" => Ok(AuraZone::Key4),
"four" => Ok(AuraZone::Key4),
"5" => Ok(AuraZone::Logo),
"logo" => Ok(AuraZone::Logo),
"6" => Ok(AuraZone::BarLeft),
"lightbar-left" => Ok(AuraZone::BarLeft),
"7" => Ok(AuraZone::BarRight),
"lightbar-right" => Ok(AuraZone::BarRight),
_ => Err(Error::ParseSpeed),
}
}
}
/// Default factory modes structure. This easily converts to an USB HID packet with:
/// ```rust
/// let bytes: [u8; LED_MSG_LEN] = mode.into();
/// // let bytes: [u8; LED_MSG_LEN] = mode.into();
/// ```
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -299,3 +372,100 @@ impl From<&AuraEffect> for [u8; LED_MSG_LEN] {
msg
}
}
#[cfg(test)]
mod tests {
use crate::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed, LED_MSG_LEN};
#[test]
fn check_led_static_packet() {
let st = AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::None,
colour1: Colour(0xff, 0x11, 0xdd),
colour2: Colour::default(),
speed: Speed::Med,
direction: Direction::Right,
};
let ar = <[u8; LED_MSG_LEN]>::from(&st);
println!("{:02x?}", ar);
let check = [
0x5d, 0xb3, 0x0, 0x0, 0xff, 0x11, 0xdd, 0xeb, 0x0, 0x0, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0,
];
assert_eq!(ar, check);
}
#[test]
fn check_led_static_zone_packet() {
let mut st = AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Key1,
colour1: Colour(0xff, 0, 0),
colour2: Colour(0, 0, 0),
speed: Speed::Low,
direction: Direction::Left,
};
let capture = [
0x5d, 0xb3, 0x01, 0x00, 0xff, 0x00, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
st.zone = AuraZone::Key2;
st.colour1 = Colour(0xff, 0xff, 0);
let capture = [
0x5d, 0xb3, 0x02, 0x00, 0xff, 0xff, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
st.zone = AuraZone::Key3;
st.colour1 = Colour(0, 0xff, 0xff);
let capture = [
0x5d, 0xb3, 0x03, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
st.zone = AuraZone::Key4;
st.colour1 = Colour(0xff, 0x00, 0xff);
let capture = [
0x5d, 0xb3, 0x04, 0x00, 0xff, 0x00, 0xff, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
st.zone = AuraZone::Logo;
st.colour1 = Colour(0x2c, 0xff, 0x00);
let capture = [
0x5d, 0xb3, 0x05, 0x00, 0x2c, 0xff, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
st.zone = AuraZone::BarLeft;
st.colour1 = Colour(0xff, 0x00, 0x00);
let capture = [
0x5d, 0xb3, 0x06, 0x00, 0xff, 0x00, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
st.zone = AuraZone::BarRight;
st.colour1 = Colour(0xff, 0x00, 0xcd);
let capture = [
0x5d, 0xb3, 0x07, 0x00, 0xff, 0x00, 0xcd, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
st.mode = AuraModeNum::Rainbow;
let capture = [
0x5d, 0xb3, 0x07, 0x03, 0xff, 0x00, 0xcd, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
}
}

View File

@@ -16,3 +16,12 @@ pub mod error;
pub const LED_MSG_LEN: usize = 17;
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
pub const RED: Colour = Colour(0xff, 0x00, 0x00);
pub const GREEN: Colour = Colour(0x00, 0xff, 0x00);
pub const BLUE: Colour = Colour(0x00, 0x00, 0xff);
pub const VIOLET: Colour = Colour(0x9B, 0x26, 0xB6);
pub const TEAL: Colour = Colour(0x00, 0x7C, 0x80);
pub const YELLOW: Colour = Colour(0xff, 0xef, 0x00);
pub const ORANGE: Colour = Colour(0xff, 0xa4, 0x00);
pub const GRADIENT: [Colour; 7] = [RED, VIOLET, BLUE, TEAL, GREEN, YELLOW, ORANGE];

View File

@@ -1,3 +1,8 @@
use serde::{Deserialize, Serialize};
use std::ops::{BitAnd, BitOr};
#[cfg(feature = "dbus")]
use zvariant::Type;
pub const LED_INIT1: [u8; 2] = [0x5d, 0xb9];
pub const LED_INIT2: &str = "]ASUS Tech.Inc."; // ] == 0x5d
pub const LED_INIT3: [u8; 6] = [0x5d, 0x05, 0x20, 0x31, 0, 0x08];
@@ -15,18 +20,319 @@ pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
]
}
pub const LED_AWAKE_ON_SLEEP_OFF: [u8; 17] = [
0x5d, 0xbd, 0x01, 0xcf, 0x17, 0x0b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum AuraDevice {
X1854,
X1869,
X1866,
X19B6,
Unknown,
}
pub const LED_AWAKE_ON_SLEEP_ON: [u8; 17] = [
0x5d, 0xbd, 0x01, 0xff, 0x1f, 0x0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
impl From<&str> for AuraDevice {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"1866" => AuraDevice::X1866,
"1869" => AuraDevice::X1869,
"1854" => AuraDevice::X1854,
"19b6" => AuraDevice::X19B6,
"0x1866" => AuraDevice::X1866,
"0x1869" => AuraDevice::X1869,
"0x1854" => AuraDevice::X1854,
"0x19b6" => AuraDevice::X19B6,
_ => AuraDevice::Unknown,
}
}
}
pub const LED_AWAKE_OFF_SLEEP_OFF: [u8; 17] = [
0x5d, 0xbd, 0x01, 0xc3, 0x13, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
/// This struct is intended as a helper to pass args to generic dbus interface
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuraPowerDev {
pub x1866: Vec<AuraDev1866>,
pub x19b6: Vec<AuraDev19b6>,
}
pub const LED_AWAKE_OFF_SLEEP_ON: [u8; 17] = [
0x5d, 0xbd, 0x01, 0xf3, 0x1b, 0x0d, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
/// # Bits for older 0x1866 keyboard model
///
/// Keybord and Lightbar require Awake, Boot and Sleep apply to both
/// Keybord and Lightbar regardless of if either are enabled (or Awake is enabled)
///
/// | Byte 1 | Byte 2 | Byte 3 | function | hex |
/// |------------|------------|------------|----------|----------|
/// | 0000, 0000 | 0000, 0000 | 0000, 0010 | Awake | 00,00,02 |
/// | 0000, 1000 | 0000, 0000 | 0000, 0000 | Keyboard | 08,00,00 |
/// | 0000, 0100 | 0000, 0101 | 0000, 0000 | Lightbar | 04,05,00 |
/// | 1100, 0011 | 0001, 0010 | 0000, 1001 | Boot/Sht | c3,12,09 |
/// | 0011, 0000 | 0000, 1000 | 0000, 0100 | Sleep | 30,08,04 |
/// | 1111, 1111 | 0001, 1111 | 0000, 1111 | all on | |
///
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum AuraDev1866 {
Awake = 0x000002,
Keyboard = 0x080000,
Lightbar = 0x040500,
Boot = 0xc31209,
Sleep = 0x300804,
}
impl From<AuraDev1866> for u32 {
fn from(a: AuraDev1866) -> Self {
a as u32
}
}
impl AuraDev1866 {
pub fn to_bytes(control: &[Self]) -> [u8; 3] {
let mut a: u32 = 0;
control.iter().for_each(|n| {
a |= *n as u32;
});
[
((a & 0xff0000) >> 16) as u8,
((a & 0xff00) >> 8) as u8,
(a & 0xff) as u8,
]
}
pub const fn dev_id() -> &'static str {
"0x1866"
}
}
impl BitOr<AuraDev1866> for AuraDev1866 {
type Output = u32;
fn bitor(self, rhs: AuraDev1866) -> Self::Output {
return self as u32 | rhs as u32;
}
}
impl BitAnd<AuraDev1866> for AuraDev1866 {
type Output = u32;
fn bitand(self, rhs: AuraDev1866) -> Self::Output {
return self as u32 & rhs as u32;
}
}
/// # Bits for 0x19b6 keyboard model
///
/// byte 4 in the USB packet is for keyboard + logo power states
/// default is on, `ff`
/// Keyboard and logo use the full range of bits (almost)
///
/// | n1 | n2 | hex | action | bit |
/// |------|------|-----|-----------------------|-------|
/// | 0000 | 0000 | 00 | all off | |
/// | 0000 | 0001 | 01 | logo boot | bit 1 |
/// | 0000 | 0010 | 02 | keyboard boot | bit 2 |
/// | 0000 | 0100 | 04 | logo awake | bit 3 |
/// | 0000 | 1000 | 08 | keyboard awake | bit 4 |
/// | 0001 | 0000 | 10 | logo sleep off | bit 5 |
/// | 0010 | 0000 | 20 | keyboard sleep | bit 6 |
/// | 0100 | 0000 | 40 | logo shutdown off | bit 7 |
/// | 1000 | 0000 | 80 | keyboard shutdown off | bit 8 |
///
/// byte 5 = lightbar
///
/// | 1 | 2 | hex | action | bit |
/// |------|------|-----|----------------------|-------|
/// | 0000 | 0010 | 02 | lightbar off boot | bit 2 |
/// | 0000 | 0100 | 04 | lightbar on | bit 3 |
/// | 0000 | 1000 | 08 | lightbar off sleep | bit 4 |
/// | 0001 | 0000 | 10 | lightbar shtdn off | bit 5 |
///
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[repr(u16)]
pub enum AuraDev19b6 {
BootLogo = 1,
BootKeyb = 1 << 1,
AwakeLogo = 1 << 2,
AwakeKeyb = 1 << 3,
SleepLogo = 1 << 4,
SleepKeyb = 1 << 5,
ShutdownLogo = 1 << 6,
ShutdownKeyb = 1 << 7,
AwakeBar = 1 << 7 + 2,
BootBar = 1 << 7 + 3,
SleepBar = 1 << 7 + 4,
ShutdownBar = 1 << 7 + 5,
}
impl From<AuraDev19b6> for u16 {
fn from(a: AuraDev19b6) -> Self {
a as u16
}
}
impl AuraDev19b6 {
pub fn to_bytes(control: &[Self]) -> [u8; 3] {
let mut a: u16 = 0;
control.iter().for_each(|n| {
a |= *n as u16;
});
[(a & 0xff) as u8, ((a & 0xff00) >> 8) as u8, 0x00]
}
pub const fn dev_id() -> &'static str {
"0x196b"
}
}
impl BitOr<AuraDev19b6> for AuraDev19b6 {
type Output = u16;
fn bitor(self, rhs: AuraDev19b6) -> Self::Output {
return self as u16 | rhs as u16;
}
}
impl BitAnd<AuraDev19b6> for AuraDev19b6 {
type Output = u16;
fn bitand(self, rhs: AuraDev19b6) -> Self::Output {
return self as u16 & rhs as u16;
}
}
#[cfg(test)]
mod tests {
use crate::usb::AuraDev19b6;
use super::AuraDev1866;
#[test]
fn check_0x1866_control_bytes() {
let bytes = [AuraDev1866::Keyboard, AuraDev1866::Awake];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0x08, 0x00, 0x02]);
let bytes = [AuraDev1866::Lightbar, AuraDev1866::Awake];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0x04, 0x05, 0x02]);
let bytes = [AuraDev1866::Sleep];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0x30, 0x08, 0x04]);
let bytes = [AuraDev1866::Boot];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0xc3, 0x12, 0x09]);
let bytes = [
AuraDev1866::Keyboard,
AuraDev1866::Lightbar,
AuraDev1866::Awake,
AuraDev1866::Sleep,
AuraDev1866::Boot,
];
let bytes = AuraDev1866::to_bytes(&bytes);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0xff, 0x1f, 0x000f]);
}
#[test]
fn check_0x19b6_control_bytes() {
// All on
let byte1 = [
AuraDev19b6::BootLogo,
AuraDev19b6::BootKeyb,
AuraDev19b6::SleepLogo,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xff);
//
let byte1 = [
// AuraControl::BootLogo,
AuraDev19b6::BootKeyb,
AuraDev19b6::SleepLogo,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xfe);
let byte1 = [
AuraDev19b6::BootLogo,
// AuraControl::BootKeyb,
AuraDev19b6::SleepLogo,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xfd);
let byte1 = [
AuraDev19b6::BootLogo,
AuraDev19b6::BootKeyb,
// AuraControl::SleepLogo,
AuraDev19b6::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xef);
let byte1 = [
AuraDev19b6::BootLogo,
AuraDev19b6::BootKeyb,
AuraDev19b6::SleepLogo,
// AuraControl::SleepKeyb,
AuraDev19b6::AwakeLogo,
AuraDev19b6::AwakeKeyb,
AuraDev19b6::ShutdownLogo,
AuraDev19b6::ShutdownKeyb,
];
let bytes = AuraDev19b6::to_bytes(&byte1);
println!("{:08b}", bytes[0]);
assert_eq!(bytes[0], 0xdf);
let byte2 = [
AuraDev19b6::AwakeBar,
AuraDev19b6::BootBar,
AuraDev19b6::SleepBar,
AuraDev19b6::ShutdownBar,
];
let bytes = AuraDev19b6::to_bytes(&byte2);
println!("{:08b}", bytes[1]);
assert_eq!(bytes[1], 0x1e);
let byte2 = [
AuraDev19b6::AwakeBar,
AuraDev19b6::BootBar,
// AuraControl::SleepBar,
AuraDev19b6::ShutdownBar,
];
let bytes = AuraDev19b6::to_bytes(&byte2);
println!("{:08b}", bytes[1]);
assert_eq!(bytes[1], 0x16);
}
}

View File

@@ -0,0 +1,45 @@
[package]
name = "rog-control-center"
version = "0.1.4"
authors = ["Luke D. Jones <luke@ljones.dev>"]
edition = "2021"
[features]
mocking = []
[dependencies]
egui = { git = "https://github.com/emilk/egui" }
eframe= { git = "https://github.com/emilk/egui" }
#eframe= { git = "https://github.com/emilk/egui", default-features = false, features = ["dark-light", "default_fonts", "wgpu"] }
rog_dbus = { path = "../rog-dbus" }
rog_aura = { path = "../rog-aura" }
rog_profiles = { path = "../rog-profiles" }
rog_supported = { path = "../rog-supported" }
# supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git" }
smol = "^1.2"
serde = "^1.0"
toml = "^0.5"
serde_json = "^1.0"
serde_derive = "^1.0"
zbus = "^2.3"
nix = "^0.20.0"
tempfile = "3.2.0"
dirs = "3.0.1"
[dependencies.notify-rust]
version = "^4.3"
default-features = false
features = ["z"]
[profile.release]
lto = true
debug = false
opt-level = 3
panic = "abort"
[profile.bench]
debug = false
opt-level = 3

View File

@@ -0,0 +1,22 @@
# App template
This is a trial app cut down to bare essentials to show how to create an app that can run in the background
with some user-config options.
egui based. Keep in mind that this is very much a bit of a mess due to experimenting.
## Running
Use `WINIT_UNIX_BACKEND=x11 rog-control-center`. `WINIT_UNIX_BACKEND` is required due to window decorations not updating and the window not really being set as visible/invisible on wayland.
## Build features
For testing some features that are typically not available on all laptops:
```rust
cargo run --features mocking
```
## TODO
- Add notification watch for certain UI elements to enforce an update (for example when a user changes Aura via a hot key).

View File

@@ -0,0 +1,11 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=ROG Control Center
Comment=Make your ASUS ROG Laptop go Brrrrr!
Categories=Settings
Icon=rog-control-center
Exec=env WINIT_UNIX_BACKEND=x11 rog-control-center
Terminal=false

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

View File

@@ -0,0 +1,127 @@
use std::{
io::Write,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use egui::{Button, RichText};
use rog_dbus::RogDbusClientBlocking;
use rog_supported::SupportedFunctions;
use crate::{config::Config, get_ipc_file, page_states::PageDataStates, Page, SHOWING_GUI};
pub struct RogApp<'a> {
pub page: Page,
pub states: PageDataStates,
pub supported: SupportedFunctions,
/// Should the app begin showing the GUI
pub begin_show_gui: Arc<AtomicBool>,
/// Is the app GUI closed (and running in bg)
pub running_in_bg: bool,
// TODO: can probably just open and read whenever
pub config: Config,
pub asus_dbus: RogDbusClientBlocking<'a>,
}
impl<'a> RogApp<'a> {
/// Called once before the first frame.
pub fn new(
start_closed: bool,
config: Config,
show_gui: Arc<AtomicBool>,
states: PageDataStates,
_cc: &eframe::CreationContext<'_>,
) -> Self {
let (dbus, _) = RogDbusClientBlocking::new().unwrap();
let supported = dbus.proxies().supported().supported_functions().unwrap();
Self {
supported,
states,
page: Page::System,
begin_show_gui: show_gui,
running_in_bg: start_closed,
config,
asus_dbus: dbus,
}
}
}
impl<'a> eframe::App for RogApp<'a> {
fn on_exit_event(&mut self) -> bool {
if self.config.run_in_background {
self.running_in_bg = true;
get_ipc_file().unwrap().write_all(&[0]).unwrap();
return false;
}
true
}
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
let Self {
begin_show_gui: should_show_gui,
supported,
asus_dbus: dbus,
states,
..
} = self;
if states.refresh_if_notfied(supported, dbus) {
ctx.request_repaint();
}
let page = self.page;
if should_show_gui.load(Ordering::SeqCst) {
let mut ipc_file = get_ipc_file().unwrap();
ipc_file.write_all(&[SHOWING_GUI]).unwrap();
should_show_gui.store(false, Ordering::SeqCst);
frame.set_visible(true);
self.running_in_bg = false;
}
if self.running_in_bg {
// Request to draw nothing at all
ctx.request_repaint_after(Duration::from_millis(500));
frame.set_visible(false);
return;
}
// Do all GUI display after this point
self.top_bar(ctx, frame);
self.side_panel(ctx);
if let Some(err) = self.states.error.clone() {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading(RichText::new("Error!").size(28.0));
ui.centered_and_justified(|ui| {
ui.label(RichText::new(format!("The error was: {:?}", err)).size(22.0));
});
});
egui::TopBottomPanel::bottom("error_bar")
.default_height(26.0)
.show(ctx, |ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
if ui
.add(Button::new(RichText::new("Okay").size(20.0)))
.clicked()
{
self.states.error = None;
}
});
});
} else if page == Page::System {
self.system_page(ctx);
} else if page == Page::AuraEffects {
self.aura_page(ctx);
} else if page == Page::AnimeMatrix {
self.anime_page(ctx);
} else if page == Page::FanCurves {
self.fan_curve_page(ctx);
}
}
}

View File

@@ -0,0 +1,92 @@
use std::{
fs::{create_dir, OpenOptions},
io::{Read, Write},
};
use serde_derive::{Deserialize, Serialize};
//use log::{error, info, warn};
use crate::error::Error;
const CFG_DIR: &str = "rog";
const CFG_FILE_NAME: &str = "app-template.cfg";
#[derive(Debug, Deserialize, Serialize)]
#[serde(default)]
pub struct Config {
pub run_in_background: bool,
pub startup_in_background: bool,
pub enable_notifications: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
run_in_background: true,
startup_in_background: false,
enable_notifications: true,
}
}
}
impl Config {
pub fn load() -> Result<Config, Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push(CFG_DIR);
if !path.exists() {
create_dir(path.clone())?;
}
path.push(CFG_FILE_NAME);
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 = Config::default();
let t = toml::to_string_pretty(&default).unwrap();
file.write_all(t.as_bytes())?;
return Ok(default);
} else if let Ok(data) = toml::from_str::<Config>(&buf) {
return Ok(data);
}
}
Err(Error::ConfigLoadFail)
}
pub fn save(&self) -> Result<(), Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push(CFG_DIR);
if !path.exists() {
create_dir(path.clone())?;
}
path.push(CFG_FILE_NAME);
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let t = toml::to_string_pretty(&self).unwrap();
file.write_all(t.as_bytes())?;
Ok(())
}
}

View File

@@ -0,0 +1,43 @@
use std::fmt;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Nix(nix::Error),
ConfigLoadFail,
ConfigLockFail,
XdgVars,
}
impl fmt::Display for Error {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Io(err) => write!(f, "Failed to open: {}", err),
Error::Nix(err) => write!(f, "Error: {}", err),
Error::ConfigLoadFail => write!(f, "Failed to load user config"),
Error::ConfigLockFail => write!(f, "Failed to lock user config"),
Error::XdgVars => write!(f, "XDG environment vars appear unset"),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
}
}
impl From<Error> for zbus::fdo::Error {
fn from(err: Error) -> Self {
zbus::fdo::Error::Failed(format!("Anime zbus error: {}", err))
}
}
impl From<nix::Error> for Error {
fn from(err: nix::Error) -> Self {
Error::Nix(err)
}
}

View File

@@ -0,0 +1,77 @@
pub mod app;
use std::{
fs::{remove_dir_all, File, OpenOptions},
io::{Read, Write},
process::exit,
thread::sleep,
time::Duration,
};
pub use app::RogApp;
pub mod config;
pub mod error;
pub mod notify;
pub mod page_states;
pub mod widgets;
use nix::{sys::stat, unistd};
use tempfile::TempDir;
//use log::{error, info, warn};
pub const SHOWING_GUI: u8 = 1;
pub const SHOW_GUI: u8 = 2;
#[derive(PartialEq, Clone, Copy)]
pub enum Page {
System,
AuraEffects,
AnimeMatrix,
FanCurves,
}
/// Either exit the process, or return with a refreshed tmp-dir
pub fn on_tmp_dir_exists() -> Result<TempDir, std::io::Error> {
let mut buf = [0u8; 4];
let path = std::env::temp_dir().join("rog-gui");
let mut ipc_file = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(path.join("ipc.pipe"))?;
// If the app is running this ends up stacked on top of SHOWING_GUI
ipc_file.write_all(&[SHOW_GUI])?;
// tiny sleep to give the app a chance to respond
sleep(Duration::from_millis(10));
ipc_file.read(&mut buf).ok();
// First entry is the actual state
if buf[0] == SHOWING_GUI {
ipc_file.write_all(&[SHOWING_GUI])?; // Store state again as we drained the fifo
exit(0);
} else if buf[0] == SHOW_GUI {
remove_dir_all(&path)?;
return tempfile::Builder::new()
.prefix("rog-gui")
.rand_bytes(0)
.tempdir();
}
exit(-1);
}
pub fn get_ipc_file() -> Result<File, crate::error::Error> {
let tmp_dir = std::env::temp_dir().join("rog-gui");
let fifo_path = tmp_dir.join("ipc.pipe");
if let Err(e) = unistd::mkfifo(&fifo_path, stat::Mode::S_IRWXU) {
if !matches!(e, nix::Error::Sys(nix::errno::Errno::EEXIST)) {
return Err(e)?;
}
}
Ok(OpenOptions::new()
.read(true)
.write(true)
.truncate(true)
.open(&fifo_path)?)
}

View File

@@ -0,0 +1,97 @@
use rog_control_center::{
config::Config, get_ipc_file, notify::start_notifications, on_tmp_dir_exists,
page_states::PageDataStates, RogApp, SHOW_GUI,
};
use rog_dbus::RogDbusClientBlocking;
use std::{
io::Read,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::spawn,
time::Duration,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Startup
let mut config = Config::load()?;
let start_closed = config.startup_in_background;
if config.startup_in_background {
config.run_in_background = true;
config.save()?;
}
let (dbus, _) = RogDbusClientBlocking::new().unwrap();
let supported = dbus.proxies().supported().supported_functions().unwrap();
// Cheap method to alert to notifications rather than spinning a thread for each
// This is quite different when done in a retained mode app
let charge_notified = Arc::new(AtomicBool::new(false));
let bios_notified = Arc::new(AtomicBool::new(false));
let aura_notified = Arc::new(AtomicBool::new(false));
let anime_notified = Arc::new(AtomicBool::new(false));
let profiles_notified = Arc::new(AtomicBool::new(false));
let fans_notified = Arc::new(AtomicBool::new(false));
let notifs_enabled = Arc::new(AtomicBool::new(config.enable_notifications));
// TODO: change this to an error instead of the nested unwraps, then use to
// display a bare box app with error message.
let states = PageDataStates::new(
notifs_enabled.clone(),
charge_notified.clone(),
bios_notified.clone(),
aura_notified.clone(),
anime_notified.clone(),
profiles_notified.clone(),
fans_notified.clone(),
&supported,
&dbus,
);
if config.enable_notifications {
start_notifications(
charge_notified,
bios_notified,
aura_notified,
anime_notified,
profiles_notified,
fans_notified,
notifs_enabled,
)?;
}
// tmp-dir must live to the end of program life
let _tmp_dir = match tempfile::Builder::new()
.prefix("rog-gui")
.rand_bytes(0)
.tempdir()
{
Ok(tmp) => tmp,
Err(_) => on_tmp_dir_exists().unwrap(),
};
let native_options = eframe::NativeOptions {
decorated: true,
..Default::default()
};
let should_show_gui = Arc::new(AtomicBool::new(!start_closed));
let should = should_show_gui.clone();
spawn(move || {
// Loop is blocked here until a single byte is read
loop {
let mut buf = [0u8; 4];
if get_ipc_file().unwrap().read(&mut buf).is_ok() && buf[0] == SHOW_GUI {
should_show_gui.store(true, Ordering::SeqCst);
// Give the starting app a change to read or we'll race it
std::thread::sleep(Duration::from_millis(10));
}
}
});
eframe::run_native(
"ROG Control Center",
native_options,
Box::new(move |cc| Box::new(RogApp::new(start_closed, config, should, states, cc))),
);
}

View File

@@ -0,0 +1,256 @@
//TODO: a lot of app state refresh depends on this so there needs
// to be an extra AtomicBool for checking if notifications are enabled
use notify_rust::{Hint, Notification, NotificationHandle};
use rog_aura::AuraEffect;
use rog_dbus::{
zbus_anime::AnimeProxy, 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::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
thread::spawn,
};
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>>>;
pub fn start_notifications(
charge_notified: Arc<AtomicBool>,
bios_notified: Arc<AtomicBool>,
aura_notified: Arc<AtomicBool>,
anime_notified: Arc<AtomicBool>,
profiles_notified: Arc<AtomicBool>,
_fans_notified: Arc<AtomicBool>,
notifs_enabled: Arc<AtomicBool>,
) -> Result<(), Box<dyn std::error::Error>> {
let last_notification: SharedHandle = Arc::new(Mutex::new(None));
let executor = Executor::new();
// BIOS notif
let last_notif = last_notification.clone();
let notifs_enabled1 = notifs_enabled.clone();
let bios_notified1 = bios_notified.clone();
// TODO: make a macro or generic function or something...
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 notifs_enabled1.load(Ordering::SeqCst) {
if let Ok(ref mut lock) = last_notif.try_lock() {
notify!(do_post_sound_notif, lock, &out.sound());
}
}
bios_notified1.store(true, Ordering::SeqCst);
}
future::ready(())
})
.await;
};
})
.detach();
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_panel_overdrive().await {
p.for_each(|_| {
bios_notified.store(true, Ordering::SeqCst);
future::ready(())
})
.await;
};
})
.detach();
// Charge notif
let last_notif = last_notification.clone();
let notifs_enabled1 = notifs_enabled.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 notifs_enabled1.load(Ordering::SeqCst) {
if let Ok(ref mut lock) = last_notif.try_lock() {
notify!(do_charge_notif, lock, &out.limit);
}
}
charge_notified.store(true, Ordering::SeqCst);
}
future::ready(())
})
.await;
};
})
.detach();
// Profile notif
let last_notif = last_notification.clone();
let notifs_enabled1 = notifs_enabled.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 notifs_enabled1.load(Ordering::SeqCst) {
if let Ok(ref mut lock) = last_notif.try_lock() {
notify!(do_thermal_notif, lock, &out.profile);
}
}
profiles_notified.store(true, Ordering::SeqCst);
}
future::ready(())
})
.await;
};
})
.detach();
// LED notif
let last_notif = last_notification.clone();
let aura_notif = aura_notified.clone();
let notifs_enabled1 = notifs_enabled.clone();
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 notifs_enabled1.load(Ordering::SeqCst) {
if let Ok(ref mut lock) = last_notif.try_lock() {
notify!(do_led_notif, lock, &out.data);
}
}
aura_notif.store(true, Ordering::SeqCst);
}
future::ready(())
})
.await;
};
})
.detach();
let aura_notif = aura_notified.clone();
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(|_| {
aura_notif.store(true, Ordering::SeqCst);
future::ready(())
})
.await;
};
})
.detach();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = LedProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_all_signals().await {
p.for_each(|_| {
aura_notified.store(true, Ordering::SeqCst);
future::ready(())
})
.await;
};
})
.detach();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = AnimeProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_power_states().await {
p.for_each(|_| {
anime_notified.store(true, Ordering::SeqCst);
future::ready(())
})
.await;
};
})
.detach();
spawn(move || loop {
smol::block_on(executor.tick());
});
Ok(())
}
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 {}",
profile.to_uppercase(),
))
.hint(Hint::Resident(true))
.timeout(2000)
.hint(Hint::Category("device".into()))
//.hint(Hint::Transient(true))
.icon(icon)
.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))
}

View File

@@ -0,0 +1,304 @@
use std::{
collections::{BTreeMap, HashMap, HashSet},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use egui::Vec2;
use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum};
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::{fan_curve_set::FanCurveSet, FanCurvePU, Profile};
use rog_supported::SupportedFunctions;
#[derive(Clone, Debug)]
pub struct BiosState {
/// To be shared to a thread that checks notifications.
/// It's a bit general in that it won't provide *what* was
/// updated, so the full state needs refresh
pub was_notified: Arc<AtomicBool>,
pub post_sound: bool,
pub dedicated_gfx: bool,
pub panel_overdrive: bool,
pub dgpu_disable: bool,
pub egpu_enable: bool,
}
impl BiosState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
was_notified,
post_sound: if supported.rog_bios_ctrl.post_sound {
dbus.proxies().rog_bios().post_boot_sound().unwrap() != 0
} else {
false
},
dedicated_gfx: if supported.rog_bios_ctrl.dedicated_gfx {
dbus.proxies().rog_bios().dedicated_graphic_mode().unwrap() != 0
} else {
false
},
panel_overdrive: if supported.rog_bios_ctrl.panel_overdrive {
dbus.proxies().rog_bios().panel_overdrive().unwrap() != 0
} else {
false
},
// TODO: needs supergfx
dgpu_disable: supported.rog_bios_ctrl.dgpu_disable,
egpu_enable: supported.rog_bios_ctrl.egpu_enable,
}
}
}
#[derive(Clone, Debug)]
pub struct ProfilesState {
pub was_notified: Arc<AtomicBool>,
pub list: Vec<Profile>,
pub current: Profile,
}
impl ProfilesState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
was_notified,
list: if supported.platform_profile.platform_profile {
dbus.proxies().profile().profiles().unwrap()
} else {
vec![]
},
current: if supported.platform_profile.platform_profile {
dbus.proxies().profile().active_profile().unwrap()
} else {
Profile::Balanced
},
}
}
}
#[derive(Clone, Debug)]
pub struct FanCurvesState {
pub was_notified: Arc<AtomicBool>,
pub show_curve: Profile,
pub show_graph: FanCurvePU,
pub enabled: HashSet<Profile>,
pub curves: HashMap<Profile, FanCurveSet>,
pub drag_delta: Vec2,
}
impl FanCurvesState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
let profiles = if supported.platform_profile.platform_profile {
dbus.proxies().profile().profiles().unwrap()
} else {
vec![Profile::Balanced, Profile::Quiet, Profile::Performance]
};
let enabled = if supported.platform_profile.fan_curves {
HashSet::from_iter(
dbus.proxies()
.profile()
.enabled_fan_profiles()
.unwrap()
.iter()
.cloned(),
)
} else {
HashSet::from([Profile::Balanced, Profile::Quiet, Profile::Performance])
};
let mut curves: HashMap<Profile, FanCurveSet> = HashMap::new();
profiles.iter().for_each(|p| {
if supported.platform_profile.fan_curves {
let curve = dbus.proxies().profile().fan_curve_data(*p).unwrap();
curves.insert(*p, curve);
} else {
let mut curve = FanCurveSet::default();
curve.cpu.pwm = [30, 40, 60, 100, 140, 180, 200, 250];
curve.cpu.temp = [20, 30, 40, 50, 70, 80, 90, 100];
curve.gpu.pwm = [40, 80, 100, 140, 170, 200, 230, 250];
curve.gpu.temp = [20, 30, 40, 50, 70, 80, 90, 100];
curves.insert(*p, curve);
}
});
let show_curve = if supported.platform_profile.fan_curves {
dbus.proxies().profile().active_profile().unwrap()
} else {
Profile::Balanced
};
Self {
was_notified,
show_curve,
show_graph: FanCurvePU::CPU,
enabled,
curves,
drag_delta: Vec2::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct AuraState {
pub was_notified: Arc<AtomicBool>,
pub current_mode: AuraModeNum,
pub modes: BTreeMap<AuraModeNum, AuraEffect>,
pub enabled: AuraPowerDev,
/// Brightness from 0-3
pub bright: i16,
}
impl AuraState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
was_notified,
current_mode: if !supported.keyboard_led.stock_led_modes.is_empty() {
dbus.proxies().led().led_mode().unwrap()
} else {
AuraModeNum::Static
},
modes: if !supported.keyboard_led.stock_led_modes.is_empty() {
dbus.proxies().led().led_modes().unwrap()
} else {
BTreeMap::new()
},
enabled: dbus.proxies().led().leds_enabled().unwrap(),
bright: if !supported.keyboard_led.brightness_set {
dbus.proxies().led().led_brightness().unwrap()
} else {
2
},
}
}
}
#[derive(Clone, Debug)]
pub struct AnimeState {
pub was_notified: Arc<AtomicBool>,
pub bright: u8,
pub boot: bool,
pub awake: bool,
pub sleep: bool,
}
impl AnimeState {
pub fn new(
was_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
was_notified,
boot: if supported.anime_ctrl.0 {
dbus.proxies().anime().boot_enabled().unwrap()
} else {
false
},
awake: if supported.anime_ctrl.0 {
dbus.proxies().anime().awake_enabled().unwrap()
} else {
false
},
// TODO:
sleep: false,
bright: 200,
}
}
}
#[derive(Debug)]
pub struct PageDataStates {
pub notifs_enabled: Arc<AtomicBool>,
pub was_notified: Arc<AtomicBool>,
/// Because much of the app state here is the same as `RogBiosSupportedFunctions`
/// we can re-use that structure.
pub bios: BiosState,
pub aura: AuraState,
pub anime: AnimeState,
pub profiles: ProfilesState,
pub fan_curves: FanCurvesState,
pub charge_limit: i16,
pub error: Option<String>,
}
impl PageDataStates {
pub fn new(
notifs_enabled: Arc<AtomicBool>,
charge_notified: Arc<AtomicBool>,
bios_notified: Arc<AtomicBool>,
aura_notified: Arc<AtomicBool>,
anime_notified: Arc<AtomicBool>,
profiles_notified: Arc<AtomicBool>,
fans_notified: Arc<AtomicBool>,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> Self {
Self {
notifs_enabled,
was_notified: charge_notified,
charge_limit: dbus.proxies().charge().limit().unwrap(),
bios: BiosState::new(bios_notified, supported, dbus),
aura: AuraState::new(aura_notified, supported, dbus),
anime: AnimeState::new(anime_notified, supported, dbus),
profiles: ProfilesState::new(profiles_notified, supported, dbus),
fan_curves: FanCurvesState::new(fans_notified, supported, dbus),
error: None,
}
}
pub fn refresh_if_notfied(
&mut self,
supported: &SupportedFunctions,
dbus: &RogDbusClientBlocking,
) -> bool {
let mut notified = false;
if self.was_notified.load(Ordering::SeqCst) {
self.charge_limit = dbus.proxies().charge().limit().unwrap();
self.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
if self.aura.was_notified.load(Ordering::SeqCst) {
self.aura = AuraState::new(self.aura.was_notified.clone(), supported, dbus);
self.aura.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
if self.bios.was_notified.load(Ordering::SeqCst) {
self.bios = BiosState::new(self.bios.was_notified.clone(), supported, dbus);
self.bios.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
if self.profiles.was_notified.load(Ordering::SeqCst) {
self.profiles = ProfilesState::new(self.profiles.was_notified.clone(), supported, dbus);
self.profiles.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
if self.fan_curves.was_notified.load(Ordering::SeqCst) {
self.fan_curves =
FanCurvesState::new(self.fan_curves.was_notified.clone(), supported, dbus);
self.fan_curves.was_notified.store(false, Ordering::SeqCst);
notified = true;
}
notified
}
}

View File

@@ -0,0 +1,77 @@
use egui::RichText;
use crate::RogApp;
impl<'a> RogApp<'a> {
pub fn anime_page(&mut self, ctx: &egui::Context) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("AniMe Matrix Settings");
ui.label("Options are incomplete. Awake + Boot should work");
let Self {
states,
asus_dbus: dbus,
..
} = self;
let mut changed = false;
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
let h = 16.0;
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Brightness").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Boot").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Awake").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Sleep").size(h));
});
});
ui.vertical(|ui| {
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
if ui
.add(egui::Slider::new(&mut states.anime.bright, 0..=254))
.changed()
{
changed = true;
}
});
ui.horizontal_wrapped(|ui| {
if ui.checkbox(&mut states.anime.boot, "Enable").changed() {
dbus.proxies()
.anime()
.set_boot_on_off(states.anime.boot)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
});
ui.horizontal_wrapped(|ui| {
if ui.checkbox(&mut states.anime.awake, "Enable").changed() {
dbus.proxies()
.anime()
.set_on_off(states.anime.awake)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
});
ui.horizontal_wrapped(|ui| {
if ui.checkbox(&mut states.anime.sleep, "Enable").changed() {
changed = true;
}
});
});
});
});
}
}

View File

@@ -0,0 +1,480 @@
use egui::{RichText, Ui};
use rog_aura::{
usb::{AuraDev1866, AuraDev19b6, AuraDevice, AuraPowerDev},
AuraModeNum, AuraZone, Colour, Speed,
};
use rog_dbus::RogDbusClientBlocking;
use rog_supported::SupportedFunctions;
use crate::{
page_states::{AuraState, PageDataStates},
RogApp,
};
impl<'a> RogApp<'a> {
pub fn aura_page(&mut self, ctx: &egui::Context) {
let Self {
supported,
states,
asus_dbus: dbus,
..
} = self;
egui::CentralPanel::default().show(ctx, |ui| {
Self::aura_power(supported, states, dbus, ui);
ui.separator();
Self::aura_modes(supported, states, dbus, ui);
});
}
fn aura_power(
supported: &SupportedFunctions,
states: &mut PageDataStates,
dbus: &mut RogDbusClientBlocking,
ui: &mut Ui,
) {
match supported.keyboard_led.prod_id {
AuraDevice::X1854 | AuraDevice::X1869 | AuraDevice::X1866 => {
Self::aura_power1(supported, states, dbus, ui)
}
AuraDevice::X19B6 => Self::aura_power2(supported, states, dbus, ui),
AuraDevice::Unknown => {}
}
}
fn aura_power1(
supported: &SupportedFunctions,
states: &mut PageDataStates,
dbus: &mut RogDbusClientBlocking,
ui: &mut Ui,
) {
let enabled_states = &mut states.aura.enabled;
ui.heading("Aura go brrrrr! (incomplete)");
ui.separator();
let boot = &mut enabled_states.x1866.contains(&AuraDev1866::Boot);
let sleep = &mut enabled_states.x1866.contains(&AuraDev1866::Sleep);
let keyboard = &mut enabled_states.x1866.contains(&AuraDev1866::Keyboard);
let lightbar = &mut enabled_states.x1866.contains(&AuraDev1866::Lightbar);
let mut changed = false;
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
let h = 16.0;
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Boot").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Awake").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Sleep").size(h));
});
// if supported.keyboard_led.brightness_set {
// ui.horizontal_wrapped(|ui| {
// ui.label(RichText::new("Brightness").size(h));
// });
// }
});
ui.vertical(|ui| {
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
if ui.checkbox(boot, "Enable").changed() {
changed = true;
}
});
ui.horizontal_wrapped(|ui| {
if ui.toggle_value(keyboard, "Keyboard").changed() {
changed = true;
}
if !supported.keyboard_led.multizone_led_mode.is_empty() {
if ui.toggle_value(lightbar, "Lightbar").changed() {
changed = true;
}
}
});
ui.horizontal_wrapped(|ui| {
if ui.checkbox(sleep, "Enable").changed() {
changed = true;
}
});
// We currently don't have a watch for system changes here
// if supported.keyboard_led.brightness_set {
// if ui
// .add(egui::Slider::new(
// &mut states.aura.bright,
// 0..=3,
// ))
// .changed()
// {
// let bright = LedBrightness::from(states.aura.bright as u32);
// dbus.proxies()
// .led()
// .set_brightness(bright)
// .map_err(|err| {
// states.error = Some(err.to_string());
// })
// .ok();
// }
// }
});
});
if changed {
let mut enabled = Vec::new();
let mut disabled = Vec::new();
let mut modify = |b: bool, a: AuraDev1866| {
if b {
enabled.push(a);
if !enabled_states.x1866.contains(&a) {
enabled_states.x1866.push(a);
}
} else {
disabled.push(a);
// This would be so much better as a hashset
if enabled_states.x1866.contains(&a) {
let mut idx = 0;
for (i, n) in enabled_states.x1866.iter().enumerate() {
if *n == a {
idx = i;
break;
}
}
enabled_states.x1866.remove(idx);
}
}
};
modify(*boot, AuraDev1866::Boot);
modify(*sleep, AuraDev1866::Sleep);
modify(*keyboard, AuraDev1866::Keyboard);
if !supported.keyboard_led.multizone_led_mode.is_empty() {
modify(*lightbar, AuraDev1866::Lightbar);
}
let mut send = |enable: bool, data: Vec<AuraDev1866>| {
let options = AuraPowerDev {
x1866: data,
x19b6: vec![],
};
// build data to send
dbus.proxies()
.led()
.set_leds_power(options, enable)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
};
send(true, enabled);
send(false, disabled);
}
}
fn aura_power2(
supported: &SupportedFunctions,
states: &mut PageDataStates,
dbus: &mut RogDbusClientBlocking,
ui: &mut Ui,
) {
let enabled_states = &mut states.aura.enabled;
ui.heading("Lights go brrrrr! (incomplete)");
ui.separator();
let has_logo = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::Logo);
let has_lightbar = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarLeft)
|| supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarRight);
let boot_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootBar);
let boot_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootLogo);
let boot_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootKeyb);
let awake_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeBar);
let awake_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeLogo);
let awake_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeKeyb);
let sleep_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepBar);
let sleep_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepLogo);
let sleep_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepKeyb);
let mut changed = false;
let mut item = |keyboard: &mut bool, logo: &mut bool, lightbar: &mut bool, ui: &mut Ui| {
ui.horizontal_wrapped(|ui| {
if ui.checkbox(keyboard, "Keyboard").changed() {
changed = true;
}
if has_logo && ui.checkbox(logo, "Logo").changed() {
changed = true;
}
if has_lightbar && ui.checkbox(lightbar, "Lightbar").changed() {
changed = true;
}
});
};
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
let h = 16.0;
ui.set_row_height(22.0);
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Boot").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Awake").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Sleep").size(h));
});
});
ui.vertical(|ui| {
ui.set_row_height(22.0);
item(boot_keyb, boot_logo, boot_bar, ui);
item(awake_keyb, awake_logo, awake_bar, ui);
item(sleep_keyb, sleep_logo, sleep_bar, ui);
});
});
if changed {
let mut enabled = Vec::new();
let mut disabled = Vec::new();
let mut modify = |b: bool, a: AuraDev19b6| {
if b {
enabled.push(a);
if !enabled_states.x19b6.contains(&a) {
enabled_states.x19b6.push(a);
}
} else {
disabled.push(a);
// This would be so much better as a hashset
if enabled_states.x19b6.contains(&a) {
let mut idx = 0;
for (i, n) in enabled_states.x19b6.iter().enumerate() {
if *n == a {
idx = i;
break;
}
}
enabled_states.x1866.remove(idx);
}
}
};
modify(*boot_keyb, AuraDev19b6::BootKeyb);
modify(*sleep_keyb, AuraDev19b6::SleepKeyb);
modify(*awake_keyb, AuraDev19b6::AwakeKeyb);
if supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::Logo)
{
modify(*boot_logo, AuraDev19b6::BootLogo);
modify(*sleep_logo, AuraDev19b6::SleepLogo);
modify(*awake_logo, AuraDev19b6::AwakeLogo);
}
if supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarLeft)
{
modify(*boot_bar, AuraDev19b6::BootBar);
modify(*sleep_bar, AuraDev19b6::SleepBar);
modify(*awake_bar, AuraDev19b6::AwakeBar);
}
let mut send = |enable: bool, data: Vec<AuraDev19b6>| {
let options = AuraPowerDev {
x1866: vec![],
x19b6: data,
};
// build data to send
dbus.proxies()
.led()
.set_leds_power(options, enable)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
};
send(true, enabled);
send(false, disabled);
}
}
fn aura_modes(
supported: &SupportedFunctions,
states: &mut PageDataStates,
dbus: &mut RogDbusClientBlocking,
ui: &mut Ui,
) {
let mut changed = false;
let mut selected = states.aura.current_mode;
let has_keyzones = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::Key2);
let has_logo = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::Logo);
let has_lightbar = supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarLeft)
|| supported
.keyboard_led
.multizone_led_mode
.contains(&AuraZone::BarRight);
ui.heading("Aura modes");
let mut item = |a: AuraModeNum, ui: &mut Ui| {
if ui
.selectable_value(&mut selected, a, format!("{:?}", a))
.clicked()
{
changed = true;
}
};
ui.horizontal_wrapped(|ui| {
for a in states.aura.modes.keys() {
item(*a, ui);
}
});
// TODO: Need some sort of mapping to enable options only if
// they actually work.
if let Some(effect) = states.aura.modes.get_mut(&selected) {
let mut zone_button = |a: AuraZone, ui: &mut Ui| {
ui.selectable_value(&mut effect.zone, a, format!("{:?}", a));
};
let mut speed_button = |a: Speed, ui: &mut Ui| {
ui.selectable_value(&mut effect.speed, a, format!("{:?}", a));
};
let mut dir_button = |a: rog_aura::Direction, ui: &mut Ui| {
ui.selectable_value(&mut effect.direction, a, format!("{:?}", a));
};
let mut c1: [f32; 3] = effect.colour1.into();
let mut c2: [f32; 3] = effect.colour2.into();
ui.separator();
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
let h = 16.0;
ui.set_row_height(22.0);
if has_keyzones || has_lightbar || has_logo {
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Zone").size(h));
});
}
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Colour 1").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Colour 2").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Speed").size(h));
});
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new("Direction").size(h));
});
});
ui.vertical(|ui| {
ui.set_row_height(22.0);
if has_keyzones || has_lightbar || has_logo {
ui.horizontal_wrapped(|ui| {
zone_button(AuraZone::None, ui);
if has_keyzones {
zone_button(AuraZone::Key1, ui);
zone_button(AuraZone::Key2, ui);
zone_button(AuraZone::Key3, ui);
zone_button(AuraZone::Key4, ui);
}
if has_logo {
zone_button(AuraZone::Logo, ui);
}
if has_lightbar {
zone_button(AuraZone::BarLeft, ui);
zone_button(AuraZone::BarRight, ui);
}
});
}
egui::color_picker::color_edit_button_rgb(ui, &mut c1);
egui::color_picker::color_edit_button_rgb(ui, &mut c2);
ui.horizontal_wrapped(|ui| {
speed_button(Speed::Low, ui);
speed_button(Speed::Med, ui);
speed_button(Speed::High, ui);
});
ui.horizontal_wrapped(|ui| {
dir_button(rog_aura::Direction::Left, ui);
dir_button(rog_aura::Direction::Down, ui);
dir_button(rog_aura::Direction::Right, ui);
dir_button(rog_aura::Direction::Up, ui);
});
});
});
effect.colour1 = Colour::from(&c1);
effect.colour2 = Colour::from(&c2);
}
ui.separator();
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
if ui.add(egui::Button::new("Cancel")).clicked() {
let notif = states.aura.was_notified.clone();
states.aura.modes = AuraState::new(notif, supported, dbus).modes;
}
if ui.add(egui::Button::new("Apply")).clicked() {
changed = true;
}
});
// egui::TopBottomPanel::bottom("error_bar")
// .default_height(26.0)
// .show(ctx, |ui| {
// ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
// if ui.add(egui::Button::new("Cancel")).clicked() {
// let notif = states.aura.was_notified.clone();
// states.aura.modes = AuraState::new(notif, supported, dbus).modes;
// }
// if ui.add(egui::Button::new("Apply")).clicked() {
// changed = true;
// }
// });
// });
if changed {
states.aura.current_mode = selected;
dbus.proxies()
.led()
.set_led_mode(states.aura.modes.get(&selected).unwrap())
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
}
}

View File

@@ -0,0 +1,199 @@
use crate::{
page_states::{FanCurvesState, ProfilesState},
RogApp,
};
use egui::{plot::Points, Ui};
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::{FanCurvePU, Profile};
use rog_supported::SupportedFunctions;
impl<'a> RogApp<'a> {
pub fn fan_curve_page(&mut self, ctx: &egui::Context) {
let Self {
supported,
states,
asus_dbus: dbus,
..
} = self;
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Custom fan curves");
ui.label("A fan curve is only active when the related profile is active and the curve is enabled");
Self::fan_curve(
supported,
&mut states.profiles,
&mut states.fan_curves,
dbus, &mut states.error,
ui,
);
Self::fan_graphs(&mut states.profiles, &mut states.fan_curves, dbus, &mut states.error, ui);
});
}
fn fan_curve(
supported: &SupportedFunctions,
profiles: &mut ProfilesState,
curves: &mut FanCurvesState,
dbus: &RogDbusClientBlocking,
do_error: &mut Option<String>,
ui: &mut Ui,
) {
ui.separator();
ui.label("Enabled fan-curves");
let mut changed = false;
ui.horizontal(|ui| {
let mut item = |p: Profile, _curves: &mut FanCurvesState, mut checked: bool| {
if ui
.add(egui::Checkbox::new(&mut checked, format!("{:?}", p)))
.changed()
{
dbus.proxies()
.profile()
.set_fan_curve_enabled(p, checked)
.map_err(|err| {
*do_error = Some(err.to_string());
})
.ok();
#[cfg(feature = "mocking")]
if !checked {
_curves.enabled.remove(&p);
} else {
_curves.enabled.insert(p);
}
changed = true;
}
};
for f in profiles.list.iter() {
item(*f, curves, curves.enabled.contains(f));
}
});
if changed {
// Need to update app data if change made
#[cfg(not(feature = "mocking"))]
{
let notif = curves.was_notified.clone();
*curves = FanCurvesState::new(notif, supported, dbus);
}
}
}
fn fan_graphs(
profiles: &mut ProfilesState,
curves: &mut FanCurvesState,
dbus: &RogDbusClientBlocking,
do_error: &mut Option<String>,
ui: &mut Ui,
) {
ui.separator();
let mut item = |p: Profile, ui: &mut Ui| {
ui.selectable_value(&mut curves.show_curve, p, format!("{p:?}"));
};
ui.horizontal_wrapped(|ui| {
for a in curves.curves.iter() {
item(*a.0, ui);
}
ui.selectable_value(
&mut curves.show_graph,
FanCurvePU::CPU,
format!("{:?}", FanCurvePU::CPU),
);
ui.selectable_value(
&mut curves.show_graph,
FanCurvePU::GPU,
format!("{:?}", FanCurvePU::GPU),
);
});
let curve = curves.curves.get_mut(&curves.show_curve).unwrap();
use egui::plot::{Line, Plot, PlotPoints};
let data = if curves.show_graph == FanCurvePU::CPU {
&mut curve.cpu
} else {
&mut curve.gpu
};
let points = data.temp.iter().enumerate().map(|(idx, x)| {
let x = *x as f64;
let y = ((data.pwm[idx] as u32) * 100 / 255) as f64;
[x, y]
});
let line = Line::new(PlotPoints::from_iter(points.clone())).width(2.0);
let points = Points::new(PlotPoints::from_iter(points)).radius(3.0);
Plot::new("my_plot")
.view_aspect(2.0)
// .center_x_axis(true)
// .center_y_axis(true)
.include_x(0.0)
.include_x(110.0)
.include_y(0.0)
.include_y(110.0)
.allow_scroll(false)
.allow_drag(false)
.allow_boxed_zoom(false)
.x_axis_formatter(|d, _r| format!("{}", d))
.y_axis_formatter(|d, _r| format!("{:.*}%", 1, d))
.label_formatter(|name, value| {
if !name.is_empty() {
format!("{}: {:.*}%", name, 1, value.y)
} else {
format!("Temp {}c\nFan {:.*}%", value.x as u8, 1, value.y)
}
})
.show(ui, |plot_ui| {
if plot_ui.plot_hovered() {
let mut idx = 0;
if let Some(point) = plot_ui.pointer_coordinate() {
let mut x: i32 = 255;
for (i, n) in data.temp.iter().enumerate() {
let tmp = x.min((point.x as i32 - *n as i32).abs());
if tmp < x {
x = tmp;
idx = i;
}
}
if plot_ui.plot_clicked() {
data.temp[idx] = point.x as u8;
data.pwm[idx] = (point.y * 255.0 / 100.0) as u8;
} else {
let drag = plot_ui.pointer_coordinate_drag_delta();
if drag.length_sq() != 0.0 {
data.temp[idx] = (point.x as f32 + drag.x) as u8;
data.pwm[idx] = ((point.y as f32 + drag.y) * 255.0 / 100.0) as u8;
}
}
}
}
plot_ui.line(line);
plot_ui.points(points)
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
if ui.add(egui::Button::new("Apply Fan-curve")).clicked() {
#[cfg(not(feature = "mocking"))]
dbus.proxies()
.profile()
.set_fan_curve(profiles.current, data.clone())
.map_err(|err| {
*do_error = Some(err.to_string());
})
.ok();
#[cfg(feature = "mocking")]
dbg!("Applied");
}
});
}
}

View File

@@ -0,0 +1,13 @@
mod anime_page;
mod aura_page;
mod fan_curve_page;
mod side_panel;
mod system_page;
mod top_bar;
pub use anime_page::*;
pub use aura_page::*;
pub use fan_curve_page::*;
pub use side_panel::*;
pub use system_page::*;
pub use top_bar::*;

View File

@@ -0,0 +1,62 @@
use crate::{Page, RogApp};
impl<'a> RogApp<'a> {
pub fn side_panel(&mut self, ctx: &egui::Context) {
egui::SidePanel::left("side_panel")
.resizable(false)
.default_width(60.0) // TODO: set size to match icon buttons when done
.show(ctx, |ui| {
let Self { page, .. } = self;
ui.heading("Functions");
ui.separator();
if ui
.selectable_value(page, Page::System, "System Settings")
.clicked()
{
*page = Page::System;
}
if self.supported.platform_profile.fan_curves || cfg!(feature = "mocking") {
ui.separator();
if ui
.selectable_value(page, Page::FanCurves, "Fan Curves")
.clicked()
{
*page = Page::FanCurves;
}
}
if !self.supported.keyboard_led.stock_led_modes.is_empty()
|| cfg!(feature = "mocking")
{
ui.separator();
if ui
.selectable_value(page, Page::AuraEffects, "Keyboard Aura")
.clicked()
{
*page = Page::AuraEffects;
}
}
if self.supported.anime_ctrl.0 || cfg!(feature = "mocking") {
ui.separator();
if ui
.selectable_value(page, Page::AnimeMatrix, "AniMe Matrix")
.clicked()
{
*page = Page::AnimeMatrix;
}
}
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("Source code ");
ui.hyperlink_to("rog-gui.", "https://gitlab.com/asus-linux/rog-gui");
});
});
});
}
}

View File

@@ -0,0 +1,135 @@
use crate::{page_states::PageDataStates, RogApp};
use egui::Ui;
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::Profile;
impl<'a> RogApp<'a> {
pub fn system_page(&mut self, ctx: &egui::Context) {
let Self {
supported,
states,
asus_dbus: dbus,
..
} = self;
egui::CentralPanel::default().show(ctx, |ui| {
// The central panel the region left after adding TopPanel's and SidePanel's
ui.heading("Experimental application for asusd");
ui.horizontal(|ui| {
egui::global_dark_light_mode_buttons(ui);
egui::warn_if_debug_build(ui);
});
ui.separator();
egui::ScrollArea::vertical().show(ui, |ui| {
ui.heading("Charge control");
let slider = egui::Slider::new(&mut states.charge_limit, 20..=100)
.text("Limit")
.step_by(1.0);
if ui.add(slider).drag_released() {
dbus.proxies()
.charge()
.set_limit(states.charge_limit as u8)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
ui.separator();
ui.heading("Bios options");
if supported.rog_bios_ctrl.post_sound {
if ui
.add(egui::Checkbox::new(
&mut states.bios.post_sound,
"POST sound",
))
.changed()
{
dbus.proxies()
.rog_bios()
.set_post_boot_sound(states.bios.post_sound)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
}
if supported.rog_bios_ctrl.post_sound {
if ui
.add(egui::Checkbox::new(
&mut states.bios.panel_overdrive,
"Panel overdrive",
))
.changed()
{
dbus.proxies()
.rog_bios()
.set_panel_overdrive(states.bios.panel_overdrive)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
}
if supported.rog_bios_ctrl.dedicated_gfx {
if ui
.add(egui::Checkbox::new(
&mut states.bios.dedicated_gfx,
"G-Sync Dedicated GPU mode",
))
.changed()
{
dbus.proxies()
.rog_bios()
.set_dedicated_graphic_mode(states.bios.dedicated_gfx)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
}
if supported.platform_profile.platform_profile {
Self::platform_profile(states, dbus, ui);
}
});
});
}
fn platform_profile(states: &mut PageDataStates, dbus: &RogDbusClientBlocking, ui: &mut Ui) {
ui.separator();
ui.heading("Platform profile");
let mut changed = false;
let mut item = |p: Profile, ui: &mut Ui| {
if ui
.selectable_value(&mut states.profiles.current, p, format!("{p:?}"))
.clicked()
{
changed = true;
}
};
ui.horizontal_wrapped(|ui| {
for a in states.profiles.list.iter() {
item(*a, ui);
}
});
if changed {
dbus.proxies()
.profile()
.set_active_profile(states.profiles.current)
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
};
}
}

View File

@@ -0,0 +1,42 @@
use std::sync::atomic::Ordering;
use crate::RogApp;
impl<'a> RogApp<'a> {
pub fn top_bar(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
let Self { states, config, .. } = self;
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Quit").clicked() {
frame.quit();
}
});
ui.menu_button("Settings", |ui| {
if ui
.checkbox(&mut config.run_in_background, "Run in Background")
.clicked()
|| ui
.checkbox(&mut config.startup_in_background, "Startup Hidden")
.clicked()
|| ui
.checkbox(&mut config.enable_notifications, "Enable Notifications")
.clicked()
{
states
.notifs_enabled
.store(config.enable_notifications, Ordering::SeqCst);
config
.save()
.map_err(|err| {
states.error = Some(err.to_string());
})
.ok();
}
});
});
});
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "rog_dbus"
version = "4.0.2"
version = "4.2.1"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -14,7 +14,6 @@ rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_profiles = { path = "../rog-profiles" }
rog_supported = { path = "../rog-supported" }
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", tag = "2.0.0" }
zbus = "^1.9"
zbus_macros = "^1.9"
zvariant = "^2.8"
zbus = "^2.2"
zbus_macros = "^2.0"
zvariant = "^3.0"

View File

@@ -1,5 +1,4 @@
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
pub static DBUS_NAME_GFX: &str = "org.supergfxctl.Daemon";
pub static DBUS_PATH: &str = "/org/asuslinux/Daemon";
pub static DBUS_IFACE: &str = "org.asuslinux.Daemon";
@@ -10,51 +9,109 @@ pub mod zbus_profile;
pub mod zbus_rogbios;
pub mod zbus_supported;
use rog_anime::AnimePowerStates;
use rog_aura::{AuraEffect, LedPowerStates};
use rog_profiles::Profile;
use std::sync::mpsc::{channel, Receiver};
use zbus::{Connection, Result, SignalReceiver};
// use rog_anime::AnimePowerStates;
// use rog_aura::{AuraEffect, LedPowerStates};
// use rog_profiles::Profile;
// use std::sync::mpsc::{channel, Receiver};
use zbus::{blocking, Connection, Result};
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
pub struct DbusProxiesBlocking<'a> {
anime: zbus_anime::AnimeProxyBlocking<'a>,
charge: zbus_charge::ChargeProxyBlocking<'a>,
led: zbus_led::LedProxyBlocking<'a>,
profile: zbus_profile::ProfileProxyBlocking<'a>,
rog_bios: zbus_rogbios::RogBiosProxyBlocking<'a>,
supported: zbus_supported::SupportedProxyBlocking<'a>,
}
impl<'a> DbusProxiesBlocking<'a> {
#[inline]
pub fn new() -> Result<(Self, blocking::Connection)> {
let conn = blocking::Connection::system()?;
Ok((
DbusProxiesBlocking {
anime: zbus_anime::AnimeProxyBlocking::new(&conn)?,
led: zbus_led::LedProxyBlocking::new(&conn)?,
charge: zbus_charge::ChargeProxyBlocking::new(&conn)?,
profile: zbus_profile::ProfileProxyBlocking::new(&conn)?,
rog_bios: zbus_rogbios::RogBiosProxyBlocking::new(&conn)?,
supported: zbus_supported::SupportedProxyBlocking::new(&conn)?,
},
conn,
))
}
pub fn anime(&self) -> &zbus_anime::AnimeProxyBlocking<'a> {
&self.anime
}
pub fn charge(&self) -> &zbus_charge::ChargeProxyBlocking<'a> {
&self.charge
}
pub fn led(&self) -> &zbus_led::LedProxyBlocking<'a> {
&self.led
}
pub fn profile(&self) -> &zbus_profile::ProfileProxyBlocking<'a> {
&self.profile
}
pub fn rog_bios(&self) -> &zbus_rogbios::RogBiosProxyBlocking<'a> {
&self.rog_bios
}
pub fn supported(&self) -> &zbus_supported::SupportedProxyBlocking<'a> {
&self.supported
}
}
/// This is the main way to communicate with the DBUS interface
pub struct RogDbusClientBlocking<'a> {
proxies: DbusProxiesBlocking<'a>,
}
impl<'a> RogDbusClientBlocking<'a> {
#[inline]
pub fn new() -> Result<(Self, blocking::Connection)> {
let (proxies, conn) = DbusProxiesBlocking::new()?;
Ok((RogDbusClientBlocking { proxies }, conn))
}
pub fn proxies(&self) -> &DbusProxiesBlocking {
&self.proxies
}
}
pub struct DbusProxies<'a> {
anime: zbus_anime::AnimeProxy<'a>,
charge: zbus_charge::ChargeProxy<'a>,
led: zbus_led::LedProxy<'a>,
profile: zbus_profile::ProfileProxy<'a>,
rog_bios: zbus_rogbios::RogBiosProxy<'a>,
supported: zbus_supported::SupportProxy<'a>,
supported: zbus_supported::SupportedProxy<'a>,
}
impl<'a> DbusProxies<'a> {
#[inline]
pub fn new() -> Result<(Self, Connection)> {
let conn = Connection::new_system()?;
pub async fn new() -> Result<(DbusProxies<'a>, Connection)> {
let conn = Connection::system().await?;
Ok((
DbusProxies {
anime: zbus_anime::AnimeProxy::new(&conn)?,
led: zbus_led::LedProxy::new(&conn)?,
charge: zbus_charge::ChargeProxy::new(&conn)?,
profile: zbus_profile::ProfileProxy::new(&conn)?,
rog_bios: zbus_rogbios::RogBiosProxy::new(&conn)?,
supported: zbus_supported::SupportProxy::new(&conn)?,
anime: zbus_anime::AnimeProxy::new(&conn).await?,
led: zbus_led::LedProxy::new(&conn).await?,
charge: zbus_charge::ChargeProxy::new(&conn).await?,
profile: zbus_profile::ProfileProxy::new(&conn).await?,
rog_bios: zbus_rogbios::RogBiosProxy::new(&conn).await?,
supported: zbus_supported::SupportedProxy::new(&conn).await?,
},
conn,
))
}
pub fn setup_recv(&'a self, conn: Connection) -> SignalReceiver<'a, 'a> {
let mut recv = SignalReceiver::new(conn);
recv.receive_for(self.anime.proxy());
recv.receive_for(self.led.proxy());
recv.receive_for(self.charge.proxy());
recv.receive_for(self.profile.proxy());
recv.receive_for(self.rog_bios.proxy());
recv.receive_for(self.supported.proxy());
recv
}
pub fn anime(&self) -> &zbus_anime::AnimeProxy<'a> {
&self.anime
}
@@ -75,96 +132,24 @@ impl<'a> DbusProxies<'a> {
&self.rog_bios
}
pub fn supported(&self) -> &zbus_supported::SupportProxy<'a> {
pub fn supported(&self) -> &zbus_supported::SupportedProxy<'a> {
&self.supported
}
}
// Signals separated out
pub struct Signals {
pub profile: Receiver<Profile>,
pub led_mode: Receiver<AuraEffect>,
pub led_power_state: Receiver<LedPowerStates>,
pub anime_power_state: Receiver<AnimePowerStates>,
pub charge: Receiver<u8>,
pub bios_gsync: Receiver<bool>,
pub bios_sound: Receiver<bool>,
}
impl Signals {
#[inline]
pub fn new(proxies: &DbusProxies) -> Result<Self> {
Ok(Signals {
profile: {
let (tx, rx) = channel();
proxies.profile.connect_notify_profile(tx)?;
rx
},
charge: {
let (tx, rx) = channel();
proxies.charge.connect_notify_charge(tx)?;
rx
},
led_mode: {
let (tx, rx) = channel();
proxies.led.connect_notify_led(tx)?;
rx
},
led_power_state: {
let (tx, rx) = channel();
proxies.led.connect_notify_power_states(tx)?;
rx
},
anime_power_state: {
let (tx, rx) = channel();
proxies.anime.connect_notify_power_states(tx)?;
rx
},
bios_gsync: {
let (tx, rx) = channel();
proxies.rog_bios.connect_notify_dedicated_graphic_mode(tx)?;
rx
},
bios_sound: {
let (tx, rx) = channel();
proxies.rog_bios.connect_notify_post_boot_sound(tx)?;
rx
},
})
}
}
/// This is the main way to communicate with the DBUS interface
pub struct RogDbusClient<'a> {
proxies: DbusProxies<'a>,
signals: Signals,
}
impl<'a> RogDbusClient<'a> {
#[inline]
pub fn new() -> Result<(Self, Connection)> {
let (proxies, conn) = DbusProxies::new()?;
let signals = Signals::new(&proxies)?;
Ok((RogDbusClient { proxies, signals }, conn))
pub async fn new() -> Result<(RogDbusClient<'a>, Connection)> {
let (proxies, conn) = DbusProxies::new().await?;
Ok((RogDbusClient { proxies }, conn))
}
pub fn proxies(&self) -> &DbusProxies {
&self.proxies
}
pub fn signals(&self) -> &Signals {
&self.signals
}
pub fn setup_recv(&'a self, conn: Connection) -> SignalReceiver<'a, 'a> {
let mut recv = SignalReceiver::new(conn);
recv.receive_for(self.proxies.anime.proxy());
recv.receive_for(self.proxies.led.proxy());
recv.receive_for(self.proxies.charge.proxy());
recv.receive_for(self.proxies.profile.proxy());
recv.receive_for(self.proxies.rog_bios.proxy());
recv.receive_for(self.proxies.supported.proxy());
recv
}
}

View File

@@ -1,12 +1,11 @@
use rog_anime::{AnimeDataBuffer, AnimePowerStates};
use std::sync::mpsc::Sender;
use zbus::{dbus_proxy, Connection, Result};
use zbus_macros::dbus_proxy;
#[dbus_proxy(
interface = "org.asuslinux.Daemon",
default_path = "/org/asuslinux/Anime"
)]
trait Daemon {
trait Anime {
/// Set whether the AniMe will show boot, suspend, or off animations
fn set_boot_on_off(&self, status: bool) -> zbus::Result<()>;
@@ -17,7 +16,7 @@ trait Daemon {
fn set_on_off(&self, status: bool) -> zbus::Result<()>;
/// Writes a data stream of length. Will force system thread to exit until it is restarted
fn write(&self, input: &[u8]) -> zbus::Result<()>;
fn write(&self, input: AnimeDataBuffer) -> zbus::Result<()>;
/// Get status of if the AniMe LEDs are on
#[dbus_proxy(property)]
@@ -29,61 +28,5 @@ trait Daemon {
/// Notify listeners of the status of AniMe LED power and factory system-status animations
#[dbus_proxy(signal)]
fn notify_power_states(&self, data: AnimePowerStates) -> zbus::Result<()>;
}
pub struct AnimeProxy<'a>(DaemonProxy<'a>);
impl<'a> AnimeProxy<'a> {
#[inline]
pub fn new(conn: &Connection) -> Result<Self> {
Ok(AnimeProxy(DaemonProxy::new(conn)?))
}
#[inline]
pub fn proxy(&self) -> &DaemonProxy<'a> {
&self.0
}
/// Set whether the AniMe will show boot, suspend, or off animations
#[inline]
pub fn set_system_animations(&self, on: bool) -> Result<()> {
self.0.set_boot_on_off(on)
}
/// Set whether the AniMe is displaying images/data
#[inline]
pub fn set_led_power(&self, on: bool) -> Result<()> {
self.0.set_on_off(on)
}
/// Writes a data stream of length. Will force system thread to exit until it is restarted
#[inline]
pub fn write(&self, input: AnimeDataBuffer) -> Result<()> {
self.0.write(input.get())
}
/// Get status of if the AniMe LEDs are on
#[inline]
pub fn awake_enabled(&self) -> Result<bool> {
self.0.awake_enabled()
}
/// Get the status of if factory system-status animations are enabled
#[inline]
pub fn boot_enabled(&self) -> Result<bool> {
self.0.boot_enabled()
}
#[inline]
pub fn connect_notify_power_states(
&self,
send: Sender<AnimePowerStates>,
) -> zbus::fdo::Result<()> {
self.0.connect_notify_power_states(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
fn power_states(&self, data: AnimePowerStates) -> zbus::Result<()>;
}

View File

@@ -19,15 +19,13 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::mpsc::Sender;
use zbus::{dbus_proxy, Connection, Result};
use zbus_macros::dbus_proxy;
#[dbus_proxy(
interface = "org.asuslinux.Daemon",
default_path = "/org/asuslinux/Charge"
)]
trait Daemon {
trait Charge {
/// Limit method
fn limit(&self) -> zbus::Result<i16>;
@@ -36,38 +34,5 @@ trait Daemon {
/// NotifyCharge signal
#[dbus_proxy(signal)]
fn notify_charge(&self, limit: u8) -> zbus::Result<()>;
}
pub struct ChargeProxy<'a>(DaemonProxy<'a>);
impl<'a> ChargeProxy<'a> {
#[inline]
pub fn new(conn: &Connection) -> Result<Self> {
Ok(ChargeProxy(DaemonProxy::new(conn)?))
}
#[inline]
pub fn proxy(&self) -> &DaemonProxy<'a> {
&self.0
}
#[inline]
pub fn write_limit(&self, level: u8) -> Result<()> {
self.0.set_limit(level)
}
#[inline]
pub fn get_limit(&self) -> Result<i16> {
self.0.limit()
}
#[inline]
pub fn connect_notify_charge(&self, send: Sender<u8>) -> zbus::fdo::Result<()> {
self.0.connect_notify_charge(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
fn notify_charge(&self, limit: u8) -> zbus::Result<u8>;
}

View File

@@ -1,7 +1,7 @@
//! # DBus interface proxy for: `org.asuslinux.Daemon`
//!
//! This code was generated by `zbus-xmlgen` `1.0.0` from DBus introspection data.
//! Source: `Interface '/org/asuslinux/Led' from service 'org.asuslinux.Daemon' on system bus`.
//! Source: `Interface '/org/asuslinux/Aura' from service 'org.asuslinux.Daemon' on system bus`.
//!
//! You may prefer to adapt it, instead of using it verbatim.
//!
@@ -19,19 +19,20 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::mpsc::Sender;
use std::collections::BTreeMap;
use zbus::{dbus_proxy, Connection, Result};
use zbus::{blocking::Connection, Result};
use zbus_macros::dbus_proxy;
use rog_aura::{AuraEffect, KeyColourArray, LedBrightness, LedPowerStates};
use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, KeyColourArray, LedBrightness};
const BLOCKING_TIME: u64 = 40; // 100ms = 10 FPS, max 50ms = 20 FPS, 40ms = 25 FPS
#[dbus_proxy(
interface = "org.asuslinux.Daemon",
default_path = "/org/asuslinux/Led"
default_path = "/org/asuslinux/Aura"
)]
trait Daemon {
trait Led {
/// NextLedMode method
fn next_led_mode(&self) -> zbus::Result<()>;
@@ -50,111 +51,43 @@ trait Daemon {
/// SetLedMode method
fn set_led_mode(&self, effect: &AuraEffect) -> zbus::Result<()>;
/// SetAwakeEnabled method
fn set_awake_enabled(&self, enabled: bool) -> zbus::Result<()>;
/// SetSleepEnabled method
fn set_sleep_enabled(&self, enabled: bool) -> zbus::Result<()>;
fn set_leds_power(&self, options: AuraPowerDev, enabled: bool) -> zbus::Result<()>;
/// NotifyLed signal
#[dbus_proxy(signal)]
fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>;
#[dbus_proxy(signal)]
fn notify_power_states(&self, data: LedPowerStates) -> zbus::Result<()>;
fn notify_power_states(&self, data: AuraPowerDev) -> zbus::Result<()>;
/// LedBrightness property
#[dbus_proxy(property)]
fn led_brightness(&self) -> zbus::Result<i16>;
/// LedMode property
#[dbus_proxy(property)]
fn led_mode(&self) -> zbus::Result<String>;
fn led_mode(&self) -> zbus::Result<AuraModeNum>;
/// LedModes property
#[dbus_proxy(property)]
fn led_modes(&self) -> zbus::Result<String>;
fn led_modes(&self) -> zbus::Result<BTreeMap<AuraModeNum, AuraEffect>>;
#[dbus_proxy(property)]
fn awake_enabled(&self) -> zbus::Result<bool>;
#[dbus_proxy(property)]
fn sleep_enabled(&self) -> zbus::Result<bool>;
// As property doesn't work for AuraPowerDev (complexity of serialization?)
// #[dbus_proxy(property)]
fn leds_enabled(&self) -> zbus::Result<AuraPowerDev>;
}
pub struct LedProxy<'a>(DaemonProxy<'a>);
pub struct LedProxyPerkey<'a>(LedProxyBlocking<'a>);
impl<'a> LedProxy<'a> {
impl<'a> LedProxyPerkey<'a> {
#[inline]
pub fn new(conn: &Connection) -> Result<Self> {
Ok(LedProxy(DaemonProxy::new(conn)?))
Ok(LedProxyPerkey(LedProxyBlocking::new(conn)?))
}
#[inline]
pub fn proxy(&self) -> &DaemonProxy<'a> {
pub fn proxy(&self) -> &LedProxyBlocking<'a> {
&self.0
}
#[inline]
pub fn get_led_brightness(&self) -> Result<i16> {
self.0.led_brightness()
}
#[inline]
pub fn set_led_brightness(&self, level: LedBrightness) -> Result<()> {
self.0.set_brightness(level)?;
Ok(())
}
/// Set the keyboard LED to enabled while the device is awake
#[inline]
pub fn set_awake_enabled(&self, enabled: bool) -> Result<()> {
self.0.set_awake_enabled(enabled)?;
Ok(())
}
/// Set the keyboard LED suspend animation to enabled while the device is suspended
#[inline]
pub fn set_sleep_enabled(&self, enabled: bool) -> Result<()> {
self.0.set_sleep_enabled(enabled)?;
Ok(())
}
#[inline]
pub fn next_led_mode(&self) -> Result<()> {
self.0.next_led_mode()
}
#[inline]
pub fn prev_led_mode(&self) -> Result<()> {
self.0.prev_led_mode()
}
#[inline]
pub fn next_led_brightness(&self) -> Result<()> {
self.0.next_led_brightness()
}
#[inline]
pub fn prev_led_brightness(&self) -> Result<()> {
self.0.prev_led_brightness()
}
#[inline]
pub fn set_led_mode(&self, mode: &AuraEffect) -> Result<()> {
self.0.set_led_mode(mode)
}
#[inline]
pub fn awake_enabled(&self) -> Result<bool> {
self.0.awake_enabled()
}
#[inline]
pub fn sleep_enabled(&self) -> Result<bool> {
self.0.sleep_enabled()
}
/// Write a single colour block.
///
/// Intentionally blocks for 10ms after sending to allow the block to
@@ -186,25 +119,4 @@ impl<'a> LedProxy<'a> {
// self.0.set_led_mode(&serde_json::to_string(&mode).unwrap())
Ok(())
}
#[inline]
pub fn connect_notify_led(&self, send: Sender<AuraEffect>) -> zbus::fdo::Result<()> {
self.0.connect_notify_led(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
#[inline]
pub fn connect_notify_power_states(
&self,
send: Sender<LedPowerStates>,
) -> zbus::fdo::Result<()> {
self.0.connect_notify_power_states(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
}

View File

@@ -19,19 +19,17 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::mpsc::Sender;
use rog_profiles::{
fan_curve_set::{CurveData, FanCurveSet},
Profile,
};
use zbus::{dbus_proxy, Connection, Result};
use zbus_macros::dbus_proxy;
#[dbus_proxy(
interface = "org.asuslinux.Daemon",
default_path = "/org/asuslinux/Profile"
)]
trait Daemon {
trait Profile {
/// Get the fan-curve data for the currently active Profile
fn fan_curve_data(&self, profile: Profile) -> zbus::Result<FanCurveSet>;
@@ -66,73 +64,5 @@ trait Daemon {
/// NotifyProfile signal
#[dbus_proxy(signal)]
fn notify_profile(&self, profile: Profile) -> zbus::Result<()>;
}
pub struct ProfileProxy<'a>(DaemonProxy<'a>);
impl<'a> ProfileProxy<'a> {
#[inline]
pub fn new(conn: &Connection) -> Result<Self> {
Ok(ProfileProxy(DaemonProxy::new(conn)?))
}
#[inline]
pub fn proxy(&self) -> &DaemonProxy<'a> {
&self.0
}
#[inline]
pub fn active_profile(&self) -> zbus::Result<Profile> {
self.0.active_profile()
}
#[inline]
pub fn enabled_fan_profiles(&self) -> zbus::Result<Vec<Profile>> {
self.0.enabled_fan_profiles()
}
#[inline]
pub fn fan_curve_data(&self, profile: Profile) -> zbus::Result<FanCurveSet> {
self.0.fan_curve_data(profile)
}
#[inline]
pub fn next_profile(&self) -> Result<()> {
self.0.next_profile()
}
#[inline]
pub fn profiles(&self) -> Result<Vec<Profile>> {
self.0.profiles()
}
#[inline]
pub fn set_active_profile(&self, profile: Profile) -> zbus::Result<()> {
self.0.set_active_profile(profile)
}
#[inline]
pub fn set_fan_curve_enabled(&self, profile: Profile, enabled: bool) -> zbus::Result<()> {
self.0.set_fan_curve_enabled(profile, enabled)
}
#[inline]
pub fn set_fan_curve(&self, curve: CurveData, profile: Profile) -> zbus::Result<()> {
self.0.set_fan_curve(profile, curve)
}
#[inline]
pub fn set_active_curve_to_defaults(&self) -> zbus::Result<()> {
self.0.set_active_curve_to_defaults()
}
#[inline]
pub fn connect_notify_profile(&self, send: Sender<Profile>) -> zbus::fdo::Result<()> {
self.0.connect_notify_profile(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
fn notify_profile(&self, profile: Profile) -> zbus::Result<Profile>;
}

View File

@@ -19,15 +19,13 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::mpsc::Sender;
use zbus::{dbus_proxy, Connection, Result};
use zbus_macros::dbus_proxy;
#[dbus_proxy(
interface = "org.asuslinux.Daemon",
default_path = "/org/asuslinux/RogBios"
)]
trait Daemon {
trait RogBios {
/// DedicatedGraphicMode method
fn dedicated_graphic_mode(&self) -> zbus::Result<i16>;
@@ -40,66 +38,21 @@ trait Daemon {
/// SetPostBootSound method
fn set_post_boot_sound(&self, on: bool) -> zbus::Result<()>;
/// PanelOverdrive method
fn panel_overdrive(&self) -> zbus::Result<i8>;
/// SetPanelOverdrive method
fn set_panel_overdrive(&self, overdrive: bool) -> zbus::Result<()>;
/// NotifyDedicatedGraphicMode signal
#[dbus_proxy(signal)]
fn notify_dedicated_graphic_mode(&self, dedicated: bool) -> zbus::Result<()>;
/// NotifyPostBootSound signal
#[dbus_proxy(signal)]
fn notify_post_boot_sound(&self, dedicated: bool) -> zbus::Result<()>;
}
pub struct RogBiosProxy<'a>(DaemonProxy<'a>);
impl<'a> RogBiosProxy<'a> {
#[inline]
pub fn new(conn: &Connection) -> Result<Self> {
Ok(RogBiosProxy(DaemonProxy::new(conn)?))
}
#[inline]
pub fn proxy(&self) -> &DaemonProxy<'a> {
&self.0
}
#[inline]
pub fn get_dedicated_gfx(&self) -> Result<i16> {
self.0.dedicated_graphic_mode()
}
#[inline]
pub fn set_dedicated_gfx(&self, on: bool) -> Result<()> {
self.0.set_dedicated_graphic_mode(on)
}
#[inline]
pub fn get_post_sound(&self) -> Result<i16> {
self.0.post_boot_sound()
}
#[inline]
pub fn set_post_sound(&self, on: bool) -> Result<()> {
self.0.set_post_boot_sound(on)
}
#[inline]
pub fn connect_notify_dedicated_graphic_mode(
&self,
send: Sender<bool>,
) -> zbus::fdo::Result<()> {
self.0.connect_notify_dedicated_graphic_mode(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
#[inline]
pub fn connect_notify_post_boot_sound(&self, send: Sender<bool>) -> zbus::fdo::Result<()> {
self.0.connect_notify_post_boot_sound(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
fn notify_post_boot_sound(&self, sound: bool) -> zbus::Result<()>;
/// NotifyPanelOverdrive signal
#[dbus_proxy(signal)]
fn notify_panel_overdrive(&self, overdrive: bool) -> zbus::Result<()>;
}

View File

@@ -20,32 +20,13 @@
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use rog_supported::SupportedFunctions;
use zbus::{dbus_proxy, Connection, Result};
use zbus_macros::dbus_proxy;
#[dbus_proxy(
interface = "org.asuslinux.Daemon",
default_path = "/org/asuslinux/Supported"
)]
trait Daemon {
trait Supported {
/// SupportedFunctions method
fn supported_functions(&self) -> zbus::Result<SupportedFunctions>;
}
pub struct SupportProxy<'a>(DaemonProxy<'a>);
impl<'a> SupportProxy<'a> {
#[inline]
pub fn new(conn: &Connection) -> Result<Self> {
Ok(SupportProxy(DaemonProxy::new(conn)?))
}
#[inline]
pub fn proxy(&self) -> &DaemonProxy<'a> {
&self.0
}
#[inline]
pub fn get_supported_functions(&self) -> Result<SupportedFunctions> {
self.0.supported_functions()
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "rog_profiles"
version = "1.1.2"
version = "1.1.3"
authors = ["Luke D. Jones <luke@ljones.dev>"]
edition = "2018"
@@ -13,5 +13,5 @@ udev = "^0.6"
serde = "^1.0"
serde_derive = "^1.0"
zvariant = { version = "^2.6", optional = true }
zvariant_derive = { version = "^2.6", optional = true }
zvariant = { version = "^3.0", optional = true }
zvariant_derive = { version = "^3.0", optional = true }

View File

@@ -12,6 +12,7 @@ pub enum ProfileError {
ParseFanCurveDigit(std::num::ParseIntError),
/// (pwm/temp, prev, next)
ParseFanCurvePrevHigher(&'static str, u8, u8),
ParseFanCurvePercentOver100(u8),
// Zbus(zbus::Error),
}
@@ -34,7 +35,9 @@ impl fmt::Display for ProfileError {
"Invalid {}, previous value {} is higher than next value {}",
part, prev, next
),
// Error::Zbus(detail) => write!(f, "Zbus error: {}", detail),
ProfileError::ParseFanCurvePercentOver100(value) => {
write!(f, "Invalid percentage, {} is higher than 100", value)
} // Error::Zbus(detail) => write!(f, "Zbus error: {}", detail),
}
}
}

View File

@@ -29,22 +29,32 @@ pub(crate) fn temp_str(fan: char, index: usize) -> String {
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
pub struct CurveData {
pub(crate) fan: FanCurvePU,
pub(crate) pwm: [u8; 8],
pub(crate) temp: [u8; 8],
pub fan: FanCurvePU,
pub pwm: [u8; 8],
pub temp: [u8; 8],
}
impl std::str::FromStr for CurveData {
type Err = ProfileError;
/// Parse a string to the correct values that the fan curve kernel driver expects
///
/// If the fan curve is given with percentage char '%' then the fan power values are converted
/// otherwise the expected fan power range is 0-255.
///
/// Temperature range is 0-255 in degrees C. You don't want to be setting over 100.
fn from_str(input: &str) -> Result<Self, Self::Err> {
let mut temp = [0u8; 8];
let mut pwm = [0u8; 8];
let mut temp_prev = 0;
let mut pwm_prev = 0;
let mut percentages = false;
for (index, value) in input.split(',').enumerate() {
for (select, num) in value.splitn(2, |c| c == 'c' || c == ':').enumerate() {
if num.contains('%') {
percentages = true;
}
let r = num.trim_matches(|c| c == 'c' || c == ':' || c == '%');
let r = r.parse::<u8>().map_err(ProfileError::ParseFanCurveDigit)?;
@@ -59,15 +69,22 @@ impl std::str::FromStr for CurveData {
temp_prev = r;
temp[index] = r;
} else {
if pwm_prev > r {
let mut p = r;
if percentages {
if r > 100 {
return Err(ProfileError::ParseFanCurvePercentOver100(r));
}
p = (p as f32 * 2.55).round() as u8;
}
if pwm_prev > p {
return Err(ProfileError::ParseFanCurvePrevHigher(
"percentage",
pwm_prev,
r,
p,
));
}
pwm_prev = r;
pwm[index] = r;
pwm_prev = p;
pwm[index] = p;
}
}
}
@@ -149,9 +166,9 @@ impl CurveData {
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FanCurveSet {
pub(crate) enabled: bool,
pub(crate) cpu: CurveData,
pub(crate) gpu: CurveData,
pub enabled: bool,
pub cpu: CurveData,
pub gpu: CurveData,
}
impl Default for FanCurveSet {
@@ -205,7 +222,7 @@ mod tests {
.unwrap();
assert_eq!(curve.fan, FanCurvePU::CPU);
assert_eq!(curve.temp, [30, 49, 59, 69, 79, 89, 99, 109]);
assert_eq!(curve.pwm, [1, 2, 3, 4, 31, 49, 56, 58]);
assert_eq!(curve.pwm, [3, 5, 8, 10, 79, 125, 143, 148]);
}
#[test]

View File

@@ -38,7 +38,7 @@ pub fn find_fan_curve_node() -> Result<Option<Device>, ProfileError> {
}
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy)]
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum Profile {
Balanced,
Performance,
@@ -219,7 +219,9 @@ impl FanCurveProfiles {
}
/// Write the curves for the selected profile to the device. If the curve is
/// in the enabled list it will become active.
/// in the enabled list it will become active. If the curve is zeroed it will be initialised
/// to a default read from the system.
// TODO: Make this return an error if curve is zeroed
pub fn write_profile_curve_to_platform(
&mut self,
profile: Profile,

View File

@@ -1,6 +1,6 @@
[package]
name = "rog_supported"
version = "4.0.0"
version = "4.2.1"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -13,5 +13,5 @@ edition = "2018"
rog_aura = { path = "../rog-aura" }
serde = "^1.0"
serde_derive = "^1.0"
zvariant = "^2.6"
zvariant_derive = "^2.6"
zvariant = "^3.0"
zvariant_derive = "^3.0"

View File

@@ -1,6 +1,6 @@
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
use rog_aura::AuraModeNum;
use rog_aura::{usb::AuraDevice, AuraModeNum, AuraZone};
use serde_derive::{Deserialize, Serialize};
use std::fmt;
use zvariant_derive::Type;
@@ -30,16 +30,20 @@ pub struct PlatformProfileFunctions {
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct LedSupportedFunctions {
pub prod_id: AuraDevice,
pub brightness_set: bool,
pub stock_led_modes: Vec<AuraModeNum>,
pub multizone_led_mode: bool,
pub multizone_led_mode: Vec<AuraZone>,
pub per_key_led_mode: bool,
}
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct RogBiosSupportedFunctions {
pub post_sound_toggle: bool,
pub dedicated_gfx_toggle: bool,
pub post_sound: bool,
pub dedicated_gfx: bool,
pub panel_overdrive: bool,
pub dgpu_disable: bool,
pub egpu_enable: bool,
}
impl fmt::Display for SupportedFunctions {
@@ -80,14 +84,18 @@ impl fmt::Display for LedSupportedFunctions {
writeln!(f, "LED:")?;
writeln!(f, "\tBrightness control: {}", self.brightness_set)?;
writeln!(f, "\tStock LED modes: {:?}", self.stock_led_modes)?;
writeln!(f, "\tMultizone LED mode: {}", self.multizone_led_mode)?;
writeln!(f, "\tMultizone LED mode: {:?}", self.multizone_led_mode)?;
writeln!(f, "\tPer key LED mode: {}", self.per_key_led_mode)
}
}
impl fmt::Display for RogBiosSupportedFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "ROG BIOS:")?;
writeln!(f, "\tPOST sound toggle: {}", self.post_sound_toggle)?;
writeln!(f, "\tDedicated GFX toggle: {}", self.dedicated_gfx_toggle)
writeln!(f, "\tPOST sound switch: {}", self.post_sound)?;
writeln!(f, "\tPanel Overdrive: {}", self.panel_overdrive)?;
writeln!(f, "\tdGPU disable switch: {}", self.dgpu_disable)?;
writeln!(f, "\teGPU enable switch: {}", self.egpu_enable)?;
writeln!(f, "\tDedicated GFX switch: {}", self.dedicated_gfx)?;
Ok(())
}
}

View File

@@ -1,5 +1,5 @@
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
use rog_dbus::RogDbusClientBlocking;
use std::collections::LinkedList;
#[derive(Debug, Clone)]
@@ -52,7 +52,7 @@ impl Ball {
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = RogDbusClient::new()?;
let (dbus, _) = RogDbusClientBlocking::new()?;
let mut colours = KeyColourArray::new();
@@ -60,8 +60,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut balls = [Ball::new(2, 1, 12), Ball::new(4, 6, 12)];
dbus.proxies().led().init_effect()?;
let rows = layout.get_rows();
loop {
for (n, ball) in balls.iter_mut().enumerate() {

Some files were not shown because too many files have changed in this diff Show More