Compare commits

...

364 Commits
2.0.2 ... 4.1.1

Author SHA1 Message Date
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
Luke Jones
ff843b1241 Merge branch 'main' into 'main'
Add G533QS to supported models

See merge request asus-linux/asusctl!81
2021-09-16 10:30:09 +00:00
George Dumitrescu
59e7af149d Add G533QS to supported models 2021-09-16 10:07:48 +03:00
Luke D. Jones
6f14c85287 Revert supergfxctl deps to git 2021-09-16 16:03:41 +12:00
Luke D. Jones
ac0dec4dbf Bump daemon version for release 2021-09-16 11:20:17 +12:00
Luke D. Jones
e3d192412e Bugfixes 2021-09-16 11:19:05 +12:00
Luke Jones
9fadb6db30 Merge branch 'necessary129-main-patch-69023' into 'main'
Add another Strix G17 model

See merge request asus-linux/asusctl!80
2021-09-15 00:06:31 +00:00
Shamil K
f8a1b71866 Add another Strix G17 model 2021-09-14 12:00:04 +00:00
Luke Jones
c0a55acba7 Merge branch 'main' into 'main'
Adding ROG Flow X13 to LED modes known devices

See merge request asus-linux/asusctl!79
2021-09-14 11:45:56 +00:00
Joseph Ferano
4f232de634 Adding ROG Flow X13 to LED modes known devices 2021-09-14 18:05:42 +07:00
Luke Jones
d351ebdaa0 Merge branch 'fluke/fan_curves_v13' into 'main'
Fluke/fan curves v13

See merge request asus-linux/asusctl!78
2021-09-14 02:55:51 +00:00
Luke D. Jones
ab195e1d84 Fan curve enablement
- Add CtrlProfileTask
- Add method to reset active profile curve to platform default
- Wrap the zbus methods for profiles + fan curves
- Enable CLI args for fan curves
- CLI mod and save curves
2021-09-14 14:52:15 +12:00
Luke D. Jones
7041d77256 Fix asusd-ledmodes.toml 2021-09-11 17:03:32 +12:00
Luke D. Jones
99dd052d54 Bump version in readme 2021-09-11 00:27:21 +12:00
Luke Jones
de9942609e Merge branch 'fluke/fan_curves_v9' into 'main'
Fluke/fan curves v9

See merge request asus-linux/asusctl!77
2021-09-10 12:26:14 +00:00
Luke D. Jones
b939a9d331 Fetch and store fan curve correctly 2021-09-11 00:25:46 +12:00
Luke D. Jones
0a565a7a5c Fleshing out functions and zbus controls 2021-09-08 23:33:42 +12:00
Luke D. Jones
bfaa478a4a Begin syncing changes with patch series 2021-09-07 20:20:37 +12:00
Luke Jones
f895a5623e Merge branch 'doc-ubuntu' into 'main'
Add ubuntu repo install commands

See merge request asus-linux/asusctl!76
2021-09-06 03:48:19 +00:00
Guy Sheffer
b7c869bd64 Add ubuntu repo install commands 2021-09-05 10:55:16 +03:00
Luke D. Jones
5f677bc3b9 Provide extra help tips. Update manual 2021-08-29 21:30:30 +12:00
Luke D. Jones
ccfadc2fcb Updates to asusctl cli app 2021-08-28 11:47:01 +12:00
Luke D. Jones
c6cc304a42 Clean up unwrap()'s. Print out info in asusctl if error 2021-08-28 11:07:47 +12:00
Luke D. Jones
3d41a7978a Fix crash when platform_profile not supported
Closes #130
2021-08-28 09:33:23 +12:00
Luke D. Jones
43fc467d58 Fix rm of not installed components 2021-08-28 00:25:51 +12:00
Luke D. Jones
6aba60f604 Try to re-get AniMe dev handle on write error 2021-08-27 22:31:50 +12:00
Luke D. Jones
a13dedf500 Cleanup 2021-08-27 20:46:04 +12:00
dragonn
cb490f23e9 Requesting dbus name after finishing initalizaction 2021-08-27 20:45:37 +12:00
Luke Jones
7dc8a743b9 Merge branch 'fluke/platform_curves' into 'main'
platform_profile + fan curve support

Closes #129 and #128

See merge request asus-linux/asusctl!75
2021-08-27 08:27:10 +00:00
Luke D. Jones
52f3b5a7bf Charge limit: try to support BAT<n>
Closes #128
2021-08-27 20:22:15 +12:00
Luke D. Jones
e89e7ca10f Add LED brightness pre/next cycle
Closes #129
2021-08-27 20:15:15 +12:00
Luke D. Jones
2431dd9e93 Remove supergfxctl to own repo 2021-08-26 16:06:57 +12:00
Luke D. Jones
453d3091c1 Bump major versions 2021-08-26 14:23:28 +12:00
Luke D. Jones
8db37491f3 Mention breaking changes in log 2021-08-26 13:57:19 +12:00
Luke D. Jones
cf915b9e00 Move anime data. Twiddle supergfxctl 2021-08-26 13:53:54 +12:00
Luke D. Jones
326ca37847 rog-supported crate 2021-08-26 13:17:17 +12:00
Luke D. Jones
498e604531 Major update to supergfx and others 2021-08-26 11:44:11 +12:00
Luke D. Jones
60b7f3be69 Begin cleanup 2021-08-25 11:42:32 +12:00
Luke D. Jones
6ceb5cf939 Major restructure to move gfx control out to crate 2021-08-25 11:16:23 +12:00
Luke D. Jones
0ed97db4c1 Temporary checkpoint 2021-08-24 12:35:57 +12:00
Luke D. Jones
8fcd05c2bb Append small extra help to bios option cli 2021-08-12 10:35:37 +12:00
Luke D. Jones
6de4590f27 Update fan curve to use git repo 2021-08-12 10:21:09 +12:00
Luke Jones
b097fd4da9 Merge branch 'fluke/anime' into 'main'
AniMe: more png colour type support

Closes #122 and #121

See merge request asus-linux/asusctl!73
2021-08-11 08:31:33 +00:00
Luke D. Jones
2a8e05707d AniMe: more png colour type support
Closes #121, #122
2021-08-11 20:27:44 +12:00
Luke Jones
a54e112978 Merge branch 'no_star_for_you' into 'main'
Remove Star from G513IH supported modes

See merge request asus-linux/asusctl!72
2021-08-04 01:04:57 +00:00
pshem
5785eb981d Remove Star from G513IH supported modes 2021-08-02 10:06:48 +01:00
Luke Jones
49234af08b Merge branch 'fluke/profiles' into 'main'
Fluke/profiles

See merge request asus-linux/asusctl!71
2021-08-01 22:53:38 +00:00
Luke D. Jones
8f5717def8 Version bump, various fixes 2021-08-02 10:50:17 +12:00
Luke D. Jones
9e55c0b2ca Fix incorrect name match of Star keyboard effect 2021-08-02 10:28:50 +12:00
Luke Jones
dd6ee91364 Merge branch 'main' into 'main'
Enable multizone support on Strix 513IH

See merge request asus-linux/asusctl!69
2021-08-01 22:03:30 +00:00
Luke Jones
efbb838ca1 Merge branch 'G513QY-ledmodes' into 'main'
add G513QY ledmodes

See merge request asus-linux/asusctl!70
2021-08-01 21:27:59 +00:00
Sonny Piers
daf7d39d41 add G513QY ledmodes 2021-08-01 23:12:59 +02:00
pshem
28b1194924 Fix multizone support on Strix 513IH 2021-08-01 18:00:04 +01:00
Luke Jones
73d95ca187 Merge branch 'fluke/profiles' into 'main'
fix: Profile select by name is correctly choosing

Closes #96 and #100

See merge request asus-linux/asusctl!68
2021-07-31 12:19:28 +00:00
Luke D. Jones
00b7c6482f fix: remove the parsing for root supported functions 2021-08-01 00:15:38 +12:00
Luke D. Jones
29c26e8c89 fix: Profile select by name is correctly choosing
Closes #100 #96
2021-08-01 00:10:11 +12:00
Luke D. Jones
fb124dd228 Fix clippy warnings 2021-07-31 23:09:23 +12:00
Luke D. Jones
0599be02dc fix: remove dbg!() output 2021-07-31 23:03:17 +12:00
Luke Jones
81a88263a9 Merge branch 'fluke/cli-fixes' into 'main'
Fluke/cli fixes

See merge request asus-linux/asusctl!67
2021-07-31 11:00:43 +00:00
Luke D. Jones
cea1fd2540 Fix small issues with CLI 2021-07-31 22:57:17 +12:00
Luke Jones
f2ced3bc7c Merge branch 'main' into 'main'
Add Zephyrus M GM501GS

Closes #109

See merge request asus-linux/asusctl!66
2021-07-30 23:48:58 +00:00
pshem
dc2a05894b Add Zephyrus M GM501GS 2021-07-30 17:45:00 +01:00
Luke D. Jones
9ee962ad09 systemd: sleep 2 after unit init 2021-07-30 14:35:44 +12:00
Luke Jones
eb7ef0af4f Merge branch 'main' into 'main'
add a protection to not allow running asusd in terminal

Closes #104

See merge request asus-linux/asusctl!64
2021-07-30 00:12:17 +00:00
Luke Jones
dc51120c27 Merge branch 'main' into 'main'
Add G533QR and 513IH to ledmodes

See merge request asus-linux/asusctl!65
2021-07-30 00:11:05 +00:00
pshem
fc4c2c4346 Add support for 513IH led modes 2021-07-24 19:34:09 +01:00
pshem
3f6be037c1 Add G533QR to ledmodes 2021-07-24 14:39:11 +01:00
Luke Jones
0a80c97f02 Update README.md 2021-07-13 04:36:39 +00:00
dragonn
88abafc728 add a protection to not allow running asusd in terminal 2021-07-09 18:40:38 +02:00
Luke Jones
ac880a0363 Merge branch 'fluke/gfx-mode-changes' into 'main'
Fluke/gfx mode changes

See merge request asus-linux/asusctl!63
2021-06-10 21:06:56 +00:00
Luke D. Jones
baebd51d99 Better control of gfx modes 2021-06-11 09:03:50 +12:00
Luke D. Jones
226620eb53 Fix up modes and icons 2021-06-07 19:19:49 +12:00
Luke D. Jones
1b34079d14 Trial of blocking vfio/compute unless in integrated mode 2021-06-07 11:02:21 +12:00
Luke D. Jones
8deeffcdad Minor fix to compute/vfio switch 2021-06-06 21:33:31 +12:00
Luke D. Jones
b7e45d7305 Bump versions for release 2021-06-06 21:02:07 +12:00
Luke D. Jones
d9077db234 Bugfix configs 2021-06-06 20:58:19 +12:00
Luke D. Jones
439b006342 Force change to integrated if in nvidia or hybrid mode
Force change to integrated if in nvidia or hybrid mode and user tries
to switch to vfio or compute
2021-06-06 13:44:34 +12:00
Luke D. Jones
ffa74d52e5 Fix LED brightness apply on resume 2021-06-06 12:12:17 +12:00
Luke Jones
6ccdd703e6 Merge branch 'fluke/anime-fade' into 'main'
Fluke/anime fade

See merge request asus-linux/asusctl!62
2021-05-31 01:07:17 +00:00
Luke D. Jones
bb910344b8 Basic fade in/out of gifs 2021-05-31 10:06:35 +12:00
Luke D. Jones
b9c4ff9ca7 Add GA503Q led modes 2021-05-30 10:20:17 +12:00
Luke D. Jones
62a18d4e57 Minor cleanup of bios controller 2021-05-29 15:33:47 +12:00
Luke D. Jones
f520e381a9 Attempt to provide more info to users gfx switching 2021-05-29 11:42:15 +12:00
Luke D. Jones
1dd543ddf3 Simplify the notifier 2021-05-27 13:22:08 +12:00
Luke Jones
a7ef63bd8a Merge branch 'formatted_supported_functions_message' into 'main'
Improving message formatting of supported functions

See merge request asus-linux/asusctl!61
2021-05-26 19:44:47 +00:00
KoStard
db43c0f2a4 Improving message formatting of supported functions
- Implementing fmt::Display for SupportedFunctions and sub-structs
2021-05-26 20:09:44 +04:00
Luke D. Jones
f0e5bb4ad1 dbus: send/recv notifications for bios options 2021-05-26 21:17:45 +12:00
Luke D. Jones
36bba75c50 bugfix: fix profile fan modes and creating 2021-05-26 09:24:18 +12:00
Luke Jones
b2dc610c0b Merge branch 'fluke/extras' into 'main'
bugfix: fix profile cycling

See merge request asus-linux/asusctl!60
2021-05-25 09:46:26 +00:00
Luke D. Jones
42d0eb0aba bugfix: fix profile cycling 2021-05-25 21:44:01 +12:00
Luke Jones
c14768182c Merge branch 'fluke/extras' into 'main'
Update config & dbus parts, cleanup deps, device power states

See merge request asus-linux/asusctl!59
2021-05-24 10:13:46 +00:00
Luke D. Jones
ef7e2135bf Prep for release 2021-05-24 22:10:30 +12:00
Luke D. Jones
2b58e259de Update config & dbus parts, cleanup deps, device power states
- Add extra config options and dbus methods
- Add power state signals for anime and led
- Refactor to use channels for dbus signal handler send/recv
- Split out profiles independant parts to a rog-profiles crate
- Cleanup dependencies
- Fix some dbus Supported issues
2021-05-24 18:56:21 +12:00
Luke D. Jones
ba03e8feb8 gfx: tmp informational only store of vfio/compute mode 2021-05-18 09:54:26 +12:00
Luke D. Jones
7771c6b8da gfx: Remove option for vfio/compute save 2021-05-18 09:18:36 +12:00
Luke D. Jones
04c9285ee6 add GX550L led modes 2021-05-17 12:25:16 +12:00
Luke D. Jones
bf4141e4b8 Additional info in manual 2021-05-16 15:59:39 +12:00
Luke D. Jones
233315f668 Small fix to heading in manual 2021-05-16 15:24:30 +12:00
Luke D. Jones
8332fb12f1 Update documents 2021-05-16 15:17:46 +12:00
Luke D. Jones
594e69f9b7 Minor readme update 2021-05-15 22:58:42 +12:00
Luke Jones
0aac0ce495 Merge branch 'fluke/refactor-patterns' into 'main'
bugfix: don't deadlock on change compute/vfio/compute

Closes #88 and #86

See merge request asus-linux/asusctl!58
2021-05-15 10:25:03 +00:00
Luke D. Jones
e24b4858a4 bugfix: don't deadlock on change compute/vfio/compute
Closes: #86 #88
2021-05-15 22:22:36 +12:00
Luke D. Jones
cf2b459e48 Add legal statement for trademarks 2021-05-06 12:29:12 +12:00
Luke D. Jones
895179fdad Add legal statement for trademarks 2021-05-06 12:27:59 +12:00
Luke Jones
fe3e8792eb Merge branch 'readmefix' into 'main'
corrected auto-builds url

See merge request asus-linux/asusctl!56
2021-05-03 18:53:15 +00:00
Aaron Johnson
1916641e2e corrected auto-builds url 2021-05-03 12:58:33 -05:00
Luke Jones
3ea0737be9 Merge branch 'fluke/refactor-patterns' into 'main'
Fluke/refactor patterns

See merge request asus-linux/asusctl!55
2021-04-26 03:53:21 +00:00
Luke D. Jones
c67373a830 bugfix: add version to user daemon. Fix multiple anime config 2021-04-26 15:49:35 +12:00
Luke D. Jones
41cbf4d353 Refactor dameon gfx 2021-04-25 22:53:38 +12:00
Luke Jones
7a4c14f7b8 Merge branch 'fluke/aura-crate' into 'main'
Release prep

See merge request asus-linux/asusctl!54
2021-04-25 03:07:13 +00:00
Luke D. Jones
f52a4d464a Release prep 2021-04-25 14:52:26 +12:00
Luke Jones
aa71592a31 Merge branch 'fluke/aura-crate' into 'main'
profiles: add dbus methods to change active profile

Closes #68, #73, and #81

See merge request asus-linux/asusctl!53
2021-04-25 02:37:31 +00:00
Luke D. Jones
dc6e8f8dcb profiles: add dbus methods to change active profile
Closes #81, #73, #68
2021-04-25 14:33:41 +12:00
Luke D. Jones
1a4836246f aura: support keyboard LED enable/disable with awake/sleep 2021-04-25 12:28:09 +12:00
Luke D. Jones
ab80b0742f gfx: asusd config option to not save compute/vfio mode switch 2021-04-20 21:10:23 +12:00
Luke D. Jones
6926aeed20 gfx: enable correct rebootless compute mode switch 2021-04-20 19:33:55 +12:00
Luke D Jones
f95e42e4b9 Reload LED mode on boot 2021-04-19 10:05:37 +12:00
Luke D Jones
82bee6b86e Update asusd unit for selinux 2021-04-15 19:43:09 +12:00
Luke D Jones
bd9bc8bcff anime: services for system sequences 2021-04-14 23:14:57 +12:00
Luke D Jones
8a6d364304 anime: initial system config work 2021-04-12 17:35:04 +12:00
Luke D Jones
64d99a3e05 gfx: partial save and recover of mode change
Properly set and recover to last mode for g-sync laptops

Partial close of #75
2021-04-12 10:49:08 +12:00
Luke D Jones
59f54b76f6 aura: split out all aura related files to rog-aura crate 2021-04-12 10:31:36 +12:00
Luke D Jones
6f36d91281 Begin rog-aura crate 2021-04-12 10:31:36 +12:00
Luke D Jones
e9f1fa01fc index on anime-cli: 0657c6c anime: prep rog-anime for publish, rename *all* AniMe~ to Anime 2021-04-12 10:31:36 +12:00
Luke D Jones
0d3a5d266b Changelog 2021-04-12 10:31:07 +12:00
Luke D Jones
cc28cee8bd anime: fix init 2021-04-11 22:47:21 +12:00
Luke D Jones
6ebf0c2bb2 Update makefile 2021-04-11 20:39:15 +12:00
Luke D Jones
77c658c94e Release 3.4.0 2021-04-11 20:36:31 +12:00
Luke Jones
df64a51372 Merge branch 'fluke/anime-cli' into 'main'
anime: prep rog-anime for publish, rename *all* AniMe~ to Anime

See merge request asus-linux/asusctl!50
2021-04-11 06:54:05 +00:00
Luke D Jones
0657c6cc74 anime: prep rog-anime for publish, rename *all* AniMe~ to Anime 2021-04-11 16:26:52 +12:00
Luke Jones
f116905e85 Merge branch 'fluke/anime-cli' into 'main'
anime: add zbus methods

See merge request asus-linux/asus-nb-ctrl!49
2021-04-10 09:58:56 +00:00
Luke D Jones
e515741efa anime: add zbus methods 2021-04-10 21:54:08 +12:00
Luke Jones
d516abdc92 Merge branch 'fluke/anime-cli' into 'main'
anime: tweak gif animation time types

See merge request asus-linux/asus-nb-ctrl!48
2021-04-09 11:21:35 +00:00
Luke D Jones
ece565de1c anime: tweak gif animation time types 2021-04-09 23:17:50 +12:00
Luke Jones
eb83d1a835 Merge branch 'fluke/anime-cli' into 'main'
anime: CLI and user-daemon work

See merge request asus-linux/asus-nb-ctrl!47
2021-04-09 08:45:06 +00:00
Luke D Jones
7d0f15d738 anime: CLI and user-daemon work 2021-04-09 20:41:25 +12:00
Luke Jones
8010da0891 Merge branch 'fluke/anime-cli' into 'main'
Fluke/anime cli

See merge request asus-linux/asus-nb-ctrl!46
2021-04-07 07:28:35 +00:00
Luke D Jones
aa500c35c4 daemon: revert zbus to 1.9.1 in daemon 2021-04-07 15:05:55 +12:00
Luke D Jones
2af33a0416 daemon: revert zbus to 1.9.1 in daemon 2021-04-07 15:05:26 +12:00
Luke D Jones
9b4ed6eb62 anime: discard frames if specified 2021-04-06 22:03:06 +12:00
Luke D Jones
47c1ca9fe4 anime: gif-image 2021-04-06 21:43:57 +12:00
Luke Jones
3cd624daf0 Merge branch 'fluke/anime-cli' into 'main'
Anime: Tweak the diagonal data to be more correct

See merge request asus-linux/asus-nb-ctrl!45
2021-04-06 02:01:41 +00:00
Luke D Jones
fa16864a3e Tweak the diagonal to be more correct 2021-04-06 13:56:02 +12:00
Luke Jones
bfc31b06d5 Merge branch 'fluke/anime-diag' into 'main'
Fluke/anime diag

See merge request asus-linux/asus-nb-ctrl!44
2021-04-05 09:12:04 +00:00
Luke D Jones
d854f7da1b Prepare for user saving of anime sequences 2021-04-05 21:06:53 +12:00
Luke D Jones
6d746b21a5 Anime gifs 2021-04-05 17:12:00 +12:00
Luke D Jones
226c083a51 Diagonal data structure 2021-04-05 00:02:05 +12:00
Luke Jones
de59d00949 Merge branch 'fluke/gfx-zbus-cleanup' into 'main'
Fluke/gfx zbus cleanup (incorrectly named branch)

Closes #72

See merge request asus-linux/asus-nb-ctrl!42
2021-04-03 08:46:17 +00:00
Luke D Jones
7ff01f12e9 Add extra models to ledmodes
- Configurable anime example
- Gfx power states as enum

Closes #72
2021-04-03 21:42:39 +13:00
Luke Jones
fbc248177a Merge branch 'fluke/gfx-vfio-optional' into 'main'
Put vfio behind config option

See merge request asus-linux/asus-nb-ctrl!43
2021-04-02 23:54:36 +00:00
Luke D Jones
fc3d7653f5 Add missing if condition for vfio 2021-04-03 12:50:13 +13:00
Luke D Jones
2dc70ea6af Put vfio behind config option 2021-04-03 09:59:36 +13:00
Luke D Jones
01345b28a5 Add extra models to ledmodes 2021-03-29 19:36:30 +13:00
Luke Jones
4eeacea832 Merge branch 'fluke/vfio' into 'main'
Bugfix vfio/integrated

See merge request asus-linux/asus-nb-ctrl!41
2021-03-24 23:04:23 +00:00
Luke D Jones
6bf0fdd117 Bugfix vfio/integrated 2021-03-25 11:14:59 +13:00
Luke Jones
7fcde7df17 Merge branch 'fluke/vfio-builtin' into 'main'
Fluke/vfio builtin

See merge request asus-linux/asus-nb-ctrl!40
2021-03-24 06:45:35 +00:00
Luke D Jones
543b0b817f Try remove nouveau 2021-03-24 19:44:40 +13:00
Luke D Jones
5a7d31fdf6 Bugfixes to session handler. Add extra profile commands
- Better handling of session tracking
- List all profile data
- Get active profile name
- Get active profile data
2021-03-24 16:30:13 +13:00
Luke D Jones
301c532b65 Formatting 2021-03-23 13:45:57 +13:00
Luke D Jones
df7ae4d014 Fix: non-rgb keyboard backlight control 2021-03-23 13:44:07 +13:00
Luke D Jones
96ceef1bdb Prep v3.2.1 2021-03-22 16:45:05 +13:00
Luke Jones
bc72b93625 Merge branch 'fluke/led-work' into 'main'
Fluke/led work

See merge request asus-linux/asus-nb-ctrl!39
2021-03-22 03:43:05 +00:00
Luke D Jones
03b338bdfa Strongly type the Led brightness 2021-03-22 16:36:10 +13:00
Luke D Jones
7a51cd1c70 Cleaned up 2021-03-22 11:03:56 +13:00
Luke D Jones
0449a4b06b Initial cleanup 2021-03-22 10:24:28 +13:00
Luke D Jones
bc46fa2b1e Prep new release 2021-03-21 21:52:30 +13:00
Luke Jones
759ddeb270 Merge branch 'fluke/vm-mode' into 'main'
Fluke/vm mode

See merge request asus-linux/asus-nb-ctrl!38
2021-03-21 08:50:23 +00:00
Luke D Jones
538e111e78 VFIO mode enabled 2021-03-21 21:50:03 +13:00
Luke D Jones
45ab568f7a Changelog update 2021-03-20 21:41:22 +13:00
Luke Jones
b32089843a Merge branch 'profile_remove' into 'main'
Added --remove ability to profile subcommand

See merge request asus-linux/asus-nb-ctrl!37
2021-03-20 08:40:04 +00:00
Luke Jones
d960aacf4f Merge branch 'fluke/optimising' into 'main'
Massive refactor of led control

Closes #53 and #63

See merge request asus-linux/asus-nb-ctrl!36
2021-03-20 08:30:39 +00:00
Tony Dwire
1c48ab227d Added --remove ability to profile subcommand 2021-03-19 22:24:59 -05:00
Luke D Jones
6528ec95c2 Massive refactor of led control
- Write brightness to kernel LED class path

Closes #63, #53
2021-03-20 11:58:47 +13:00
Luke Jones
53ee6015d0 Merge branch 'main' into 'main'
Added --list for profiles

See merge request asus-linux/asus-nb-ctrl!35
2021-03-17 01:50:16 +00:00
Tony
ad150903af Forwarded error from ProfileProxy::profile_names instead of 'expecting' there. Handled error up in main by logging. Reorganized code in ctrl_fan_cpu to keep consistent code structure 2021-03-17 01:50:16 +00:00
Luke Jones
c29afaf751 Merge branch 'fluke/optimising' into 'main'
Fluke/optimising

See merge request asus-linux/asus-nb-ctrl!34
2021-03-16 08:12:01 +00:00
Luke D Jones
cec4016862 Refactored gfx switch session monitor 2021-03-16 21:09:17 +13:00
Luke Jones
c697d94a00 Merge branch 'main' into 'main'
added fish completion

See merge request asus-linux/asus-nb-ctrl!32
2021-03-14 08:30:39 +00:00
Luke D Jones
35438e2e77 Move logind-zbus to own crate and publish 2021-03-13 22:07:31 +13:00
alenpaul2001
716b524d70 updated Makefile 2021-03-13 01:01:22 +05:30
alenpaul2001
cffd5672b2 added fish completion 2021-03-13 00:40:46 +05:30
Luke D Jones
82bb032336 Bump crate deps 2021-03-12 22:09:40 +13:00
Luke D Jones
ae4f7f9949 Buildup of logind dbus methods 2021-03-12 22:00:31 +13:00
Luke D Jones
875ff6d354 Begin implementing logind dbus crate 2021-03-12 16:55:52 +13:00
Luke D Jones
842fa48fac Refresh sessions list every 3rd active check 2021-03-12 15:04:37 +13:00
Luke D Jones
8a63dce85f Bugfix: destroy the deref clone stackoverflow 2021-03-11 23:42:38 +13:00
Luke Jones
01386599f4 Merge branch 'fluke/hotfixing' into 'main'
Graphics switching now waits for user sessions to end

See merge request asus-linux/asus-nb-ctrl!31
2021-03-11 08:17:41 +00:00
Luke D Jones
4310b4b742 Graphics switching now waits for user sessions to end 2021-03-11 21:13:41 +13:00
Luke D Jones
89f4dd6ec4 Prep release 2021-03-11 12:32:34 +13:00
Luke Jones
85e0b79fb9 Merge branch 'fluke/testing-gfx-switch' into 'main'
More verbose and thorough checks for gfx switching

See merge request asus-linux/asus-nb-ctrl!30
2021-03-10 23:28:02 +00:00
Luke D Jones
fba5f26f7e More verbose and thorough checks for gfx switching
- Small fixes
- Cleanup bios help
- g-sync warnings on toggling
2021-03-11 12:24:01 +13:00
Luke D Jones
90b0fc434d Hotfix: graphics help display 2021-03-10 21:23:35 +13:00
Luke D Jones
6743d5bc78 Add display-manager restart check 2021-03-10 18:42:44 +13:00
Luke D Jones
def0259d24 Bump version 2021-03-10 16:47:22 +13:00
Luke D Jones
a678f54f59 :sadface: 2021-03-10 16:45:06 +13:00
Luke D Jones
ebe7e61355 Slightly change how module load error is reported 2021-03-10 16:30:42 +13:00
Luke D Jones
bda58c9695 Trial of logging for gfx switch 2021-03-10 16:21:53 +13:00
Luke D Jones
e335133bf8 refactor help again 2021-03-10 16:17:22 +13:00
Luke D Jones
47432524e1 Further improve CLI feedback 2021-03-10 16:01:04 +13:00
Luke D Jones
707b3bcc2d Notify on manually select profile 2021-03-10 15:24:24 +13:00
Luke D Jones
60014b8a40 Customise initial help for laptop 2021-03-10 14:43:48 +13:00
Luke D Jones
2e4ce27f6b Hotfix: try to handle module remove gracefully
Try to handle module remove more gracefully if in-use when the
display manager is shutting down
2021-03-10 14:07:08 +13:00
Luke D Jones
b8384c55c3 Bump changelog version 2021-03-10 11:21:09 +13:00
Luke D Jones
dfe1f02101 Hotfix: Catch some edge-cases exposed on fedora 34 2021-03-10 11:20:19 +13:00
Luke D Jones
7c2fb0be81 Hotfix: Nvidia module handling improved 2021-03-10 10:15:59 +13:00
Luke D Jones
b05f680650 Test and create /etc/X11/xorg.conf.d/ if not exist 2021-03-10 09:20:59 +13:00
Luke D Jones
2a9a436f9c Add nvidia-uvm to module list 2021-03-10 07:34:03 +13:00
Luke D Jones
0d6faf3fda Mark as new release 2021-03-09 17:23:19 +13:00
Luke Jones
aede000218 Merge branch 'fluke/rebootless-gfx-switch' into 'main'
Fluke/rebootless gfx switch

See merge request asus-linux/asus-nb-ctrl!29
2021-03-09 04:20:29 +00:00
Luke D Jones
176ab0a639 Rebootless graphics switching
This changes out how the current graphics switching works, enabling
asusd to stop/start the display-manager to enable/disable PCI devices
and add/remove drivers as required.

All existing graphics modes and commands still work as normal.

G-Sync enable is now only through the bios setting, and on reboot
will set all relevant settings to Nvidia mode.
2021-03-09 16:45:43 +13:00
Luke D Jones
4efb2caa56 GU502LU led-modes 2021-03-07 21:48:44 +13:00
Luke D Jones
6f81f86483 Version bump 2021-02-22 11:48:08 +13:00
Luke D Jones
b64b8a38e4 cargo update, update udev rules 2021-02-22 11:34:47 +13:00
Luke Jones
ff56170ac5 Merge branch 'main' into 'main'
added G531GD stock_led_modes

See merge request asus-linux/asus-nb-ctrl!28
2021-02-21 08:47:51 +00:00
alenpaul2001
733f1f827e added G531GD stock_led_modes 2021-02-21 13:00:04 +05:30
Luke Jones
ac903a05da Merge branch 'donate-button' into 'main'
Test donate button

See merge request asus-linux/asus-nb-ctrl!27
2021-02-19 21:39:49 +00:00
Luke D Jones
838e6f789b Test donate button 2021-02-20 10:36:28 +13:00
Luke D Jones
d462393e8b Add 'users' group to dbus config 2021-02-20 10:15:38 +13:00
Luke D Jones
e98cf8d50b Add 0x19b6 to supported keyb list 2021-02-19 19:32:14 +13:00
Luke
eb173fc9dc CI pipe fix 2021-02-14 22:45:37 +13:00
Luke
50756046cf Cleanup fan+cpu+config 2021-02-07 00:25:40 +13:00
Luke
629bdc2213 Large code cleanup 2021-02-06 23:18:01 +13:00
Luke
39bbe33831 Further refinement 2021-02-06 08:53:02 +13:00
Luke
00bd556d7a Initial refactor 2021-02-06 08:53:02 +13:00
Luke
12061ea9df Fix 'Supported' dbus method 2021-02-06 08:52:35 +13:00
Luke Jones
580ed72e73 Merge branch 'asere/anime_compatibility' into 'next'
Adding asusd rules to restart asusd service when anime is detected too late

See merge request asus-linux/asus-nb-ctrl!18
2021-02-04 00:31:04 +00:00
Asere
c01f0892a5 adding asusd rules to restart asusd service when anime is detected too late 2021-02-03 16:44:44 +01:00
Luke Jones
0fed34b12e Merge branch 'fluke/crate-refactor' into 'next'
split out types, dbus

See merge request asus-linux/asus-nb-ctrl!21
2021-02-03 10:07:10 +00:00
Luke
0af68baf7b split out types, dbus 2021-02-03 23:06:54 +13:00
Luke Jones
161e3c4d3b Merge branch 'fluke/zbus-migrate' into 'next'
Migrate to use zbus for all dbus requirements

See merge request asus-linux/asus-nb-ctrl!20
2021-02-03 03:47:16 +00:00
Luke
4720af2cb8 Migrate to use zbus for all dbus requirements 2021-02-03 16:46:48 +13:00
Luke Jones
06d37aa009 Merge branch 'fluke/1854-device' into 'next'
Try to fix up multizone modes

See merge request asus-linux/asus-nb-ctrl!19
2021-02-01 22:09:17 +00:00
Luke
c6fa860b2e Try to fix up multizone modes
- Write set+apply after each array in multizone
- Remove misc bad logic
- Use same code path as 0x1866 device to configure led support
- Remove duplicate code
- Set correct speeds for multizone
2021-02-02 11:08:45 +13:00
Luke
4fe9ab70e5 Merge branch 'fluke/1854-device' into next 2021-01-31 10:00:48 +13:00
Luke
920e4e86f5 Trial fix for 1854 2021-01-31 10:00:06 +13:00
Luke
720dc0c177 v2.2.2 prep 2021-01-31 09:59:24 +13:00
Luke Jones
b3a555cab9 Merge branch 'fluke/asus_bios_settings' into 'next'
Bugfixes and improvements

Closes #48

See merge request asus-linux/asus-nb-ctrl!17
2021-01-27 01:16:39 +00:00
Luke
cf13b4f71b Bugfixes and improvements
- fix CLI feedback for reboot/restartx. Update readme
- dracut force driver include for nvidia dedicated
- change fan-mode CLI tag

Closes #48
2021-01-27 14:13:02 +13:00
Luke Jones
cd0b9fe350 Merge branch 'fluke/asus_bios_settings' into 'next'
Fluke/asus bios settings

See merge request asus-linux/asus-nb-ctrl!15
2021-01-26 08:08:13 +00:00
Luke
82900f4645 CLI args for bios. Cleanup and improve
- dbus method for 'supported modes'
- add dedicated gfx safety
- bring ctrl-gfx back in to main control for better integration
- safely upgrade config files
2021-01-26 21:07:19 +13:00
Luke
703bba9ffd Correct changelog 2021-01-10 22:34:45 +13:00
Luke Jones
73706154a4 Merge branch 'fluke/fixes' into 'next'
Fixes

See merge request asus-linux/asus-nb-ctrl!14
2021-01-10 09:31:46 +00:00
Luke
c9b2a0c777 Fixes
- Adjust gfx controller to assume that the graphics driver is loaded if the
  mode is set for nvidia/hybrid
- Small code adjustments for error handling
2021-01-10 22:27:56 +13:00
Luke
54cc51fe5d Minor update to deps 2021-01-09 13:48:13 +13:00
Luke
81645d0777 Update deps and fmt 2021-01-09 13:42:46 +13:00
Luke Jones
d61c180ee5 Update discord link 2020-12-20 21:21:53 +00:00
Luke Jones
a668800fd9 Merge branch 'asere/anime_better_options' into 'next'
Better anime options & gumdrop error Display

See merge request asus-linux/asus-nb-ctrl!12
2020-12-20 21:18:51 +00:00
Asere
3bdc11c994 Better anime options & gumdrop error Display 2020-11-25 18:37:36 +01:00
Luke Jones
b496139063 Merge branch 'StaticRocket-next-patch-05672' into 'next'
Update paths and variables to follow GNU Make standards

See merge request asus-linux/asus-nb-ctrl!13
2020-11-08 00:17:44 +00:00
Randolph Sapp
607483629a Don't rebuild during install
Rebuild during all
2020-11-08 00:17:44 +00:00
Luke D Jones
5c8d138cef Update readme 2020-10-25 20:37:53 +13:00
Luke D Jones
e3db1f7c4c Add notification dbus signal for aura next/prev 2020-10-25 19:34:40 +13:00
Luke D Jones
de3b803f14 Add DBUS methods to toggle next/previous aura mode 2020-10-25 15:49:14 +13:00
Luke D Jones
0558f919c4 Add DBUS method to toggle to next profile 2020-10-25 15:02:35 +13:00
Luke Jones
68ea73c847 Merge branch 'asere/anime_on_off' into 'next'
AniMe: adding --on and --off options to turn on/off (and accept/reject write requests)

See merge request asus-linux/asus-nb-ctrl!11
2020-10-25 01:14:12 +00:00
Luke Jones
96ddb7132d Merge branch 'asere/show_keyboard_brightness' into 'next'
Improving user experience by showing current keyboard led brightness

See merge request asus-linux/asus-nb-ctrl!10
2020-10-25 01:12:21 +00:00
Asere
d36ac44603 updating option "-k" to show current brightness: asusctl -k 2020-10-23 02:00:08 +02:00
Asere
5d06c87943 AniMe: adding --on and --off options to turn on/off (and accept/reject write requests) 2020-10-22 14:09:05 +02:00
Luke D Jones
588e3c0102 Panic if config file is bad 2020-10-22 08:28:12 +13:00
Luke D Jones
6ce32c1cab Fix one super silly bug
closes #30
2020-10-22 08:16:00 +13:00
Luke Jones
a23c51e5db Adding Anime commands to asusctl 2020-10-11 22:46:06 +00:00
Asere
3845071ba3 adding Anime feature: led brightness command to asusctl, and updating dbus client 2020-10-12 00:11:26 +02:00
Luke Jones
a48b3634bf Merge branch 'next' into 'next'
Add some basic zsh completions for asusctl

See merge request asus-linux/asus-nb-ctrl!8
2020-10-05 00:38:34 +00:00
Luke Jones
806882b2e9 Merge branch 'next' into 'next'
fixed fan presets for profiles

See merge request asus-linux/asus-nb-ctrl!7
2020-10-05 00:37:43 +00:00
Luke D Jones
9ac3c46fe6 Actually enable/disable gfx switch control 2020-10-05 09:32:46 +13:00
Luke D Jones
6817a1c027 Bump dbus autogen 2020-10-05 09:31:07 +13:00
Dylan Jones
cf2be1b12b Add some basic zsh completions for asusctl 2020-09-30 13:28:05 -04:00
Luke D Jones
9ffeb19c8c Bump version 2020-09-29 21:30:23 +13:00
Cuong
a7f6e8bd24 fixed fan presets for profiles 2020-09-28 10:04:57 +00:00
Luke Jones
f61f62c219 Merge branch 'feature/dbus-profile-name' into 'next'
 Add ActiveProfileName() dbus method returning name of current profile

See merge request asus-linux/asus-nb-ctrl!5
2020-09-26 08:50:03 +00:00
Andreas Streichardt
bb70fd7b71 Add ActiveProfileName() dbus method returning name of current profile 2020-09-26 09:05:50 +02:00
Luke D Jones
b80c860b7a Fix args to systemctl for reboot 2020-09-25 21:05:47 +12:00
Luke D Jones
3c7544f034 Explicitly state arch is not supported 2020-09-25 14:14:21 +12:00
Luke D Jones
6746c2b654 Update changelog 2020-09-24 09:04:49 +12:00
Luke D Jones
8d59f89438 Add verbose error for LED node missing 2020-09-24 08:18:10 +12:00
Luke D Jones
5753160e91 Don't throw away do_task error, log it 2020-09-24 08:10:05 +12:00
Luke D Jones
622cd9d943 Fix timeout for GFX switching 2020-09-24 08:00:37 +12:00
Luke D Jones
2daa7f0811 Git internal version from crate version 2020-09-23 22:12:47 +12:00
Luke D Jones
05c53df0ed Better description of config panic 2020-09-23 21:24:25 +12:00
Luke D Jones
a7419cbc4c Remove many unwraps or change to maps 2020-09-23 20:47:33 +12:00
Luke D Jones
67ad38a7e6 Ditch the gsync line in screen settings file. Oops
- Add force option to `systemctl reboot`. Be careful.
2020-09-23 16:51:46 +12:00
Luke Jones
e572ae2c62 Update Makefile 2020-09-23 02:32:25 +00:00
195 changed files with 15569 additions and 6400 deletions

7
.gitignore vendored
View File

@@ -1,2 +1,7 @@
/target
vendor.tar.xz
vendor.tar.xz
cargo-config
.idea
vendor-*
vendor_*
.vscode-ctags

View File

@@ -13,7 +13,7 @@ test:
build:
only:
- next
- main
script:
- make && make vendor
artifacts:

View File

@@ -4,9 +4,351 @@ 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 ]
# [2.0.0] - 2020-09-21
## [4.1.1] - 2022-06-21
### Changed
- Fixes to anime matrix system thread cancelation
## [4.1.0] - 2022-06-20
### Changed
- Huge refactor to use zbus 2.2 + zvariant 3.0 in system-daemon.
- Daemons with tasks now use `smol` for async ops.
- Fixes to fan-curve settings from CLI (Author: Armas Span)
- Add brightness to anime zbus notification
- Adjust how threads in AniMe matrix controller work
- Use proper power-state packet for keyboard LED's (Author: Martin Piffault)
### Added
- Support for GA402R LED modes
- Support for GU502LV LED modes
- Support for G512 LED modes
- Support for G513IC LED modes (Author: dada513)
- Support for G513QM LED modes (Author: Martin Piffault)
- Add side-LED toggle support (Author: Martin Piffault)
- Support reloading keyboard mode on wake (from sleep/hiber)
- Support reloading charge-level on wake (from sleep/hiber)
- Support running AniMe animation blocks on wake/sleep and boot/shutdown events
# [4.0.7] - 2021-12-19
### Changed
- Fix incorrect power-profile validation
- Update asusd-ledmodes.toml to support Asus Rog Strix G15 G513QE (@LordVicky)
- Update patch notes and links
# [4.0.6] - 2021-11-01
### Changed
- Fix CLI for bios toggles
### Added
- Extra commands for AniMe: pixel-image, gif, pixel-gif
# [4.0.5] - 2021-10-27
### Changed
- Convert fan curve percentage to 0-255 expected by kernel driver only if '%' char is used, otherwise the expected range for fan power is 0-255
- Use correct error in daemon for invalid charging limit
- Enforce charging limit values in range 20-100
### Added
- LED modes for G513QR
# [4.0.4] - 2021-10-02
### Changed
- Add missing Profile commands
- Spawn tasks on individual threads to prevent blocking
- Don't force fan-curve default on reload
- Begin obsoleting the graphics switch command in favour of supergfxctl
- Slim down the notification daemon to pure ASUS notifications
# [4.0.3] - 2021-09-16
### Changed
- Don't show fan-curve warning if fan-curve available
- Add G713QR to Strix led-modes
- Fix part of CLI fan-curve control
# [4.0.2] - 2021-09-14
### Changed
- Backup old configs to *-old if parse fails
- Prevent some types of crashes related to unpatched kernels
- Add better help for graphics errors
- Add better help for asusctl general errors
- Implement fan-curve dbus API
- Implement partial fan-curve control via CLI tool
+ Set fan curve for profile + fan gpu/cpu
# [4.0.1] - 2021-09-11
### Changed
- Fix asusd-ledmodes.toml
# [4.0.0] - 2021-09-10
### Added
- AniMe:
+ Support 8bit RGB, RGBA, 16bit Greyscalw, RGB, RGBA
+ add `AsusImage` type for slanted-template pixel-perfect images
+ `BREAKING:` plain `Image` with time period is changed and old anime configs break as a result (sorry)
- LED:
+ By popular request LED prev/next cycle is added
+ Add led modes for GX551Q
### BREAKING CHANGES
- Graphics control:
+ graphics control is pulled out of asusd and moved to new package; https://gitlab.com/asus-linux/supergfxctl
- Proflies:
+ profiles now depend on power-profile-daemon plus kernel patches for support of platform_profile
- if your system supports fan-curves you will also require upcoming kernel patches for this
+ profiles are now moved to a new file
+ fan-curves are only partially completed due to this release needing to be done sooner
# [3.7.2] - 2021-08-02
### Added
- Enable multizone support on Strix 513IH
- Add G513QY ledmodes
### Changed
- Fix missing CLI command help for some supported options
- Fix incorrectly selecting profile by name, where the active profile was being copied to the selected profile
- Add `asusd` version back to `asusctl -v` report
- Fix various clippy warnings
# [3.7.1] - 2021-06-11
### Changed
- Refine graphics mode switching:
+ Disallow switching to compute or vfio mode unless existing mode is "Integrated"
# [3.7.0] - 2021-06-06
### Changed
- Set PM to auto for Nvidia always
- Extra info output for gfx dev scan
- Extra info in log for G-Sync to help prevent user confusion around gfx switching
- Add GA503Q led modes
- Added ability to fade in/out gifs and images for anime. This does break anime configs. See manual for details.
- Added task to CtrlLed to set the keyboard LED brightness on wake from suspend
+ requires a kernel patch which will be upstreamed and in fedora rog kernel
- Make gfx change from nvidia to vfio/compute also force-change to integrated _then_
to requested mode
- Fix invalid gfx status when switching from some modes
- Fix copy over of serde skipped config values on config reload
# [3.6.1] - 2021-05-25
### Changed
- Bugfix: write correct fan modes for profiles
- Bugfix: apply created profiles
# [3.6.1] - 2021-05-25
### Changed
- Bugfix for cycling through profiles
# [3.6.0] - 2021-05-24
### Changed
- Add GX550L led modes
- Don't save compute/vfio modes. Option in config for this is removed.
- Store a temporary non-serialised option in config for if compute/vfio is active
for informational purposes only (will not apply on boot)
- Save state for LEDs enabled + sleep animation enabled
- Save state for AnimMe enabled + boot animation enabled
- Add extra config options and dbus methods
- Add power state signals for anime and led
- Refactor to use channels for dbus signal handler send/recv
- Split out profiles independant parts to a rog-profiles crate
- Cleanup dependencies
- Fix some dbus Supported issues
# [3.5.2] - 2021-05-15
### Changed
- Bugfix: prevent the hang on compute/integrated mode change
# [3.5.1] - 2021-04-25
### Changed
+ Anime:
- Fix using multiple configs
# [3.5.0] - 2021-04-25
### Changed
+ Keyboard:
- Split out all aura functionality that isn't dependent on the daemon in to a
new crate `rog-aura` (incomplete)
- Keyboard LED control now includes:
+ Enable/disable LED's while laptop is awake
+ Enable/disable LED animation while laptop is suspended and AC plugged in
- Properly reload the last used keyboard mode on boot
+ Graphics:
- Correctly enable compute mode for nvidia plus no-reboot or logout if switching
from vfio/integrated/compute.
- Add asusd config option to not save compute/vfio mode switch.
+ Anime:
- Enable basic multiple user anime configs (asusd-user must still be restarted)
+ Profiles:
- Enable dbus methods for freq min/max, fan curve, fan preset, CPU turbo enable.
These options will apply to the active profile if no profile name is specified.
# [3.4.1] - 2021-04-11
### Changed
- Fix anime init sequence
# [3.4.0] - 2021-04-11
### Changed
- Revert zbus to 1.9.1
- Use enum to show power states, and catch missing pci path for nvidia.
- Partial user-daemon for anime/per-key done, `asusd-user`. Includes asusd-user systemd unit.
- user-daemon provides dbus emthods to insert anime actions, remove from index, set leds on/off
+ Config file is stored in `~/.config/rog/rog-user.cfg`
- AniMe display parts split out to individual crate in preparation for publishing
on crates.io
# [3.3.0] - 2021-04-3
### Changed
- Add ledmodes for G733QS
- Add ledmodes for GA401Q
- Default to vfio disabled in configuration. Will now hard-error if enabled and
the kernel modules are builtin. To enable vfio switching `"gfx_vfio_enable": false,`
must be changed to `true` in `/etc/asusd/asusd.conf`
# [3.2.4] - 2021-03-24
### Changed
- Ignore vfio-builtin error if switching to integrated
# [3.2.3] - 2021-03-24
### Changed
- Better handling of session tracking
### Added
- List all profile data
- Get active profile name
- Get active profile data
# [3.2.2] - 2021-03-23
### Changed
- Fix brightness control, again, for non-RGB keyboards
# [3.2.1] - 2021-03-21
### Changed
- Fix brightness control
- Large cleanup of code relating to LED controls
# [3.2.0] - 2021-03-21
### Changed
- Refactor keyboard LED handling
- Added --list for profiles (Thanks @aqez)
- Added --remove for profiles (Thanks @aqez)
- Added a graphics mode: vfio. This attaches Nvidia devices to vfio module.
### Broken
- Per-key LED modes, which need thinking about how to go ahead with for future
# [3.1.7] - 2021-03-11
### Changed
- Refactor many parts of daemon
- Switch out session monitoring to logind-zbus
# [3.1.6] - 2021-03-11
### Changed
- Graphics switching will now wait until all users logged out before switching
### Changed
- Further tweaks to gfx switching
- More logging on gfx switching
- Filter bios help according to supported modes
- Prevent gfx mode switching if in dedicated/G-Sync mode
# [3.1.4] - 2021-03-10
### Changed
- Notify through dbus if user changes profile manually
- Better help on CLI, show help only for supported items
- Bugfix to gfx switcher
# [3.1.3] - 2021-03-10
### Changed
- Hotfix: gracefully handle removing modules in use caused by display-manager not
fully shutdown at the time of trying to remove modules. It will now retry every
250ms per module
# [3.1.2] - 2021-03-10
### Changed
- Test and create /etc/X11/xorg.conf.d/ if it doesn't exist
- Hotfix to better report module issues
# [3.1.1] - 2021-03-10
### Changed
- Add missing nvidia module nvidia_uvm to gfx ctrl list
# [3.1.0] - 2021-03-09
### Added
- GU502LU led-modes
### Changed
- Graphics switching is now rebootless, the daemon will now restart the
display-manager to switch modes instead. Caveats are:
+ There is no confirmation from the daemon, the program issuing the command
must confirm the request.
+ systemd only
- Laptops with dedicated Nvidia mode:
+ You still must reboot for the bios to switch modes
+ On boot if dedicated mode is active then asusd will update the required configs
to put display-manager in nvidia mode
# [3.0.0] - 2021-02-22
### Added
- G531GD led modes
# [3.0.0] - 2021-02-14
### Changed
- Write set+apply after each array in multizone
- Remove misc bad logic
- Use same code path as 0x1866 device to configure led support for 0x1854 device
- Remove duplicate code
- Set correct speeds for multizone
- Remove dbus crate in favour of zbus. This removes the external dbus lib requirement.
- Huge internal refactor
- BREAKING CHANGE: Anime code refactor. DBUS method names have changed
- Cleanup fan and cpu control + configs
# [2.2.2] - 2021-01-31
### Changed
- Fix for dedicated gfx capable laptops in integrated mode
- Fix for 0x1854 device
# [2.2.1] - 2021-01-27
### Added
- Add ROG Zephyrus M15 LED config
### Changed
- Bugfixes
- Fix reboot/restartx status for GFX switching
- Update readme
- Change CLI arg tag for fan modes
- Make dracut include the nvidia modules in initramfs
# [2.2.0] - 2021-01-26
### Added
- Dbus command to fetch all supported functions of the laptop. That is, all the
functions that asusd supports for the currently running laptop.
- Bios setting toggles for:
+ Dedicated gfx toggle (support depends on the laptop)
+ Bios boot POST sound toggle
### Changed
- added config option for dedicated gfx mode on laptops with it to enable
switching directly to dedicated using `asusctl graphics -m nvidia`
# [2.1.2] - 2021-01-10
### Changed
- Adjust gfx controller to assume that the graphics driver is loaded if the
mode is set for nvidia/hybrid
# [2.1.1] - 2021-01-09
### Changed
- Updates to dependencies
# [2.1.0] - 2020-10-25
### Added
- Option to turn off AniMe display (@asere)
### Changed
- Change option -k to show current LED bright (@asere)
- Correctly disable GFX control via config
- Panic and exit if config can't be parsed
- Add DBUS method to toggle to next fan/thermal profile
- Add DBUS method to toggle to next/prev Aura mode
# [2.0.5] - 2020-09-29
### Changed
- Bugfixes
# [2.0.4] - 2020-09-24
### Changed
- Better and more verbose error handling and logging in many places.
- Fix timeout for client waiting on reply for graphics switching
# [2.0.2] - 2020-09-21
### Changed
- graphics options via CLI are now a command block:
+ `asusctl graphics`

1505
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[workspace]
members = ["asus-notify", "asus-nb-ctrl", "asus-nb", "ctrl-gfx"]
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-supported", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles"]
[profile.release]
lto = true

359
MANUAL.md Normal file
View File

@@ -0,0 +1,359 @@
# asusctrl manual
`asusd` is a utility for Linux to control many aspects of various ASUS laptops
but can also be used with non-asus laptops with reduced features.
## Programs Available
- `asusd`: The main system daemon. It is autostarted by a udev rule and systemd unit.
- `asusd-user`: The user level daemon. Currently will run an anime sequence, with RGB keyboard sequences soon.
- `asusctl`: The CLI for interacting with the system daemon
- `asus-notify`: A notification daemon with a user systemd unit that can be enabled.
## `asusd`
`asusd` is the main system-level daemon which will control/load/save various settings in a safe way for the user, along with exposing a *safe* dbus interface for these interactions. This section covers only the daemon plus the various configuration file options.
The functionality that `asusd` exposes is:
- anime control
- led keyboard control (aura)
- charge limiting
- bios/efivar control
- power profile switching
- fan curves (if supported, this is auto-detected)
each of these will be detailed in sections.
### AniMe control
Controller for the fancy AniMe matrix display on the lid of some machines. This controller is a work in progress.
#### Config options
If you have an AniMe device a few system-level config options are enabled for you in `/etc/asusd/anime.conf`;
1. `"system": [],`: currently unused, is intended to be a default continuous sequence in future versions
2. `"boot": [],`: a sequence that plays on system boot (when asusd is loaded)
3. `"wake": [],`: a sequence that plays when waking from suspend
4. `"shutdown": [],`: a sequence that plays when shutdown begins
5. `"brightness": <FLOAT>`: global brightness control, where `<FLOAT> is 0.0-1.0
Some default examples are provided but are minimal. The full range of configuration options will be covered in another section of this manual.
### Led keyboard control
The LED controller (e.g, aura) enables setting many of the factory modes available if a laptop supports them. It also enables per-key RGB settings but this is a WIP and will likely be similar to how AniMe sequences can be created.
#### Supported laptops
Models GA401, GA502, GU502 support LED brightness change only (no RGB). However the GA401Q model can actually use three modes; static, breathe, and pulse, plus also use red to control the LED brightness intensity.
All models that have any form of LED mode control need to be enabled via the config file at `/etc/asusd/asusd-ledmodes.toml`. Unfortunately ASUS doesn't provide any easy way to find all the supported modes for all laptops (not even through Armory Crate and its various files, that progrma downloads only the required settings for the laptop it runs on) so each model must be added as needed.
#### Config options
The defaults are located at `/etc/asusd/asusd-ledmodes.toml`, and on `asusd` start it creates `/etc/asusd/aura.conf` whcih stores the per-mode settings. If you edit the defaults file you must remove `/etc/asusd/aura.conf` and restart `asusd.service` with `systemctl restart asusd`.
##### /etc/asusd/asusd-ledmodes.toml
Example:
```toml
[[led_data]]
prod_family = "ROG Zephyrus M15"
board_names = ["GU502LU"]
standard = ["Static", "Breathe", "Strobe", "Pulse"]
multizone = false
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.
5. `per_key`: enable per-key RGB effects. The keyboard must support this or it has no effect.
##### /etc/asusd/aura.conf
This file can be manually edited if desired, but the `asusctl` CLI tool, or dbus methods are the preferred method. Any manual changes to this file mean that the `asusd.service` will need to be restarted, or you need to cycle between modes to force a reload.
### Charge control
Almost all modern ASUS laptops have charging limit control now. This can be controlled in `/etc/asusd/asusd.conf`.
```json
"bat_charge_limit": 80,
```
where the number is a percentage.
### Bios control
Some options that you find in Armory Crate are available under this controller, so far there is:
- POST sound: this is the sound you here on bios boot post
- G-Sync: this controls if the dGPU (Nvidia) is the *only* GPU, making it the main GPU and disabling the iGPU
These options are not written to the config file as they are stored in efivars. The only way to change these is to use the exposed safe dbus methods, or use the `asusctl` CLI tool.
### Profiles
asusctl can support setting a power profile via platform_profile drivers. This requires [power-profiles-daemon](https://gitlab.freedesktop.org/hadess/power-profiles-daemon) v0.10.0 minimum. It also requires the kernel patch for platform_profile support to be applied form [here](https://lkml.org/lkml/2021/8/18/1022) - this patch is merged to 5.15 kernel upstream.
A common use of asusctl is to bind the `fn+f5` (fan) key to `asusctl profile -n` to cycle through the 3 profiles:
1. Balanced
2. Performance
3. Quiet
#### Fan curves
Fan curve support requires a laptop that supports it (this is detected automatically) and the kernel patch from [here](https://lkml.org/lkml/2021/10/23/250) which is accepted for the 5.17 kernel release .
The fan curve format can be of varying formats:
- `30c:0%,40c:5%,50c:10%,60c:20%,70c:35%,80c:55%,90c:65%,100c:65%"`
- `30:0,40:5,50:10,60:20,70:35,80:55,90:65,100:65"`
- `30 0,40 5,50 10,60 20,70 35,80 55,90 65,100 65"`
- `30 0 40 5 50 10 60 20 70 35 80 55 90 65 100 65"`
the order must always be the same "temperature:percentage", lowest from left to rigth being highest.
The config file is located at `/etc/asusd/profile.conf` and is self-descriptive. On first run it is populated with the system EC defaults.
### Support controller
There is one more controller; the support controller. The sole pupose of this controller is to querie all the other controllers for information about their support level for the host laptop. Returns a json string.
## asusd-user
`asusd-user` is a usermode daemon. The intended purpose is to provide a method for users to run there own custom per-key keyboard effects and modes, AniMe sequences, and possibly their own profiles - all without overwriting the *base* system config. As such some parts of the system daemon will migrate to the user daemon over time with the expectation that the Linux system runs both.
As of now only AniMe is active in this with configuration in `~/.config/rog/`. On first run defaults are created that are intended to work as examples.
The main config is `~/.config/rog/rog-user.cfg`
#### Config options: AniMe
`~/.config/rog/rog-user.cfg` contains a setting `"active_anime": "<FILENAME>"` where `<FILENAME>` is the name of the AniMe config to use, located in the same directory and without the file postfix, e.g, `"active_anime": "anime-doom"`
An AniMe config itself is a file with contents:
```json
{
"name": "<FILENAME>",
"anime": []
}
```
`<FILENAME>` is used as a reference internally. `"anime": []` is an array of sequences (WIP).
##### "anime" array options
Each object in the array can be one of:
1. AsusAnimation
2. ImageAnimation
3. Image
4. Pause
##### AsusAnimation
`AsusAnimation` is specifically for running the gif files that Armory Crate comes with. `asusctl` includes all of these in `/usr/share/asusd/anime/asus/`
```json
"AsusAnimation": {
"file": "<FILE_PATH>",
"time": <TIME>,
"brightness": <FLOAT>
}
```
##### AsusImage
Virtually the same as `AsusAnimation` but for png files, typically created in the same "slanted" style using a template (`diagonal-template.png`) as the ASUS gifs for pixel perfection.
```json
"AsusImage": {
"file": "<FILE_PATH>",
"time": <TIME>,
"brightness": <FLOAT>
}
```
##### ImageAnimation
`ImageAnimation` can play *any* gif of any size.
```json
"ImageAnimation": {
"file": "<FILE_PATH>",
"scale": <FLOAT>,
"angle": <FLOAT>,
"translation": [
<FLOAT>,
<FLOAT>
],
"time": <TIME>,
"brightness": <FLOAT>
}
},
```
##### Image
`Image` currently requires 8bit greyscale png. It will be able to use most in future.
```json
{
"Image": {
"file": "<FILE_PATH>",
"scale": <FLOAT>,
"angle": <FLOAT>,
"translation": [
<FLOAT>,
<FLOAT>
],
"time": <TIME>,
"brightness": <FLOAT>
}
},
```
##### Pause
A `Pause` is handy for after an `Image` to hold the `Image` on the AniMe for a period.
```json
{
"Pause": {
"secs": <INT>,
"nanos": <INT>
}
},
```
##### Options for objects
**<FILE_PATH>**
Must be full path: `"/usr/share/asusd/anime/asus/gaming/Controller.gif"` or `/home/luke/Downloads/random.gif`.
**<FLOAT>**
A number from 0.0-1.0.
- `brightness`: If it is brightness it is combined with the system daemon global brightness
- `scale`: 1.0 is the original size with lower number shrinking, larger growing
- `angle`: Rotation angle in radians
- `translation`: Shift the image X -/+, and y -/+
**<TIME>**
Time is the length of time to run the gif for:
```json
"time": {
"Time": {
"secs": 5,
"nanos": 0
}
},
```
A cycle is how many gif loops to run:
```json
"time": {
"Cycles": 2
},
```
`Infinite` means that this gif will never end:
```json
"time": "Infinite",
```
`Fade` allows an image or gif to fade in and out, and remain at max brightness to n time:
```json
"time": {
"Fade": {
"fade_in": {
"secs": 2,
"nanos": 0
},
"show_for": {
"secs": 1,
"nanos": 0
},
"fade_out": {
"secs": 2,
"nanos": 0
}
}
},
```
`show_for` can be `null`, if it is `null` then the `show_for` becomes `gif_time_length - fade_in - fade_out`.
This is period for which the gif or image will be max brightness (as set).
**<INT>**
A plain non-float integer.
## asusctl
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. I can be used in any place a terminal app can be used.
This program will query `asusd` for the `Support` level of the laptop and show or hide options according to this support level.
Most commands are self-explanatory.
### CLI Usage and help
Commands are given by:
```
asusctl <option> <command> <command-options>
```
Help is available through:
```
asusctl --help
asusctl <command> --help
```
Some commands may have subcommands:
```
asusctl <command> <subcommand> --help
```
### Keybinds
To switch to next/previous Aura modes you will need to bind both the aura keys (if available) to one of:
**Next**
```
asusctl led-mode -n
```
**Previous**
```
asusctl led-mode -p
```
To switch Fan/Thermal profiles you need to bind the Fn+F5 key to `asusctl profile -n`.
## User NOTIFICATIONS via dbus
If you have a notifications handler set up, or are using KDE or Gnome then you
can enable the user service to get basic notifications when something changes.
```
systemctl --user enable asus-notify.service
systemctl --user start asus-notify.service
```
# License & Trademarks
Mozilla Public License 2 (MPL-2.0)
---
ASUS and ROG Trademark is either a US registered trademark or trademark of ASUSTeK Computer Inc. in the United States and/or other countries.
Reference to any ASUS products, services, processes, or other information and/or use of ASUS Trademarks does not constitute or imply endorsement, sponsorship, or recommendation thereof by ASUS.
The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops.
---

112
Makefile
View File

@@ -1,36 +1,36 @@
prefix ?= /usr
sysconfdir ?= /etc
VERSION := $(shell grep -Pm1 'version = "(\d.\d.\d)"' daemon/Cargo.toml | cut -d'"' -f2)
INSTALL = install
INSTALL_PROGRAM = ${INSTALL} -D -m 0755
INSTALL_DATA = ${INSTALL} -D -m 0644
prefix = /usr
exec_prefix = $(prefix)
bindir = $(exec_prefix)/bin
libdir = $(exec_prefix)/lib
includedir = $(prefix)/include
datarootdir = $(prefix)/share
datadir = $(datarootdir)
libdir = $(exec_prefix)/lib
zshcpl = $(datarootdir)/zsh/site-functions
SRC = Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs')
BIN_C := asusctl
BIN_D := asusd
BIN_U := asusd-user
BIN_N := asus-notify
LEDCFG := asusd-ledmodes.toml
.PHONY: all clean distclean install uninstall update
BIN_C=asusctl
BIN_D=asusd
BIN_N=asus-notify
LEDCFG=asusd-ledmodes.toml
X11CFG=90-nvidia-screen-G05.conf
PMRULES=90-asusd-nvidia-pm.rules
VERSION:=$(shell grep -Pm1 'version = "(\d.\d.\d)"' asus-nb-ctrl/Cargo.toml | cut -d'"' -f2)
SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs')
DEBUG ?= 0
ifeq ($(DEBUG),0)
ARGS += "--release"
ARGS += --release
TARGET = release
endif
VENDORED ?= 0
ifeq ($(VENDORED),1)
ARGS += "--frozen"
ARGS += --frozen
endif
all: target/release/$(BIN_D)
all: build
clean:
cargo clean
@@ -38,34 +38,56 @@ clean:
distclean:
rm -rf .cargo vendor vendor.tar.xz
install: all
install -D -m 0755 "target/release/$(BIN_C)" "$(DESTDIR)$(bindir)/$(BIN_C)"
install -D -m 0755 "target/release/$(BIN_D)" "$(DESTDIR)$(bindir)/$(BIN_D)"
install -D -m 0755 "target/release/$(BIN_N)" "$(DESTDIR)$(bindir)/$(BIN_N)"
install -D -m 0644 "data/$(PMRULES)" "$(DESTDIR)/lib/udev/rules.d/$(PMRULES)"
install -D -m 0644 "data/$(BIN_D).rules" "$(DESTDIR)/lib/udev/rules.d/99-$(BIN_D).rules"
install -D -m 0644 "data/$(PMRULES).rules" "$(DESTDIR)/lib/udev/rules.d/$(PMRULES).rules"
install -D -m 0644 "data/$(LEDCFG)" "$(DESTDIR)$(sysconfdir)/asusd/$(LEDCFG)"
install -D -m 0644 "data/$(BIN_D).conf" "$(DESTDIR)$(sysconfdir)/dbus-1/system.d/$(BIN_D).conf"
install -D -m 0644 "data/$(X11CFG)" "$(DESTDIR)$(sysconfdir)/X11/xorg.conf.d/$(X11CFG)"
install -D -m 0644 "data/$(BIN_D).service" "$(DESTDIR)/lib/systemd/system/$(BIN_D).service"
install -D -m 0644 "data/$(BIN_N).service" "$(DESTDIR)/lib/systemd/user/$(BIN_N).service"
install -D -m 0644 "data/icons/asus_notif_yellow.png" "$(DESTDIR)/usr/share/icons/hicolor/512x512/apps/asus_notif_yellow.png"
install -D -m 0644 "data/icons/asus_notif_green.png" "$(DESTDIR)/usr/share/icons/hicolor/512x512/apps/asus_notif_green.png"
install -D -m 0644 "data/icons/asus_notif_red.png" "$(DESTDIR)/usr/share/icons/hicolor/512x512/apps/asus_notif_red.png"
install:
$(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)"
$(INSTALL_PROGRAM) "./target/release/$(BIN_N)" "$(DESTDIR)$(bindir)/$(BIN_N)"
$(INSTALL_DATA) "./data/$(BIN_D).rules" "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
$(INSTALL_DATA) "./data/$(LEDCFG)" "$(DESTDIR)/etc/asusd/$(LEDCFG)"
$(INSTALL_DATA) "./data/$(BIN_D).conf" "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
$(INSTALL_DATA) "./data/$(BIN_D).service" "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
$(INSTALL_DATA) "./data/$(BIN_N).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
$(INSTALL_DATA) "./data/$(BIN_U).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_U).service"
$(INSTALL_DATA) "./data/icons/asus_notif_yellow.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
$(INSTALL_DATA) "./data/icons/asus_notif_green.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
$(INSTALL_DATA) "./data/icons/asus_notif_red.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
$(INSTALL_DATA) "./data/icons/scalable/gpu-compute.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-compute.svg"
$(INSTALL_DATA) "./data/icons/scalable/gpu-hybrid.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-hybrid.svg"
$(INSTALL_DATA) "./data/icons/scalable/gpu-integrated.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-integrated.svg"
$(INSTALL_DATA) "./data/icons/scalable/gpu-nvidia.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-nvidia.svg"
$(INSTALL_DATA) "./data/icons/scalable/gpu-vfio.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-vfio.svg"
$(INSTALL_DATA) "./data/icons/scalable/notification-reboot.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/notification-reboot.svg"
$(INSTALL_DATA) "./data/_asusctl" "$(DESTDIR)$(zshcpl)/_asusctl"
$(INSTALL_DATA) "./data/completions/asusctl.fish" "$(DESTDIR)$(datarootdir)/fish/vendor_completions.d/asusctl.fish"
cd rog-anime/data && find "./anime" -type f -exec install -Dm 755 "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
uninstall:
rm -f "$(DESTDIR)$(bindir)/$(BIN_C)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_D)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_N)"
rm -f "$(DESTDIR)/lib/udev/rules.d/$(PMRULES)"
rm -f "$(DESTDIR)/lib/udev/rules.d/99-$(BIN_D).rules"
rm -f "$(DESTDIR)/lib/udev/rules.d/$(PMRULES).rules"
rm -f "$(DESTDIR)$(sysconfdir)/dbus-1/system.d/$(BIN_D).conf"
rm -f "$(DESTDIR)$(sysconfdir)/X11/xorg.conf.d/$(X11CFG)"
rm -f "$(DESTDIR)/lib/systemd/system/$(BIN_D).service"
rm -r "$(DESTDIR)/lib/systemd/user/$(BIN_N).service"
rm -r "$(DESTDIR)/usr/share/icons/hicolor/512x512/apps/asus_notif_*"
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
rm -f "$(DESTDIR)/etc/asusd/$(LEDCFG)"
rm -f "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
rm -f "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
rm -r "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-compute.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-hybrid.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-integrated.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-nvidia.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-vfio.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/notification-reboot.svg"
rm -f "$(DESTDIR)$(zshcpl)/_asusctl"
rm -f "$(DESTDIR)$(datarootdir)/fish/vendor_completions.d/asusctl.fish"
rm -rf "$(DESTDIR)$(datarootdir)/asusd"
update:
cargo update
@@ -76,12 +98,14 @@ vendor:
echo 'directory = "vendor"' >> .cargo/config
mv .cargo/config ./cargo-config
rm -rf .cargo
tar pcfJ vendor_asus-nb-ctrl_$(VERSION).tar.xz vendor
tar pcfJ vendor_asusctl_$(VERSION).tar.xz vendor
rm -rf vendor
target/release/$(BIN_D): $(SRC)
build:
ifeq ($(VENDORED),1)
@echo "version = $(VERSION)"
tar pxf vendor_asus-nb-ctrl_$(VERSION).tar.xz
tar pxf vendor_asusctl_$(VERSION).tar.xz
endif
cargo build $(ARGS)
.PHONY: all clean distclean install uninstall update build

245
README.md
View File

@@ -1,29 +1,33 @@
# ASUS NB Ctrl
# `asusctl` for ASUS ROG
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/donate/?hosted_button_id=4V2DEPS7K6APC) - [Asus Linux Website](https://asus-linux.org/)
`asusd` is a utility for Linux to control many aspects of various ASUS laptops
but can also be used with non-asus laptops with reduced features.
**NOTICE:**
## Kernel support
This program requires the kernel patch [here](https://www.spinics.net/lists/linux-input/msg68977.html) to be applied.
Alternatively you may use the dkms module for 'hid-asus-rog` from one of the
repositories [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/).
**The minimum supported kernel version is 5.15**
The patch enables the following in kernel:
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.
- All hotkeys (FN+Key combos)
- Control of keyboard brightness using FN+Key combos (not RGB)
- FN+F5 (fan) to toggle fan modes
## Goals
You will not get RGB control in kernel (yet), and `asusd` + `asusctl` is required
to change modes and RGB settings.
1. To provide an interface for rootless control of some system functions most users wish to control such as fan speeds, keyboard LEDs, graphics modes.
2. Enable third-party apps to use the above with dbus methods
3. To make the above as easy as possible for new users
4. Respect the users resources: be small, light, and fast
Many other patches for these laptops, AMD and Intel based, are working their way
in to the kernel.
Point 3 means that the list of supported distros is very narrow - fedora is explicitly
supported, while Ubuntu and openSUSE are level-2 support. All other distros are *not*
supported (while asusd might still run fine on them). For best support use fedora 32+ Workstation.
Point 4? asusd currently uses a tiny fraction of cpu time, and less than 1Mb of ram, the way
a system-level daemon should.
## Discord
[Discord server link](https://discord.gg/PVyFzWj)
[Discord server link](https://discord.gg/4ZKGd7Un5t)
## SUPPORTED LAPTOPS
@@ -44,100 +48,37 @@ will probably suffer another rename once it becomes generic enough to do so.
- [X] User notifications daemon
- [X] Setting/modifying built-in LED modes
- [X] Per-key LED setting
- [X] Fancy LED modes (See examples)
- [X] Fancy LED modes (See examples) (currently being reworked)
- [X] Saving settings for reload
- [X] Logging - required for journalctl
- [X] AniMatrix display on G14 models that include it
- [X] Set battery charge limit (with kernel supporting this)
- [X] Fancy fan control on G14 + G15 thanks to @Yarn1
- [X] Graphics mode switching between iGPU, dGPU, and On-Demand
# FUNCTIONS
## Graphics switching
A new feature has been added to enable switching graphics modes. This can be disabled
in the config with `"manage_gfx": false,`. Please be aware it is a work in progress.
The CLI option for this does not require root until it asks for it, and provides
instructions.
This switcher conflicts with other gpu switchers like optimus-manager, suse-prime
or ubuntu-prime, system76-power, and bbswitch. If you have issues with `asusd`
always defaulting to `integrated` mode on boot then you will need to check for
stray configs blocking nvidia modules from loading in:
- `/etc/modprobe.d/`
- `/usr/lib/modprope.d/`
### Power management udev rule
If you have installed the Nvidia driver manually you will require the
`data/90-asusd-nvidia-pm.rules` udev rule to be installed in `/etc/udev/rules.d/`.
The above seems to also apply to Arch in general as it leaves a lot of things up
to the user.
### fedora and openSUSE
You *may* need a file `/etc/dracut.conf.d/90-nvidia-dracut-G05.conf` installed
to stop dracut including the nvidia modules in the ramdisk.
```
# filename /etc/dracut.conf.d/90-nvidia-dracut-G05.conf
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
# the ramdisk on updates, and to ensure the power-management udev rules run
# on module load
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
```
and run `dracut -f` after creating it.
## KEYBOARD BACKLIGHT MODES
Models GA401, GA502, GU502 support LED brightness change only (no RGB).
If you model isn't getting the correct led modes, you can edit the file
`/etc/asusd/asusd-ledmodes.toml`, the LED Mode numbers are as follows:
```
0 STATIC
1 BREATHING
2 STROBE
3 RAINBOW
4 STAR
5 RAIN
6 HIGHLIGHT
7 LASER
8 RIPPLE
10 PULSE
11 COMET
12 FLASH
13 MULTISTATIC
255 PER_KEY
```
use `cat /sys/class/dmi/id/product_name` to get details about your laptop.
- [X] Fan curve control on G14 + G15. Requires kernel patch (should reach 5.15 kernel)
- [X] Toggle bios setting for boot/POST sound
- [X] Toggle bios setting for "dedicated gfx" mode on supported laptops (g-sync)
# BUILDING
Requirements are:
Requirements are rust >= 1.57 installed from rustup.io if the distro provided version is too old, and `make`.
- `rustc` + `cargo` + `make`
- `libusb-1.0-0-dev`
- `libdbus-1-dev`
- `llvm`
- `libclang-dev`
- `libudev-dev`
**Ubuntu (unsuported):**
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
make
sudo make install
## Installing
- Fedora copr = https://copr.fedorainfracloud.org/coprs/lukenukem/asus-linux/
- openSUSE = https://download.opensuse.org/repositories/home:/luke_nukem:/asus/
- Ubuntu = not supported due to packaging woes, but you can build and install on your own.
Packaging and auto-builds are available [here](https://build.opensuse.org/package/show/home:luke_nukem:asus/asus-nb-ctrl)
Download repositories are available [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/)
---
Run `make` then `sudo make install` then reboot.
=======
The default init method is to use the udev rule, this ensures that the service is
started when the device is initialised and ready.
@@ -150,116 +91,26 @@ $ systemctl daemon-reload && systemctl restart asusd
You may also need to activate the service for debian install. If running Pop!_OS, I suggest disabling `system76-power` gnome-shell extension and systemd service.
If you would like to run this daemon on another non-ASUS laptop you can. You'll
have all features available except the LED and AniMe control (further controllers
can be added on request). You will need to install the alternative service from
`data/asusd-alt.service`.
## Uninstalling
Run `sudo make uninstall` in the source repo, and remove `/etc/asusd/`.
## Updating
If there has been a config file format change your config will be overwritten. This will
become less of an issue once the feature set is nailed down. Work is happening to enable
parsing of older configs and transferring settings to new.
# USAGE
**NOTE! Fan mode toggling requires a newer kernel**. I'm unsure when the patches
required for it got merged - I've tested with the 5.6.6 kernel and above only.
To see if the fan-mode changed cat either:
- `cat /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy` or
- `cat /sys/devices/platform/asus-nb-wmi/fan_boost_mode`
The numbers are 0 = Normal/Balanced, 1 = Boost, 2 = Silent.
Running the program as a daemon manually will require root. Standard (non-daemon)
mode expects to be communicating with the daemon mode over dbus.
Commands are given by:
```
asusctl <option> <command> <command-options>
```
Help is available through:
```
asusctl --help
asusctl <command> --help
```
Some commands may have subcommands:
```
asusctl <command> <subcommand> --help
```
## Daemon mode
If the daemon service is enabled then on boot the following will be reloaded from save:
- LED brightness
- Last used built-in mode
- fan-boost/thermal mode
- battery charging limit
The daemon also saves the settings per mode as the keyboard does not do this
itself - this means cycling through modes with the Aura keys will use the
settings that were used via CLI.
Daemon mode creates a config file at `/etc/asusd/asusd.conf` which you can edit a
little of. Most parts will be byte arrays, but you can adjust things like
`mode_performance`.
## User NOTIFICATIONS via dbus
If you have a notifications handler set up, or are using KDE or Gnome then you
can enable the user service to get basic notifications when something changes.
```
systemctl --user enable asus-notify.service
systemctl --user start asus-notify.service
```
# OTHER
## DBUS Input
See [README_DBUS.md](./README_DBUS.md).
## AniMe input
You will want to look at what MeuMeu has done with [https://github.com/Meumeu/ZephyrusBling/](https://github.com/Meumeu/ZephyrusBling/)
## Supporting more laptops
Please file a support request.
## Notes:
- If charge limit or fan modes are not working, then you may require a kernel newer than 5.6.10.
- AniMe device check is performed on start, if your device has one it will be detected.
- GA14/GA401 and GA15/GA502/GU502, You will need kernel [patches](https://lab.retarded.farm/zappel/asus-rog-zephyrus-g14/-/tree/master/kernel_patches), these are on their way to the kernel upstream.
- On fedora manually installed Nvidia driver requires a dracut config as follows:
```
# filename/etc/dracut.conf.d/90-nvidia-dracut-G05.conf
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
# the ramdisk on updates, and to ensure the power-management udev rules run
# on module load
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
```
# License
# License & Trademarks
Mozilla Public License 2 (MPL-2.0)
# Credits
---
- [flukejones](https://github.com/flukejones/), project maintainer.
- [tuxuser](https://github.com/tuxuser/)
- [aspann](https://github.com/aspann)
- [meumeu](https://github.com/Meumeu)
- Anyone missed? Please contact me
ASUS and ROG Trademark is either a US registered trademark or trademark of ASUSTeK Computer Inc. in the United States and/or other countries.
Reference to any ASUS products, services, processes, or other information and/or use of ASUS Trademarks does not constitute or imply endorsement, sponsorship, or recommendation thereof by ASUS.
The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops.
---

View File

@@ -1,109 +0,0 @@
# DBUS Guide
**WARNING: In progress updates**
Interface name = org.asuslinux.Daemon
Paths:
- `/org/asuslinux/Gfx`
+ `SetVendor` (string)
+ `NotifyVendor` (recv vendor label string)
- `/org/asuslinux/Led`
+ `LedMode` (AuraMode as json)
+ `LedModes` (array[AuraMode] as json)
+ `SetLedMode` (AuraMode -> json)
+ `NotifyLed` (recv json data)
- `/org/asuslinux/Anime`
+ `SetAnime` (byte array data)
- `/org/asuslinux/Charge`
+ `Limit` (u8)
+ `SetLimit` (u8)
+ `NotifyCharge` (recv i8)
- `/org/asuslinux/Profile`
+ `Profile` (recv current profile data as json string)
+ `Profiles` (recv profiles data as json string (map))
+ `SetProfile` (event -> json)
+ `NotifyProfile` (recv current profile name)
All `Notify*` methods are signals.
### SetLed
This method expects a string of JSON as input. The JSON is of format such:
```
{
"Static": {
"colour": [ 255, 0, 0]
}
}
```
The possible contents of a mode are:
- `"colour": [u8, u8, u8],`
- `"speed": <String>,` <Low, Med, High>
- `"direction": <String>,` <Up, Down, Left, Right>
Modes may or may not be available for a specific laptop (TODO: dbus getter for
supported modes). Modes are:
- `"Static": { "colour": <colour> },`
- `"Pulse": { "colour": <colour> },`
- `"Comet": { "colour": <colour> },`
- `"Flash": { "colour": <colour> },`
- `"Strobe": { "speed": <speed> },`
- `"Rain": { "speed": <speed> },`
- `"Laser": { "colour": <colour>, "speed": <speed> },`
- `"Ripple": { "colour": <colour>, "speed": <speed> },`
- `"Highlight": { "colour": <colour>, "speed": <speed> },`
- `"Rainbow": { "direction": <direction>, "speed": <speed> },`
- `"Breathe": { "colour": <colour>, "colour2": <colour>, "speed": <speed> },`
- `"Star": { "colour": <colour>, "colour2": <colour>, "speed": <speed> },`
- `"MultiStatic": { "colour1": <colour>, "colour2": <colour>, , "colour3": <colour>, "colour4": <colour> },`
Additionally to the above there is `"RGB": [[u8; 64]; 11]` which is for per-key
setting of LED's but this requires some refactoring to make it easily useable over
dbus.
Lastly, there is `"LedBrightness": <u8>` which accepts 0-3 for off, low, med, high.
### SetFanMode
Accepts an integer from the following:
- `0`: Normal
- `1`: Boost mode
- `2`: Silent mode
## dbus-send examples OUTDATED
```
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetKeyBacklight string:'{"Static": {"colour": [ 80, 0, 40]}}'
```
```
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetKeyBacklight string:'{"Star":{"colour":[0,255,255],"colour2":[0,0,0],"speed":"Med"}}'
```
**Note:** setting colour2 to `[0,0,255]` activates random star colour. Colour2 has no effect on the
mode otherwise.
```
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetKeyBacklight string:'{"Star":{"colour":[0,255,255],"colour2":[0,0,255],"speed":"Med"}}'
```
```
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetKeyBacklight string:'{"LedBrightness":3}'
```
```
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetFanMode byte:'2'
```
Monitoring dbus while sending commands via `rog-core` will give you the json structure if you are otherwise unsure, e.g: `dbus-monitor --system |grep -A2 asuslinux`.
## Getting an introspection .xml
```
dbus-send --system --print-reply --dest=org.asuslinux.Daemon /org/asuslinux/Charge org.freedesktop.DBus.Introspectable.Introspect > xml/asusd-charge.xml
```

View File

@@ -1,7 +0,0 @@
# TODO
- There is lots of code duplication. This should be turned in to macros (dbus stuff etc)
- Add a little more information to profile notifications such as freq min/max, fan curves
- Finish splitting out controllers to own crates
- Finish move to zbus in client when zbus has client signal watch
- Consider a rename again because the project is getting a lot less ASUS centric

1016
asus-nb-ctrl/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,166 +0,0 @@
use asus_nb::aura_modes::AuraModes;
use log::{error, warn};
use rog_fan_curve::Curve;
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
#[derive(Default, Deserialize, Serialize)]
pub struct Config {
pub gfx_managed: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
// TODO: remove power_profile
pub power_profile: u8,
pub bat_charge_limit: u8,
pub kbd_led_brightness: u8,
pub kbd_backlight_mode: u8,
pub kbd_backlight_modes: Vec<AuraModes>,
pub power_profiles: BTreeMap<String, Profile>,
}
impl Config {
/// `load` will attempt to read the config, but if it is not found it
/// will create a new default config and write that out.
pub fn load(mut self, supported_led_modes: &[u8]) -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&CONFIG_PATH)
.unwrap(); // okay to cause panic here
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
self = Config::create_default(&mut file, &supported_led_modes);
} else {
self = serde_json::from_str(&buf).unwrap_or_else(|_| {
warn!(
"Could not deserialise {}. Overwriting with default",
CONFIG_PATH
);
Config::create_default(&mut file, &supported_led_modes)
});
}
}
self
}
fn create_default(file: &mut File, supported_led_modes: &[u8]) -> Self {
// create a default config here
let mut config = Config::default();
config.gfx_managed = true;
config.bat_charge_limit = 100;
config.kbd_backlight_mode = 0;
config.kbd_led_brightness = 1;
for n in supported_led_modes {
config.kbd_backlight_modes.push(AuraModes::from(*n))
}
let profile = Profile::default();
config.power_profiles.insert("normal".into(), profile);
let mut profile = Profile::default();
profile.fan_preset = 1;
config.power_profiles.insert("boost".into(), profile);
let mut profile = Profile::default();
profile.fan_preset = 2;
config.power_profiles.insert("silent".into(), profile);
config.toggle_profiles.push("normal".into());
config.toggle_profiles.push("boost".into());
config.toggle_profiles.push("silent".into());
config.active_profile = "normal".into();
// Should be okay to unwrap this as is since it is a Default
let json = serde_json::to_string_pretty(&config).unwrap();
file.write_all(json.as_bytes())
.unwrap_or_else(|_| panic!("Could not write {}", CONFIG_PATH));
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", CONFIG_PATH);
} else {
let x: Config = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", CONFIG_PATH));
*self = x;
}
}
}
pub fn read_new() -> Result<Config, Box<dyn std::error::Error>> {
let mut file = OpenOptions::new()
.read(true)
.open(&CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
let mut buf = String::new();
file.read_to_string(&mut buf)?;
let x: Config = serde_json::from_str(&buf)?;
Ok(x)
}
pub fn write(&self) {
let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
pub fn set_mode_data(&mut self, mode: AuraModes) {
let byte: u8 = (&mode).into();
for (index, n) in self.kbd_backlight_modes.iter().enumerate() {
if byte == u8::from(n) {
// Consume it, OMNOMNOMNOM
self.kbd_backlight_modes[index] = mode;
break;
}
}
}
pub fn get_led_mode_data(&self, num: u8) -> Option<&AuraModes> {
for mode in &self.kbd_backlight_modes {
if u8::from(mode) == num {
return Some(mode);
}
}
None
}
}
#[derive(Deserialize, Serialize)]
pub struct Profile {
pub min_percentage: u8,
pub max_percentage: u8,
pub turbo: bool,
pub fan_preset: u8,
pub fan_curve: Option<Curve>,
}
#[deprecated]
pub type CPUSettings = Profile;
impl Default for Profile {
fn default() -> Self {
Profile {
min_percentage: 0,
max_percentage: 100,
turbo: false,
fan_preset: 0,
fan_curve: None,
}
}
}

View File

@@ -1,205 +0,0 @@
const INIT_STR: &str = "ASUS Tech.Inc.";
const PACKET_SIZE: usize = 640;
// Only these two packets must be 17 bytes
const DEV_PAGE: u8 = 0x5e;
// These bytes are in [1] position of the array
const WRITE: u8 = 0xc0;
const INIT: u8 = 0xc2;
const APPLY: u8 = 0xc3;
const SET: u8 = 0xc4;
use asus_nb::error::AuraError;
use log::{error, info, warn};
use rusb::{Device, DeviceHandle};
use std::convert::TryInto;
use std::error::Error;
use std::time::Duration;
use zbus::dbus_interface;
#[allow(dead_code)]
#[derive(Debug)]
pub enum AnimatrixCommand {
Apply,
Set,
WriteImage(Vec<Vec<u8>>),
//ReloadLast,
}
pub struct CtrlAnimeDisplay {
handle: DeviceHandle<rusb::GlobalContext>,
initialised: bool,
}
//AnimatrixWrite
pub trait Dbus {
fn set_anime(&mut self, input: Vec<Vec<u8>>);
}
impl crate::ZbusAdd for CtrlAnimeDisplay {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/Anime".try_into().unwrap(), self)
.unwrap();
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl Dbus for CtrlAnimeDisplay {
fn set_anime(&mut self, input: Vec<Vec<u8>>) {
self.do_command(AnimatrixCommand::WriteImage(input))
.unwrap_or_else(|err| warn!("{}", err));
}
}
impl CtrlAnimeDisplay {
#[inline]
pub fn new() -> Result<CtrlAnimeDisplay, Box<dyn Error>> {
// We don't expect this ID to ever change
let device = CtrlAnimeDisplay::get_device(0x0b05, 0x193b)?;
let mut device = device.open()?;
device.reset()?;
device.set_auto_detach_kernel_driver(true).map_err(|err| {
error!("Auto-detach kernel driver failed: {}", err);
err
})?;
device.claim_interface(0).map_err(|err| {
error!("Could not claim device interface: {}", err);
err
})?;
info!("Device has an AniMe Matrix display");
Ok(CtrlAnimeDisplay {
handle: device,
initialised: false,
})
}
#[inline]
fn get_device(vendor: u16, product: u16) -> Result<Device<rusb::GlobalContext>, rusb::Error> {
for device in rusb::devices()?.iter() {
let device_desc = device.device_descriptor()?;
if device_desc.vendor_id() == vendor && device_desc.product_id() == product {
return Ok(device);
}
}
Err(rusb::Error::NoDevice)
}
pub fn do_command(&mut self, command: AnimatrixCommand) -> Result<(), AuraError> {
if !self.initialised {
self.do_initialization()?
}
match command {
AnimatrixCommand::WriteImage(effect) => self.write_image(effect)?,
AnimatrixCommand::Set => self.do_set()?,
AnimatrixCommand::Apply => self.do_apply()?,
//AnimatrixCommand::ReloadLast => self.reload_last_builtin(&config).await?,
}
Ok(())
}
/// Should only be used if the bytes you are writing are verified correct
#[inline]
fn write_bytes(&self, message: &[u8]) -> Result<(), AuraError> {
match self.handle.write_control(
0x21, // request_type
0x09, // request
0x35e, // value
0x00, // index
message,
Duration::from_millis(200),
) {
Ok(_) => {}
Err(err) => match err {
rusb::Error::Timeout => {}
_ => error!("Failed to write to led interrupt: {}", err),
},
}
Ok(())
}
/// Write an Animatrix image
///
/// The expected input here is *two* Vectors, 640 bytes in length. The two vectors
/// are each one half of the full image write.
///
/// After each write a flush is written, it is assumed that this tells the device to
/// go ahead and display the written bytes
///
/// # Note:
/// The vectors are expected to contain the full sequence of bytes as follows
///
/// - Write pane 1: 0x5e 0xc0 0x02 0x01 0x00 0x73 0x02 .. <led brightness>
/// - Write pane 2: 0x5e 0xc0 0x02 0x74 0x02 0x73 0x02 .. <led brightness>
///
/// Where led brightness is 0..255, low to high
#[inline]
fn write_image(&mut self, image: Vec<Vec<u8>>) -> Result<(), AuraError> {
for row in image.iter() {
self.write_bytes(row)?;
}
self.do_flush()?;
Ok(())
}
#[inline]
fn do_initialization(&mut self) -> Result<(), AuraError> {
let mut init = [0; PACKET_SIZE];
init[0] = DEV_PAGE; // This is the USB page we're using throughout
for (idx, byte) in INIT_STR.as_bytes().iter().enumerate() {
init[idx + 1] = *byte
}
self.write_bytes(&init)?;
// clear the init array and write other init message
for ch in init.iter_mut() {
*ch = 0;
}
init[0] = DEV_PAGE; // write it to be sure?
init[1] = INIT;
self.write_bytes(&init)?;
self.initialised = true;
Ok(())
}
#[inline]
fn do_flush(&mut self) -> Result<(), AuraError> {
let mut flush = [0; PACKET_SIZE];
flush[0] = DEV_PAGE;
flush[1] = WRITE;
flush[2] = 0x03;
self.write_bytes(&flush)?;
Ok(())
}
#[inline]
fn do_set(&mut self) -> Result<(), AuraError> {
let mut flush = [0; PACKET_SIZE];
flush[0] = DEV_PAGE;
flush[1] = SET;
flush[2] = 0x01;
flush[3] = 0x80;
self.write_bytes(&flush)?;
Ok(())
}
#[inline]
fn do_apply(&mut self) -> Result<(), AuraError> {
let mut flush = [0; PACKET_SIZE];
flush[0] = DEV_PAGE;
flush[1] = APPLY;
flush[2] = 0x01;
flush[3] = 0x80;
self.write_bytes(&flush)?;
Ok(())
}
}

View File

@@ -1,103 +0,0 @@
use crate::{config::Config, error::RogError};
//use crate::dbus::DbusEvents;
use log::{info, warn};
use std::convert::TryInto;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::dbus_interface;
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
pub struct CtrlCharge {
path: &'static str,
config: Arc<Mutex<Config>>,
}
trait Dbus {
fn set_limit(&mut self, charge: u8);
fn limit(&self) -> i8;
fn notify_charge(&self, limit: u8) -> zbus::Result<()>;
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl Dbus for CtrlCharge {
fn set_limit(&mut self, limit: u8) {
if let Ok(mut config) = self.config.try_lock() {
self.set(limit, &mut config).unwrap();
self.notify_charge(limit).unwrap();
}
}
fn limit(&self) -> i8 {
if let Ok(config) = self.config.try_lock() {
return config.bat_charge_limit as i8;
}
-1
}
#[dbus_interface(signal)]
fn notify_charge(&self, limit: u8) -> zbus::Result<()>;
}
impl crate::ZbusAdd for CtrlCharge {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/Charge".try_into().unwrap(), self)
.unwrap();
}
}
impl crate::Reloadable for CtrlCharge {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(mut config) = self.config.try_lock() {
config.read();
info!("Reloaded battery charge limit");
self.set(config.bat_charge_limit, &mut config)?;
}
Ok(())
}
}
impl CtrlCharge {
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
let path = CtrlCharge::get_battery_path()?;
info!("Device has battery charge threshold control");
Ok(CtrlCharge { path, config })
}
fn get_battery_path() -> Result<&'static str, RogError> {
if Path::new(BAT_CHARGE_PATH).exists() {
Ok(BAT_CHARGE_PATH)
} else {
Err(RogError::MissingFunction(
"Charge control not available".into(),
))
}
}
pub(super) fn set(&self, limit: u8, config: &mut Config) -> Result<(), RogError> {
if limit < 20 || limit > 100 {
warn!(
"Unable to set battery charge limit, must be between 20-100: requested {}",
limit
);
}
let mut file = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
file.write_all(limit.to_string().as_bytes())
.map_err(|err| RogError::Write(self.path.into(), err))?;
info!("Battery charge limit: {}", limit);
config.read();
config.bat_charge_limit = limit;
config.write();
Ok(())
}
}

View File

@@ -1,381 +0,0 @@
use crate::config::{Config, Profile};
use asus_nb::profile::ProfileEvent;
use log::{info, warn};
use std::convert::TryInto;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::dbus_interface;
static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy";
static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode";
static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost";
pub struct CtrlFanAndCPU {
pub path: &'static str,
config: Arc<Mutex<Config>>,
}
pub struct DbusFanAndCpu {
inner: Arc<Mutex<CtrlFanAndCPU>>,
}
impl DbusFanAndCpu {
pub fn new(inner: Arc<Mutex<CtrlFanAndCPU>>) -> Self {
Self { inner }
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl DbusFanAndCpu {
fn set_profile(&self, profile: String) {
if let Ok(event) = serde_json::from_str(&profile) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
cfg.read();
ctrl.handle_profile_event(&event, &mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
self.notify_profile(&cfg.active_profile)
.unwrap_or_else(|_| ());
}
}
}
}
fn profile(&mut self) -> String {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
if let Ok(json) = serde_json::to_string(profile) {
return json;
}
}
}
}
"Failed".to_string()
}
fn profiles(&mut self) -> String {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
if let Ok(json) = serde_json::to_string(&cfg.power_profiles) {
return json;
}
}
}
"Failed".to_string()
}
#[dbus_interface(signal)]
fn notify_profile(&self, profile: &str) -> zbus::Result<()>;
}
impl crate::ZbusAdd for DbusFanAndCpu {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/Profile".try_into().unwrap(), self)
.unwrap();
}
}
impl crate::Reloadable for CtrlFanAndCPU {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(mut config) = self.config.clone().try_lock() {
let mut file = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
file.write_all(format!("{}\n", config.power_profile).as_bytes())
.map_err(|err| RogError::Write(self.path.into(), err))?;
let profile = config.active_profile.clone();
self.set(&profile, &mut config)?;
info!(
"Reloaded fan mode: {:?}",
FanLevel::from(config.power_profile)
);
}
Ok(())
}
}
impl crate::CtrlTask for CtrlFanAndCPU {
fn do_task(&mut self) -> Result<(), RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)
.map_err(|err| RogError::Read(self.path.into(), err))?;
if let Some(num) = char::from(buf[0]).to_digit(10) {
if let Ok(mut config) = self.config.clone().try_lock() {
if config.power_profile != num as u8 {
config.read();
let mut i = config
.toggle_profiles
.iter()
.position(|x| x == &config.active_profile)
.map(|i| i + 1)
.unwrap_or(0);
if i >= config.toggle_profiles.len() {
i = 0;
}
let new_profile = config
.toggle_profiles
.get(i)
.unwrap_or(&config.active_profile)
.clone();
self.set(&new_profile, &mut config)?;
info!("Profile was changed: {}", &new_profile);
}
}
return Ok(());
}
Err(RogError::DoTask("Fan-level could not be parsed".into()))
}
}
impl CtrlFanAndCPU {
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
let path = CtrlFanAndCPU::get_fan_path()?;
info!("Device has thermal throttle control");
Ok(CtrlFanAndCPU { path, config })
}
fn get_fan_path() -> Result<&'static str, RogError> {
if Path::new(FAN_TYPE_1_PATH).exists() {
Ok(FAN_TYPE_1_PATH)
} else if Path::new(FAN_TYPE_2_PATH).exists() {
Ok(FAN_TYPE_2_PATH)
} else {
Err(RogError::MissingFunction(
"Fan mode not available, you may require a v5.8 series kernel or newer".into(),
))
}
}
pub(super) fn do_update(&mut self, config: &mut Config) -> Result<(), RogError> {
config.read();
let mut i = config
.toggle_profiles
.iter()
.position(|x| x == &config.active_profile)
.map(|i| i + 1)
.unwrap_or(0);
if i >= config.toggle_profiles.len() {
i = 0;
}
let new_profile = config
.toggle_profiles
.get(i)
.unwrap_or(&config.active_profile)
.clone();
self.set(&new_profile, config)?;
info!("Profile was changed: {}", &new_profile);
Ok(())
}
pub(super) fn set_fan_mode(&mut self, preset: u8, config: &mut Config) -> Result<(), RogError> {
let mode = config.active_profile.clone();
let mut fan_ctrl = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
config.read();
let mut mode_config = config
.power_profiles
.get_mut(&mode)
.ok_or_else(|| RogError::MissingProfile(mode.clone()))?;
config.power_profile = preset;
mode_config.fan_preset = preset;
config.write();
fan_ctrl
.write_all(format!("{}\n", preset).as_bytes())
.map_err(|err| RogError::Write(self.path.into(), err))?;
info!("Fan mode set to: {:?}", FanLevel::from(preset));
self.set_pstate_for_fan_mode(&mode, config)?;
self.set_fan_curve_for_fan_mode(&mode, config)?;
Ok(())
}
fn handle_profile_event(
&mut self,
event: &ProfileEvent,
config: &mut Config,
) -> Result<(), RogError> {
match event {
ProfileEvent::Toggle => self.do_update(config)?,
ProfileEvent::ChangeMode(mode) => {
self.set_fan_mode(*mode, config)?;
}
ProfileEvent::Cli(command) => {
let profile_key = match command.profile.as_ref() {
Some(k) => k.clone(),
None => config.active_profile.clone(),
};
let mut profile = if command.create {
config
.power_profiles
.entry(profile_key.clone())
.or_insert_with(Profile::default)
} else {
config
.power_profiles
.get_mut(&profile_key)
.ok_or_else(|| RogError::MissingProfile(profile_key.clone()))?
};
if command.turbo.is_some() {
profile.turbo = command.turbo.unwrap();
}
if let Some(min_perc) = command.min_percentage {
profile.min_percentage = min_perc;
}
if let Some(max_perc) = command.max_percentage {
profile.max_percentage = max_perc;
}
if let Some(ref preset) = command.preset {
profile.fan_preset = preset.into();
}
if let Some(ref curve) = command.curve {
profile.fan_curve = Some(curve.clone());
}
self.set(&profile_key, config)?;
}
}
Ok(())
}
fn set(&mut self, profile: &str, config: &mut Config) -> Result<(), RogError> {
let mode_config = config
.power_profiles
.get(profile)
.ok_or_else(|| RogError::MissingProfile(profile.into()))?;
let mut fan_ctrl = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
fan_ctrl
.write_all(format!("{}\n", mode_config.fan_preset).as_bytes())
.map_err(|err| RogError::Write(self.path.into(), err))?;
config.power_profile = mode_config.fan_preset;
self.set_pstate_for_fan_mode(profile, config)?;
self.set_fan_curve_for_fan_mode(profile, config)?;
config.active_profile = profile.into();
config.write();
Ok(())
}
fn set_pstate_for_fan_mode(&self, mode: &str, config: &mut Config) -> Result<(), RogError> {
info!("Setting pstate");
let mode_config = config
.power_profiles
.get(mode)
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
// Set CPU pstate
if let Ok(pstate) = intel_pstate::PState::new() {
pstate.set_min_perf_pct(mode_config.min_percentage)?;
pstate.set_max_perf_pct(mode_config.max_percentage)?;
pstate.set_no_turbo(!mode_config.turbo)?;
info!(
"Intel CPU Power: min: {}%, max: {}%, turbo: {}",
mode_config.min_percentage, mode_config.max_percentage, mode_config.turbo
);
} else {
info!("Setting pstate for AMD CPU");
// must be AMD CPU
let mut file = OpenOptions::new()
.write(true)
.open(AMD_BOOST_PATH)
.map_err(|err| RogError::Path(self.path.into(), err))?;
let boost = if mode_config.turbo { "1" } else { "0" }; // opposite of Intel
file.write_all(boost.as_bytes())
.map_err(|err| RogError::Write(AMD_BOOST_PATH.into(), err))?;
info!("AMD CPU Turbo: {}", boost);
}
Ok(())
}
fn set_fan_curve_for_fan_mode(&self, mode: &str, config: &Config) -> Result<(), RogError> {
let mode_config = &config
.power_profiles
.get(mode)
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
if let Some(ref curve) = mode_config.fan_curve {
use rog_fan_curve::{Board, Fan};
if let Some(board) = Board::from_board_name() {
curve.apply(board, Fan::Cpu)?;
curve.apply(board, Fan::Gpu)?;
} else {
warn!("Fan curve unsupported on this board.")
}
}
Ok(())
}
}
use crate::error::RogError;
#[derive(Debug)]
pub enum FanLevel {
Normal,
Boost,
Silent,
}
impl FromStr for FanLevel {
type Err = RogError;
fn from_str(s: &str) -> Result<Self, RogError> {
match s.to_lowercase().as_str() {
"normal" => Ok(FanLevel::Normal),
"boost" => Ok(FanLevel::Boost),
"silent" => Ok(FanLevel::Silent),
_ => Err(RogError::ParseFanLevel),
}
}
}
impl From<u8> for FanLevel {
fn from(n: u8) -> Self {
match n {
0 => FanLevel::Normal,
1 => FanLevel::Boost,
2 => FanLevel::Silent,
_ => FanLevel::Normal,
}
}
}
impl From<FanLevel> for u8 {
fn from(n: FanLevel) -> Self {
match n {
FanLevel::Normal => 0,
FanLevel::Boost => 1,
FanLevel::Silent => 2,
}
}
}

View File

@@ -1,394 +0,0 @@
// Only these two packets must be 17 bytes
static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
use crate::{config::Config, error::RogError, laptops::HELP_ADDRESS};
use asus_nb::{aura_brightness_bytes, aura_modes::AuraModes, fancy::KeyColourArray, LED_MSG_LEN};
use log::{info, warn};
use std::convert::TryInto;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::sync::Arc;
use std::sync::Mutex;
use zbus::dbus_interface;
pub struct CtrlKbdBacklight {
led_node: Option<String>,
#[allow(dead_code)]
kbd_node: Option<String>,
pub bright_node: String,
supported_modes: Vec<u8>,
flip_effect_write: bool,
config: Arc<Mutex<Config>>,
}
pub struct DbusKbdBacklight {
inner: Arc<Mutex<CtrlKbdBacklight>>,
}
impl DbusKbdBacklight {
pub fn new(inner: Arc<Mutex<CtrlKbdBacklight>>) -> Self {
Self { inner }
}
}
trait Dbus {
fn set_led(&mut self, data: String);
fn ledmode(&self) -> String;
fn notify_led(&self, data: &str) -> zbus::Result<()>;
}
impl crate::ZbusAdd for DbusKbdBacklight {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/Led".try_into().unwrap(), self)
.unwrap();
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl DbusKbdBacklight {
fn set_led_mode(&mut self, data: String) {
if let Ok(data) = serde_json::from_str(&data) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
match &data {
AuraModes::PerKey(_) => {
ctrl.do_command(data, &mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
}
_ => {
let json = serde_json::to_string(&data).unwrap();
ctrl.do_command(data, &mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
self.notify_led(&json).unwrap();
}
}
}
}
} else {
warn!("SetKeyBacklight could not deserialise");
}
}
fn led_mode(&self) -> String {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(cfg) = ctrl.config.clone().try_lock() {
if let Some(mode) = cfg.get_led_mode_data(cfg.kbd_backlight_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
return json;
}
}
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not deserialise".to_string()
}
fn led_modes(&self) -> String {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(cfg) = ctrl.config.clone().try_lock() {
if let Ok(json) = serde_json::to_string(&cfg.kbd_backlight_modes) {
return json;
}
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not deserialise".to_string()
}
#[dbus_interface(signal)]
fn notify_led(&self, data: &str) -> zbus::Result<()>;
}
impl crate::Reloadable for CtrlKbdBacklight {
fn reload(&mut self) -> Result<(), RogError> {
// set current mode (if any)
if let Ok(mut config) = self.config.clone().try_lock() {
if self.supported_modes.len() > 1 {
if self.supported_modes.contains(&config.kbd_backlight_mode) {
let mode = config
.get_led_mode_data(config.kbd_backlight_mode)
.ok_or(RogError::NotSupported)?
.to_owned();
self.write_mode(&mode)?;
info!("Reloaded last used mode");
} else {
warn!(
"An unsupported mode was set: {}, reset to first mode available",
<&str>::from(&<AuraModes>::from(config.kbd_backlight_mode))
);
for (idx, mode) in config.kbd_backlight_modes.iter_mut().enumerate() {
if !self.supported_modes.contains(&mode.into()) {
config.kbd_backlight_modes.remove(idx);
config.write();
break;
}
}
config.kbd_backlight_mode = self.supported_modes[0];
// TODO: do a recursive call with a boxed dyn future later
let mode = config
.get_led_mode_data(config.kbd_backlight_mode)
.ok_or(RogError::NotSupported)?
.to_owned();
self.write_mode(&mode)?;
info!("Reloaded last used mode");
}
}
// Reload brightness
let bright = config.kbd_led_brightness;
let bytes = aura_brightness_bytes(bright);
self.write_bytes(&bytes)?;
info!("Reloaded last used brightness");
}
Ok(())
}
}
impl crate::CtrlTask for CtrlKbdBacklight {
fn do_task(&mut self) -> Result<(), RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(&self.bright_node)
.map_err(|err| RogError::Path((&self.bright_node).into(), err))?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)
.map_err(|err| RogError::Read("buffer".into(), err))?;
if let Some(num) = char::from(buf[0]).to_digit(10) {
if let Ok(mut config) = self.config.clone().try_lock() {
if config.kbd_led_brightness != num as u8 {
config.read();
config.kbd_led_brightness = num as u8;
config.write();
}
}
return Ok(());
}
Err(RogError::ParseLED)
}
}
impl CtrlKbdBacklight {
#[inline]
pub fn new(
id_product: &str,
condev_iface: Option<&String>,
supported_modes: Vec<u8>,
config: Arc<Mutex<Config>>,
) -> Self {
// TODO: return error if *all* nodes are None
CtrlKbdBacklight {
led_node: Self::get_node_failover(id_product, None, Self::scan_led_node).ok(),
kbd_node: Self::get_node_failover(id_product, condev_iface, Self::scan_kbd_node).ok(),
// TODO: Check for existance
bright_node: "/sys/class/leds/asus::kbd_backlight/brightness".to_string(),
supported_modes,
flip_effect_write: false,
config,
}
}
fn get_node_failover(
id_product: &str,
iface: Option<&String>,
fun: fn(&str, Option<&String>) -> Result<String, RogError>,
) -> Result<String, RogError> {
for n in 0..=2 {
// 0,1,2 inclusive
match fun(id_product, iface) {
Ok(o) => return Ok(o),
Err(e) => {
if n == 2 {
warn!("Looking for node: {}", e.to_string());
std::thread::sleep(std::time::Duration::from_secs(1));
} else {
break;
}
}
}
}
// Shouldn't be possible to reach this...
Err(RogError::NotFound(format!("{}, {:?}", id_product, iface)))
}
fn scan_led_node(id_product: &str, _: Option<&String>) -> Result<String, RogError> {
let mut enumerator = udev::Enumerator::new().map_err(|err| {
warn!("{}", err);
RogError::Udev("enumerator failed".into(), err)
})?;
enumerator.match_subsystem("hidraw").map_err(|err| {
warn!("{}", err);
RogError::Udev("match_subsystem failed".into(), err)
})?;
for device in enumerator.scan_devices().map_err(|err| {
warn!("{}", err);
RogError::Udev("scan_devices failed".into(), err)
})? {
if let Some(parent) = device
.parent_with_subsystem_devtype("usb", "usb_device")
.map_err(|err| {
warn!("{}", err);
RogError::Udev("parent_with_subsystem_devtype failed".into(), err)
})?
{
if parent.attribute_value("idProduct").unwrap() == id_product {
// && device.parent().unwrap().sysnum().unwrap() == 3
if let Some(dev_node) = device.devnode() {
info!("Using device at: {:?} for LED control", dev_node);
return Ok(dev_node.to_string_lossy().to_string());
}
}
}
}
warn!("Did not find a hidraw node for LED control, your device may be unsupported or require a kernel patch, see: {}", HELP_ADDRESS);
Err(RogError::MissingFunction(
"ASUS LED device node not found".into(),
))
}
fn scan_kbd_node(id_product: &str, iface: Option<&String>) -> Result<String, RogError> {
let mut enumerator = udev::Enumerator::new().map_err(|err| {
warn!("{}", err);
RogError::Udev("enumerator failed".into(), err)
})?;
enumerator.match_subsystem("input").map_err(|err| {
warn!("{}", err);
RogError::Udev("match_subsystem failed".into(), err)
})?;
enumerator
.match_property("ID_MODEL_ID", id_product)
.map_err(|err| {
warn!("{}", err);
RogError::Udev("match_property failed".into(), err)
})?;
for device in enumerator.scan_devices().map_err(|err| {
warn!("{}", err);
err
}).map_err(|err| {
warn!("{}", err);
RogError::Udev("scan_devices failed".into(), err)
})? {
if let Some(dev_node) = device.devnode() {
if let Some(inum) = device.property_value("ID_USB_INTERFACE_NUM") {
if let Some(iface) = iface {
if inum == iface.as_str() {
info!("Using device at: {:?} for keyboard polling", dev_node);
return Ok(dev_node.to_string_lossy().to_string());
}
}
}
}
}
warn!("Did not find keyboard consumer device node, if expected functions are missing please file an issue at {}", HELP_ADDRESS);
Err(RogError::MissingFunction(
"ASUS keyboard 'Consumer Device' node not found".into(),
))
}
pub fn do_command(&mut self, mode: AuraModes, config: &mut Config) -> Result<(), RogError> {
self.set_and_save(mode, config)
}
/// 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) {
file.write_all(message).unwrap();
return Ok(());
}
}
Err(RogError::NotSupported)
}
/// Write an effect block
#[inline]
fn write_effect(&mut self, effect: &[Vec<u8>]) -> Result<(), RogError> {
if self.flip_effect_write {
for row in effect.iter().rev() {
self.write_bytes(row)?;
}
} else {
for row in effect.iter() {
self.write_bytes(row)?;
}
}
self.flip_effect_write = !self.flip_effect_write;
Ok(())
}
/// Used to set a builtin mode and save the settings for it
///
/// This needs to be universal so that settings applied by dbus stick
#[inline]
fn set_and_save(&mut self, mode: AuraModes, config: &mut Config) -> Result<(), RogError> {
match mode {
AuraModes::LedBrightness(n) => {
let bytes: [u8; LED_MSG_LEN] = (&mode).into();
self.write_bytes(&bytes)?;
config.read();
config.kbd_led_brightness = n;
config.write();
info!("LED brightness set to {:#?}", n);
}
AuraModes::PerKey(v) => {
if v.is_empty() || v[0].is_empty() {
let bytes = KeyColourArray::get_init_msg();
self.write_bytes(&bytes)?;
} else {
self.write_effect(&v)?;
}
}
_ => {
config.read();
let mode_num: u8 = u8::from(&mode);
self.write_mode(&mode)?;
config.kbd_backlight_mode = mode_num;
config.set_mode_data(mode);
config.write();
}
}
Ok(())
}
#[inline]
fn write_mode(&mut self, mode: &AuraModes) -> Result<(), RogError> {
match mode {
AuraModes::PerKey(v) => {
if v.is_empty() || v[0].is_empty() {
let bytes = KeyColourArray::get_init_msg();
self.write_bytes(&bytes)?;
} else {
self.write_effect(v)?;
}
}
_ => {
let mode_num: u8 = u8::from(mode);
match mode {
AuraModes::MultiStatic(_) => {
if self.supported_modes.contains(&mode_num) {
let bytes: [[u8; LED_MSG_LEN]; 4] = mode.into();
for array in bytes.iter() {
self.write_bytes(array)?;
}
}
}
_ => {
if self.supported_modes.contains(&mode_num) {
let bytes: [u8; LED_MSG_LEN] = mode.into();
self.write_bytes(&bytes)?;
}
}
}
self.write_bytes(&LED_SET)?;
// Changes won't persist unless apply is set
self.write_bytes(&LED_APPLY)?;
}
}
Ok(())
}
}

View File

@@ -1,139 +0,0 @@
use ctrl_gfx::ctrl_gfx::CtrlGraphics;
use daemon::config::Config;
use daemon::ctrl_anime::CtrlAnimeDisplay;
use daemon::ctrl_charge::CtrlCharge;
use daemon::ctrl_fan_cpu::{CtrlFanAndCPU, DbusFanAndCpu};
use daemon::ctrl_leds::{CtrlKbdBacklight, DbusKbdBacklight};
use daemon::laptops::match_laptop;
use asus_nb::DBUS_NAME;
use daemon::{CtrlTask, Reloadable, ZbusAdd};
use log::LevelFilter;
use log::{error, info, warn};
use std::error::Error;
use std::io::Write;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::fdo;
use zbus::Connection;
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut logger = env_logger::Builder::new();
logger
.target(env_logger::Target::Stdout)
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
.filter(None, LevelFilter::Info)
.init();
info!("Version: {}", daemon::VERSION);
start_daemon()?;
Ok(())
}
// Timing is such that:
// - interrupt write is minimum 1ms (sometimes lower)
// - read interrupt must timeout, minimum of 1ms
// - for a single usb packet, 2ms total.
// - to maintain constant times of 1ms, per-key colours should use
// the effect endpoint so that the complete colour block is written
// as fast as 1ms per row of the matrix inside it. (10ms total time)
//
// DBUS processing takes 6ms if not tokiod
fn start_daemon() -> Result<(), Box<dyn Error>> {
let laptop = match_laptop();
let config = if let Some(laptop) = laptop.as_ref() {
Config::default().load(laptop.supported_modes())
} else {
Config::default().load(&[])
};
let connection = Connection::new_system()?;
fdo::DBusProxy::new(&connection)?
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
let mut object_server = zbus::ObjectServer::new(&connection);
let config = Arc::new(Mutex::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);
}
Err(err) => {
error!("charge_control: {}", err);
}
}
match CtrlAnimeDisplay::new() {
Ok(ctrl) => {
ctrl.add_to_server(&mut object_server);
}
Err(err) => {
error!("AniMe control: {}", err);
}
}
match CtrlGraphics::new() {
Ok(mut ctrl) => {
ctrl.reload()
.unwrap_or_else(|err| warn!("Gfx controller: {}", err));
ctrl.add_to_server(&mut object_server);
}
Err(err) => {
error!("Gfx control: {}", err);
}
}
// Collect tasks for task thread
let mut tasks: Vec<Arc<Mutex<dyn CtrlTask + Send>>> = Vec::new();
match CtrlFanAndCPU::new(config.clone()) {
Ok(mut ctrl) => {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
DbusFanAndCpu::new(tmp.clone()).add_to_server(&mut object_server);
tasks.push(tmp);
}
Err(err) => {
error!("Profile control: {}", err);
}
};
if let Some(laptop) = laptop {
let ctrl = CtrlKbdBacklight::new(
laptop.usb_product(),
laptop.condev_iface(),
laptop.supported_modes().to_owned(),
config,
);
let tmp = Arc::new(Mutex::new(ctrl));
DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server);
tasks.push(tmp);
}
// TODO: implement messaging between threads to check fails
// These tasks generally read a sys path or file to check for a
// change
let _handle = std::thread::Builder::new()
.name("asusd watch".to_string())
.spawn(move || loop {
std::thread::sleep(std::time::Duration::from_millis(100));
for ctrl in tasks.iter() {
if let Ok(mut lock) = ctrl.try_lock() {
lock.do_task().unwrap();
}
}
});
loop {
if let Err(err) = object_server.try_handle_next() {
eprintln!("{}", err);
}
}
}

View File

@@ -1,58 +0,0 @@
use std::fmt;
use std::convert::From;
use intel_pstate::PStateError;
use rog_fan_curve::CurveError;
#[derive(Debug)]
pub enum RogError {
ParseFanLevel,
ParseVendor,
ParseLED,
MissingProfile(String),
Udev(String, std::io::Error),
Path(String, std::io::Error),
Read(String, std::io::Error),
Write(String, std::io::Error),
NotSupported,
NotFound(String),
IntelPstate(PStateError),
FanCurve(CurveError),
DoTask(String),
MissingFunction(String),
}
impl fmt::Display for RogError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RogError::ParseFanLevel => write!(f, "Parse profile error"),
RogError::ParseVendor => write!(f, "Parse gfx vendor error"),
RogError::ParseLED => write!(f, "Parse LED error"),
RogError::MissingProfile(profile) => write!(f, "Profile does not exist {}", profile),
RogError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
RogError::Path(path, error) => write!(f, "Path {}: {}", path, error),
RogError::Read(path, error) => write!(f, "Read {}: {}", path, error),
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
RogError::NotSupported => write!(f, "Not supported"),
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
RogError::IntelPstate(err) => write!(f, "Intel pstate error: {}", err),
RogError::FanCurve(err) => write!(f, "Custom fan-curve error: {}", err),
RogError::DoTask(deets) => write!(f, "Task error: {}", deets),
RogError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
}
}
}
impl std::error::Error for RogError {}
impl From<PStateError> for RogError {
fn from(err: PStateError) -> Self {
RogError::IntelPstate(err)
}
}
impl From<CurveError> for RogError {
fn from(err: CurveError) -> Self {
RogError::FanCurve(err)
}
}

View File

@@ -1,144 +0,0 @@
use asus_nb::aura_modes::{AuraModes, BREATHING, STATIC, STROBE};
use log::{info, warn};
use serde_derive::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Read;
pub static LEDMODE_CONFIG_PATH: &str = "/etc/asusd/asusd-ledmodes.toml";
pub static HELP_ADDRESS: &str = "https://gitlab.com/asus-linux/asus-nb-ctrl";
pub struct LaptopBase {
usb_product: String,
condev_iface: Option<String>, // required for finding the Consumer Device interface
supported_modes: Vec<u8>,
}
impl LaptopBase {
pub fn usb_product(&self) -> &str {
&self.usb_product
}
pub fn condev_iface(&self) -> Option<&String> {
self.condev_iface.as_ref()
}
pub fn supported_modes(&self) -> &[u8] {
&self.supported_modes
}
}
pub fn match_laptop() -> Option<LaptopBase> {
for device in rusb::devices().unwrap().iter() {
let device_desc = device.device_descriptor().unwrap();
if device_desc.vendor_id() == 0x0b05 {
match device_desc.product_id() {
0x1866 => {
let laptop = select_1866_device("1866".to_owned());
print_modes(&laptop.supported_modes);
return Some(laptop);
}
0x1869 => return Some(select_1866_device("1869".to_owned())),
0x1854 => {
info!("Found GL753 or similar");
return Some(LaptopBase {
usb_product: "1854".to_string(),
condev_iface: None,
supported_modes: vec![STATIC, BREATHING, STROBE],
});
}
_ => {}
}
}
}
warn!(
"Unsupported laptop, please request support at {}",
HELP_ADDRESS
);
warn!("Continuing with minimal support");
None
}
fn select_1866_device(prod: String) -> LaptopBase {
let dmi = sysfs_class::DmiId::default();
let board_name = dmi.board_name().expect("Could not get board_name");
let prod_family = dmi.product_family().expect("Could not get product_family");
let prod_name = dmi.product_name().expect("Could not get product_name");
info!("Product name: {}", prod_name.trim());
info!("Board name: {}", board_name.trim());
let mut laptop = LaptopBase {
usb_product: prod,
condev_iface: Some("02".to_owned()),
supported_modes: vec![],
};
if let Some(modes) = LEDModeGroup::load_from_config() {
if let Some(led_modes) = modes.matcher(&prod_family, &board_name) {
laptop.supported_modes = led_modes;
return laptop;
}
}
laptop
}
fn print_modes(supported_modes: &[u8]) {
if !supported_modes.is_empty() {
info!("Supported Keyboard LED modes are:");
for mode in supported_modes {
let mode = <&str>::from(&<AuraModes>::from(*mode));
info!("- {}", mode);
}
info!(
"If these modes are incorrect or missing please request support at {}",
HELP_ADDRESS
);
} else {
info!("No RGB control available");
}
}
#[derive(Debug, Deserialize, Serialize)]
struct LEDModeGroup {
led_modes: Vec<LEDModes>,
}
impl LEDModeGroup {
/// Consumes the LEDModes
fn matcher(self, prod_family: &str, board_name: &str) -> Option<Vec<u8>> {
for led_modes in self.led_modes {
if prod_family.contains(&led_modes.prod_family) {
for board in led_modes.board_names {
if board_name.contains(&board) {
info!("Matched to {} {}", led_modes.prod_family, board);
return Some(led_modes.led_modes);
}
}
}
}
None
}
fn load_from_config() -> Option<Self> {
if let Ok(mut file) = OpenOptions::new().read(true).open(&LEDMODE_CONFIG_PATH) {
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("{} is empty", LEDMODE_CONFIG_PATH);
} else {
return Some(toml::from_str(&buf).unwrap_or_else(|_| {
panic!("Could not deserialise {}", LEDMODE_CONFIG_PATH)
}));
}
}
}
warn!("Does {} exist?", LEDMODE_CONFIG_PATH);
None
}
}
#[derive(Debug, Deserialize, Serialize)]
struct LEDModes {
prod_family: String,
board_names: Vec<String>,
led_modes: Vec<u8>,
}

View File

@@ -1,40 +0,0 @@
#![deny(unused_must_use)]
/// Configuration loading, saving
pub mod config;
///
pub mod ctrl_anime;
///
pub mod ctrl_charge;
///
pub mod ctrl_fan_cpu;
///
pub mod ctrl_leds;
///
/// Laptop matching to determine capabilities
pub mod laptops;
mod error;
use config::Config;
use crate::error::RogError;
use zbus::ObjectServer;
pub static VERSION: &str = "2.0.2";
pub trait Reloadable {
fn reload(&mut self) -> Result<(), RogError>;
}
pub trait ZbusAdd {
fn add_to_server(self, server: &mut ObjectServer);
}
pub trait CtrlTask {
fn do_task(&mut self) -> Result<(), RogError>;
}
pub trait CtrlTaskComplex {
type A;
fn do_task(&mut self, config: &mut Config, event: Self::A);
}

View File

@@ -1,191 +0,0 @@
use asus_nb::{
cli_options::{LedBrightness, SetAuraBuiltin},
core_dbus::AuraDbusClient,
profile::{ProfileCommand, ProfileEvent},
};
use ctrl_gfx::vendors::GfxVendors;
use daemon::ctrl_fan_cpu::FanLevel;
use gumdrop::Options;
use log::LevelFilter;
use std::io::Write;
use std::process::Command;
use yansi_term::Colour::Green;
use yansi_term::Colour::Red;
#[derive(Options)]
struct CLIStart {
#[options(help = "print help message")]
help: bool,
#[options(help = "show program version number")]
version: bool,
#[options(meta = "VAL", help = "<off, low, med, high>")]
kbd_bright: Option<LedBrightness>,
#[options(meta = "PWR", help = "<silent, normal, boost>")]
pwr_profile: Option<FanLevel>,
#[options(meta = "CHRG", help = "<20-100>")]
chg_limit: Option<u8>,
#[options(command)]
command: Option<CliCommand>,
}
#[derive(Options)]
enum CliCommand {
#[options(help = "Set the keyboard lighting from built-in modes")]
LedMode(LedModeCommand),
#[options(help = "Create and configure profiles")]
Profile(ProfileCommand),
#[options(help = "Set the graphics mode")]
Graphics(GraphicsCommand),
}
#[derive(Options)]
struct LedModeCommand {
#[options(help = "print help message")]
help: bool,
#[options(command, required)]
command: Option<SetAuraBuiltin>,
}
#[derive(Options)]
struct GraphicsCommand {
#[options(help = "print help message")]
help: bool,
#[options(help = "Set graphics mode: <nvidia, hybrid, compute, integrated>")]
mode: Option<GfxVendors>,
#[options(help = "Get the current mode")]
get: bool,
#[options(help = "Get the current power status")]
pow: bool,
#[options(help = "Do not ask for confirmation")]
force: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut logger = env_logger::Builder::new();
logger
.target(env_logger::Target::Stdout)
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
.filter(None, LevelFilter::Info)
.init();
let parsed = CLIStart::parse_args_default_or_exit();
if parsed.version {
println!("Version: {}", daemon::VERSION);
}
let writer = AuraDbusClient::new()?;
match parsed.command {
Some(CliCommand::LedMode(mode)) => {
if let Some(command) = mode.command {
writer.write_builtin_mode(&command.into())?
}
}
Some(CliCommand::Profile(command)) => {
writer.write_profile_command(&ProfileEvent::Cli(command))?
}
Some(CliCommand::Graphics(command)) => do_gfx(command, &writer)?,
None => (),
}
if let Some(brightness) = parsed.kbd_bright {
writer.write_brightness(brightness.level())?;
}
if let Some(fan_level) = parsed.pwr_profile {
writer.write_fan_mode(fan_level.into())?;
}
if let Some(chg_limit) = parsed.chg_limit {
writer.write_charge_limit(chg_limit)?;
}
Ok(())
}
fn do_gfx(
command: GraphicsCommand,
writer: &AuraDbusClient,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(mode) = command.mode {
println!("Updating settings, please wait...");
println!("If this takes longer than 30s, ctrl+c then check journalctl");
writer.write_gfx_mode(mode)?;
let res = writer.wait_gfx_changed()?;
match res.as_str() {
"reboot" => {
println!(
"{}",
Green.paint("\nGraphics vendor mode changed successfully\n"),
);
do_gfx_action(
command.force,
Command::new("systemctl").arg("reboot"),
"Reboot Linux PC",
"Please reboot when ready",
)?;
}
"restartx" => {
println!(
"{}",
Green.paint("\nGraphics vendor mode changed successfully\n")
);
do_gfx_action(
command.force,
Command::new("systemctl")
.arg("restart")
.arg("display-manager.service"),
"Restart display-manager server",
"Please restart display-manager when ready",
)?;
std::process::exit(1)
}
_ => std::process::exit(-1),
}
std::process::exit(-1)
}
if command.get {
let res = writer.get_gfx_mode()?;
println!("Current graphics mode: {}", res);
}
if command.pow {
let res = writer.get_gfx_pwr()?;
if res.contains("active") {
println!("Current power status: {}", Red.paint(&format!("{}", res)));
} else {
println!("Current power status: {}", Green.paint(&format!("{}", res)));
}
}
Ok(())
}
fn do_gfx_action(
no_confirm: bool,
command: &mut Command,
ask_msg: &str,
cancel_msg: &str,
) -> Result<(), Box<dyn std::error::Error>> {
println!("{}? y/n", ask_msg);
let mut buf = String::new();
if no_confirm {
let status = command.status()?;
if !status.success() {
println!("systemctl: returned with {}", status);
}
}
std::io::stdin().read_line(&mut buf).expect("Input failed");
let input = buf.chars().next().unwrap() as char;
if input == 'Y' || input == 'y' || no_confirm {
let status = command.status()?;
if !status.success() {
println!("systemctl: returned with {}", status);
}
} else {
println!("{}", Red.paint(&format!("{}", cancel_msg)));
}
Ok(())
}

View File

@@ -1,25 +0,0 @@
[package]
name = "asus-nb"
version = "2.0.2"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
repository = "https://gitlab.com/asus-linux/asus-nb-ctrl"
homepage = "https://gitlab.com/asus-linux/asus-nb-ctrl"
description = "A small library of effect types and conversions for ROG Aura"
edition = "2018"
[dependencies]
gumdrop = "^0.8"
dbus = { version = "^0.8" }
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
yansi-term = "^0.1"
rog_fan_curve = { version = "0.1", features = ["serde"] }
zbus = "1.1.1"
zvariant = "2.2.0"
ctrl-gfx = { path = "../ctrl-gfx" }
[dev-dependencies]
tinybmp = "^0.2.3"

View File

@@ -1,41 +0,0 @@
use asus_nb::anime_dbus::AniMeDbusWriter;
use asus_nb::anime_matrix::{AniMeMatrix, AniMePacketType, HEIGHT, WIDTH};
use tinybmp::{Bmp, Pixel};
fn main() {
let mut writer = AniMeDbusWriter::new().unwrap();
let bmp =
Bmp::from_slice(include_bytes!("non-skewed_r.bmp")).expect("Failed to parse BMP image");
let pixels: Vec<Pixel> = bmp.into_iter().collect();
//assert_eq!(pixels.len(), 56 * 56);
// Try an outline, top and right
let mut matrix = AniMeMatrix::new();
// Aligned left
for px in pixels {
if (px.x as usize / 2) < WIDTH && (px.y as usize) < HEIGHT && px.x % 2 == 0 {
matrix.get_mut()[px.y as usize][px.x as usize / 2] = px.color as u8;
}
}
// Throw an alignment border up
// {
// let tmp = matrix.get_mut();
// for x in tmp[0].iter_mut() {
// *x = 0xff;
// }
// for row in tmp.iter_mut() {
// row[row.len() - 1] = 0xff;
// }
// }
matrix.debug_print();
let mut matrix: AniMePacketType = AniMePacketType::from(matrix);
// println!("{:?}", matrix[0].to_vec());
// println!("{:?}", matrix[1].to_vec());
writer.write_image(&mut matrix).unwrap();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -1,79 +0,0 @@
use crate::anime_matrix::AniMePacketType;
use crate::{DBUS_IFACE, DBUS_NAME};
use dbus::channel::Sender;
use dbus::{blocking::Connection, Message};
use std::error::Error;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::{thread, time::Duration};
pub const ANIME_PANE1_PREFIX: [u8; 7] = [0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02];
pub const ANIME_PANE2_PREFIX: [u8; 7] = [0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02];
/// Interface for the AniMe dot-matrix display
///
/// The resolution is 34x56 (1904) but only 1,215 LEDs in the top-left are used.
/// The display is available only on select GA401 models.
///
/// Actual image ration when displayed is stretched width.
///
/// Data structure should be nested array of [[u8; 33]; 56]
pub struct AniMeDbusWriter {
connection: Box<Connection>,
block_time: u64,
stop: Arc<AtomicBool>,
}
impl AniMeDbusWriter {
#[inline]
pub fn new() -> Result<Self, Box<dyn Error>> {
let connection = Connection::new_system()?;
Ok(AniMeDbusWriter {
connection: Box::new(connection),
block_time: 25,
stop: Arc::new(AtomicBool::new(false)),
})
}
pub fn write_image_to_buf(_buf: &mut AniMePacketType, _image_data: &[u8]) {
unimplemented!("Image format is in progress of being worked out")
}
/// Write an Animatrix image
///
/// The expected input here is *two* Vectors, 640 bytes in length. The two vectors
/// are each one half of the full image write.
///
/// After each write a flush is written, it is assumed that this tells the device to
/// go ahead and display the written bytes
///
/// # Note:
/// The vectors are expected to contain the full sequence of bytes as follows
///
/// - Write packet 1: 0x5e 0xc0 0x02 0x01 0x00 0x73 0x02 .. <led brightness>
/// - Write packet 2: 0x5e 0xc0 0x02 0x74 0x02 0x73 0x02 .. <led brightness>
///
/// Where led brightness is 0..255, low to high
#[inline]
pub fn write_image(&mut self, image: &mut AniMePacketType) -> Result<(), Box<dyn Error>> {
if image[0][0] != ANIME_PANE1_PREFIX[0] && image[0][6] != ANIME_PANE1_PREFIX[6] {
image[0][..7].copy_from_slice(&ANIME_PANE1_PREFIX);
}
if image[1][0] != ANIME_PANE2_PREFIX[0] && image[1][6] != ANIME_PANE2_PREFIX[6] {
image[1][..7].copy_from_slice(&ANIME_PANE2_PREFIX);
}
let mut msg =
Message::new_method_call(DBUS_NAME, "/org/asuslinux/Anime", DBUS_IFACE, "SetAnime")?
.append2(image[0].to_vec(), image[1].to_vec());
msg.set_no_reply(true);
self.connection.send(msg).unwrap();
thread::sleep(Duration::from_millis(self.block_time));
if self.stop.load(Ordering::Relaxed) {
panic!("Got signal to stop!");
}
Ok(())
}
}

View File

@@ -1,236 +0,0 @@
pub const WIDTH: usize = 34; // Width is definitely 34 items
pub const HEIGHT: usize = 56;
pub type AniMeBufferType = [[u8; WIDTH]; HEIGHT];
pub type AniMePacketType = [[u8; 640]; 2];
const BLOCK_START: usize = 7;
const BLOCK_END: usize = 634;
use yansi_term::Colour::RGB;
/// Helper structure for writing images.
///
/// See the examples for ways to write an image to `AniMeMatrix` format.
pub struct AniMeMatrix(AniMeBufferType);
impl Default for AniMeMatrix {
fn default() -> Self {
Self::new()
}
}
impl AniMeMatrix {
pub fn new() -> Self {
AniMeMatrix([[0u8; WIDTH]; HEIGHT])
}
pub fn get(&self) -> &AniMeBufferType {
&self.0
}
pub fn get_mut(&mut self) -> &mut AniMeBufferType {
&mut self.0
}
pub fn fill_with(&mut self, fill: u8) {
for row in self.0.iter_mut() {
for x in row.iter_mut() {
*x = fill;
}
}
}
pub fn debug_print(&self) {
// this is the index from right. It is used to progressively shorten rows
let mut prog_row_len = WIDTH - 2;
for (count, row) in self.0.iter().enumerate() {
// Write the top block of LEDs (first 7 rows)
if count < 6 {
if count % 2 != 0 {
print!(" ");
} else {
print!(" ");
}
let tmp = if count == 0 || count == 1 || count == 3 || count == 5 {
row[1..].iter()
} else {
row.iter()
};
for x in tmp {
print!(" {}", RGB(*x, *x, *x).paint(&format!("{:#04X}", x)));
}
println!();
} else {
// Switch to next block (looks like )
if count % 2 != 0 {
// Row after 6 is only 1 less, then rows after 7 follow pattern
if count == 7 {
prog_row_len -= 1;
} else {
prog_row_len -= 2;
}
} else {
prog_row_len += 1; // if count 6, 0
}
let index = row.len() - prog_row_len;
if count % 2 == 0 {
print!(" ");
}
for (i, x) in row.iter().enumerate() {
if i >= index {
print!(" {}", RGB(*x, *x, *x).paint(&format!("{:#04X}", x)));
} else {
print!(" ");
}
}
println!();
}
}
}
}
impl From<AniMeMatrix> for AniMePacketType {
/// Do conversion from the nested Vec in AniMeMatrix to the two required
/// packets suitable for sending over USB
#[inline]
fn from(anime: AniMeMatrix) -> Self {
let mut buffers = [[0; 640]; 2];
let mut write_index = BLOCK_START;
let mut write_block = &mut buffers[0];
let mut block1_done = false;
// this is the index from right. It is used to progressively shorten rows
let mut prog_row_len = WIDTH - 2;
for (count, row) in anime.0.iter().enumerate() {
// Write the top block of LEDs (first 7 rows)
if count < 6 {
for (i, x) in row.iter().enumerate() {
// Rows 0, 1, 3, 5 are short and misaligned
if count == 0 || count == 1 || count == 3 || count == 5 {
if i > 0 {
write_block[write_index - 1] = *x;
}
} else {
write_block[write_index] = *x;
}
write_index += 1;
}
} else {
// Switch to next block (looks like )
if count % 2 != 0 {
// Row after 6 is only 1 less, then rows after 7 follow pattern
if count == 7 {
prog_row_len -= 1;
} else {
prog_row_len -= 2;
}
} else {
prog_row_len += 1; // if count 6, 0
}
let index = row.len() - prog_row_len;
for n in row.iter().skip(index) {
// Require a special case to catch the correct end-of-packet which is
// 6 bytes from the end
if write_index == BLOCK_END && !block1_done {
block1_done = true;
write_block = &mut buffers[1];
write_index = BLOCK_START;
}
write_block[write_index] = *n;
write_index += 1;
}
}
}
buffers
}
}
#[cfg(test)]
mod tests {
use crate::anime_matrix::{AniMeMatrix, AniMePacketType};
#[test]
fn check_data_alignment() {
let mut matrix = AniMeMatrix::new();
{
let tmp = matrix.get_mut();
for row in tmp.iter_mut() {
row[row.len() - 1] = 0xff;
}
}
let matrix: AniMePacketType = AniMePacketType::from(matrix);
// The bytes at the right of the initial AniMeMatrix should always end up aligned in the
// same place after conversion to data packets
// Check against manually worked out right align
assert_eq!(
matrix[0].to_vec(),
[
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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 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, 0, 0, 0, 0, 255, 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, 0, 0, 0, 0, 0, 255,
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, 0, 0, 0, 255, 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, 0, 0, 0, 0, 0, 255, 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, 0, 0, 0, 255, 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, 0, 0, 0, 0, 255, 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,
0, 0, 255, 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, 0, 0, 0, 255, 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, 0, 255, 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, 0, 0, 255, 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, 255, 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, 0, 255, 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, 255,
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, 255, 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, 255, 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, 255, 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, 255, 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, 255, 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, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
.to_vec()
);
assert_eq!(
matrix[1].to_vec(),
[
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, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
255, 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,
255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 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, 0, 0, 0,
255, 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, 255, 0, 0, 0,
0, 0, 0, 0
]
.to_vec()
);
}
}

View File

@@ -1,287 +0,0 @@
use crate::cli_options;
use crate::cli_options::SetAuraBuiltin;
use serde_derive::{Deserialize, Serialize};
pub const STATIC: u8 = 0x00;
pub const BREATHING: u8 = 0x01;
pub const STROBE: u8 = 0x02;
pub const RAINBOW: u8 = 0x03;
pub const STAR: u8 = 0x04;
pub const RAIN: u8 = 0x05;
pub const HIGHLIGHT: u8 = 0x06;
pub const LASER: u8 = 0x07;
pub const RIPPLE: u8 = 0x08;
pub const PULSE: u8 = 0x0a;
pub const COMET: u8 = 0x0b;
pub const FLASH: u8 = 0x0c;
pub const MULTISTATIC: u8 = 0x0d;
pub const PER_KEY: u8 = 0xff;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Colour(pub u8, pub u8, pub u8);
impl From<cli_options::Colour> for Colour {
fn from(c: cli_options::Colour) -> Self {
Colour(c.0, c.1, c.2)
}
}
impl Default for Colour {
fn default() -> Self {
Colour(128, 0, 0)
}
}
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
pub enum Speed {
Low = 0xe1,
Med = 0xeb,
High = 0xf5,
}
impl From<cli_options::Speed> for Speed {
fn from(s: cli_options::Speed) -> Self {
match s {
cli_options::Speed::Low => Speed::Low,
cli_options::Speed::Med => Speed::Med,
cli_options::Speed::High => Speed::High,
}
}
}
impl Default for Speed {
fn default() -> Self {
Speed::Med
}
}
/// Used for Rainbow mode.
///
/// Enum corresponds to the required integer value
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
pub enum Direction {
Right,
Left,
Up,
Down,
}
impl From<cli_options::Direction> for Direction {
fn from(s: cli_options::Direction) -> Self {
match s {
cli_options::Direction::Right => Direction::Right,
cli_options::Direction::Left => Direction::Left,
cli_options::Direction::Up => Direction::Up,
cli_options::Direction::Down => Direction::Down,
}
}
}
impl Default for Direction {
fn default() -> Self {
Direction::Right
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct TwoColourSpeed {
pub colour: Colour,
pub colour2: Colour,
pub speed: Speed,
}
impl From<cli_options::TwoColourSpeed> for TwoColourSpeed {
fn from(mode: cli_options::TwoColourSpeed) -> Self {
TwoColourSpeed {
colour: mode.colour.into(),
colour2: mode.colour2.into(),
speed: mode.speed.into(),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct SingleSpeed {
pub speed: Speed,
}
impl From<cli_options::SingleSpeed> for SingleSpeed {
fn from(mode: cli_options::SingleSpeed) -> Self {
SingleSpeed {
speed: mode.speed.into(),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct SingleColour {
pub colour: Colour,
}
impl From<cli_options::SingleColour> for SingleColour {
fn from(mode: cli_options::SingleColour) -> Self {
SingleColour {
colour: mode.colour.into(),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct MultiColour {
pub colour1: Colour,
pub colour2: Colour,
pub colour3: Colour,
pub colour4: Colour,
}
impl From<cli_options::MultiColour> for MultiColour {
fn from(mode: cli_options::MultiColour) -> Self {
MultiColour {
colour1: mode.colour1.into(),
colour2: mode.colour2.into(),
colour3: mode.colour3.into(),
colour4: mode.colour4.into(),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct SingleSpeedDirection {
pub direction: Direction,
pub speed: Speed,
}
impl From<cli_options::SingleSpeedDirection> for SingleSpeedDirection {
fn from(mode: cli_options::SingleSpeedDirection) -> Self {
SingleSpeedDirection {
direction: mode.direction.into(),
speed: mode.speed.into(),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct SingleColourSpeed {
pub colour: Colour,
pub speed: Speed,
}
impl From<cli_options::SingleColourSpeed> for SingleColourSpeed {
fn from(mode: cli_options::SingleColourSpeed) -> Self {
SingleColourSpeed {
colour: mode.colour.into(),
speed: mode.speed.into(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum AuraModes {
Static(SingleColour),
Breathe(TwoColourSpeed),
Strobe(SingleSpeed),
Rainbow(SingleSpeedDirection),
Star(TwoColourSpeed),
Rain(SingleSpeed),
Highlight(SingleColourSpeed),
Laser(SingleColourSpeed),
Ripple(SingleColourSpeed),
Pulse(SingleColour),
Comet(SingleColour),
Flash(SingleColour),
MultiStatic(MultiColour),
LedBrightness(u8),
// TODO: use a serializable structure for this (KeyColourArray)
PerKey(Vec<Vec<u8>>),
}
impl From<SetAuraBuiltin> for AuraModes {
fn from(mode: SetAuraBuiltin) -> Self {
match mode {
SetAuraBuiltin::Static(x) => AuraModes::Static(x.into()),
SetAuraBuiltin::Breathe(x) => AuraModes::Breathe(x.into()),
SetAuraBuiltin::Strobe(x) => AuraModes::Strobe(x.into()),
SetAuraBuiltin::Rainbow(x) => AuraModes::Rainbow(x.into()),
SetAuraBuiltin::Star(x) => AuraModes::Star(x.into()),
SetAuraBuiltin::Rain(x) => AuraModes::Rain(x.into()),
SetAuraBuiltin::Highlight(x) => AuraModes::Highlight(x.into()),
SetAuraBuiltin::Laser(x) => AuraModes::Laser(x.into()),
SetAuraBuiltin::Ripple(x) => AuraModes::Ripple(x.into()),
SetAuraBuiltin::Pulse(x) => AuraModes::Pulse(x.into()),
SetAuraBuiltin::Comet(x) => AuraModes::Comet(x.into()),
SetAuraBuiltin::Flash(x) => AuraModes::Flash(x.into()),
SetAuraBuiltin::MultiStatic(x) => AuraModes::MultiStatic(x.into()),
}
}
}
/// Very specific mode conversion required because numbering isn't linear
impl From<AuraModes> for u8 {
fn from(mode: AuraModes) -> Self {
u8::from(&mode)
}
}
/// Very specific mode conversion required because numbering isn't linear
impl From<&mut AuraModes> for u8 {
fn from(mode: &mut AuraModes) -> Self {
u8::from(&*mode)
}
}
/// Very specific mode conversion required because numbering isn't linear
impl From<&AuraModes> for u8 {
fn from(mode: &AuraModes) -> Self {
match mode {
AuraModes::Static(_) => STATIC,
AuraModes::Breathe(_) => BREATHING,
AuraModes::Strobe(_) => STROBE,
AuraModes::Rainbow(_) => RAINBOW,
AuraModes::Star(_) => STAR,
AuraModes::Rain(_) => RAIN,
AuraModes::Highlight(_) => HIGHLIGHT,
AuraModes::Laser(_) => LASER,
AuraModes::Ripple(_) => RIPPLE,
AuraModes::Pulse(_) => PULSE,
AuraModes::Comet(_) => COMET,
AuraModes::Flash(_) => FLASH,
AuraModes::MultiStatic(_) => MULTISTATIC,
AuraModes::PerKey(_) => PER_KEY,
_ => panic!("Invalid mode"),
}
}
}
impl From<&AuraModes> for &str {
fn from(mode: &AuraModes) -> Self {
match mode {
AuraModes::Static(_) => "Static",
AuraModes::Breathe(_) => "Breathing",
AuraModes::Strobe(_) => "Strobing",
AuraModes::Rainbow(_) => "Rainbow",
AuraModes::Star(_) => "Stars",
AuraModes::Rain(_) => "Rain",
AuraModes::Highlight(_) => "Keypress Highlight",
AuraModes::Laser(_) => "Keypress Laser",
AuraModes::Ripple(_) => "Keypress Ripple",
AuraModes::Pulse(_) => "Pulse",
AuraModes::Comet(_) => "Comet",
AuraModes::Flash(_) => "Flash",
AuraModes::MultiStatic(_) => "4-Zone Static Colours",
AuraModes::PerKey(_) => "RGB per-key",
_ => panic!("Invalid mode"),
}
}
}
/// Exists to convert back from correct bytes. PER_KEY byte intentionally left off as it
/// does not correspond to an actual pre-set mode, nor does brightness.
impl From<u8> for AuraModes {
fn from(byte: u8) -> Self {
match byte {
STATIC => AuraModes::Static(SingleColour::default()),
BREATHING => AuraModes::Breathe(TwoColourSpeed::default()),
STROBE => AuraModes::Strobe(SingleSpeed::default()),
RAINBOW => AuraModes::Rainbow(SingleSpeedDirection::default()),
STAR => AuraModes::Star(TwoColourSpeed::default()),
RAIN => AuraModes::Rain(SingleSpeed::default()),
HIGHLIGHT => AuraModes::Highlight(SingleColourSpeed::default()),
LASER => AuraModes::Laser(SingleColourSpeed::default()),
RIPPLE => AuraModes::Ripple(SingleColourSpeed::default()),
PULSE => AuraModes::Pulse(SingleColour::default()),
COMET => AuraModes::Comet(SingleColour::default()),
FLASH => AuraModes::Flash(SingleColour::default()),
MULTISTATIC => AuraModes::MultiStatic(MultiColour::default()),
PER_KEY => AuraModes::PerKey(vec![]),
_ => panic!("Invalid mode byte"),
}
}
}

View File

@@ -1,215 +0,0 @@
use crate::error::AuraError;
use gumdrop::Options;
use serde_derive::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Options)]
pub struct LedBrightness {
level: u8,
}
impl LedBrightness {
pub fn level(&self) -> u8 {
self.level
}
}
impl FromStr for LedBrightness {
type Err = AuraError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"off" => Ok(LedBrightness { level: 0x00 }),
"low" => Ok(LedBrightness { level: 0x01 }),
"med" => Ok(LedBrightness { level: 0x02 }),
"high" => Ok(LedBrightness { level: 0x03 }),
_ => {
println!("Missing required argument, must be one of:\noff,low,med,high\n");
Err(AuraError::ParseBrightness)
}
}
}
}
#[derive(Deserialize, Serialize)]
pub struct Colour(pub u8, pub u8, pub u8);
impl Default for Colour {
fn default() -> Self {
Colour(255, 0, 0)
}
}
impl FromStr for Colour {
type Err = AuraError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() < 6 {
return Err(AuraError::ParseColour);
}
let r = u8::from_str_radix(&s[0..2], 16).or(Err(AuraError::ParseColour))?;
let g = u8::from_str_radix(&s[2..4], 16).or(Err(AuraError::ParseColour))?;
let b = u8::from_str_radix(&s[4..6], 16).or(Err(AuraError::ParseColour))?;
Ok(Colour(r, g, b))
}
}
#[derive(Deserialize, Serialize)]
pub enum Speed {
Low = 0xe1,
Med = 0xeb,
High = 0xf5,
}
impl Default for Speed {
fn default() -> Self {
Speed::Med
}
}
impl FromStr for Speed {
type Err = AuraError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"low" => Ok(Speed::Low),
"med" => Ok(Speed::Med),
"high" => Ok(Speed::High),
_ => Err(AuraError::ParseSpeed),
}
}
}
/// Used for Rainbow mode.
///
/// Enum corresponds to the required integer value
#[derive(Deserialize, Serialize)]
pub enum Direction {
Right,
Left,
Up,
Down,
}
impl Default for Direction {
fn default() -> Self {
Direction::Right
}
}
impl FromStr for Direction {
type Err = AuraError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"right" => Ok(Direction::Right),
"up" => Ok(Direction::Up),
"down" => Ok(Direction::Down),
"left" => Ok(Direction::Left),
_ => Err(AuraError::ParseDirection),
}
}
}
#[derive(Default, Options, Deserialize, Serialize)]
pub struct TwoColourSpeed {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "HEX", help = "set the first RGB value e.g, ff00ff")]
pub colour: Colour,
#[options(no_long, meta = "HEX", help = "set the second RGB value e.g, ff00ff")]
pub colour2: Colour,
#[options(no_long, help = "set the speed: low, med, high")]
pub speed: Speed,
}
#[derive(Default, Options, Deserialize, Serialize)]
pub struct SingleSpeed {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "WORD", help = "set the speed: low, med, high")]
pub speed: Speed,
}
#[derive(Default, Options, Deserialize, Serialize)]
pub struct SingleColour {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "HEX", help = "set the RGB value e.g, ff00ff")]
pub colour: Colour,
}
#[derive(Default, Options, Deserialize, Serialize)]
pub struct MultiColour {
#[options(help = "print help message")]
help: bool,
#[options(meta = "HEX", help = "set the RGB value e.g, ff00ff")]
pub colour1: Colour,
#[options(meta = "HEX", help = "set the RGB value e.g, ff00ff")]
pub colour2: Colour,
#[options(meta = "HEX", help = "set the RGB value e.g, ff00ff")]
pub colour3: Colour,
#[options(meta = "HEX", help = "set the RGB value e.g, ff00ff")]
pub colour4: Colour,
}
#[derive(Default, Options, Deserialize, Serialize)]
pub struct SingleSpeedDirection {
#[options(help = "print help message")]
help: bool,
#[options(
no_long,
meta = "DIR",
help = "set the direction: up, down, left, right"
)]
pub direction: Direction,
#[options(no_long, help = "set the speed: low, med, high")]
pub speed: Speed,
}
#[derive(Default, Options, Deserialize, Serialize)]
pub struct SingleColourSpeed {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "HEX", help = "set the RGB value e.g, ff00ff")]
pub colour: Colour,
#[options(no_long, help = "set the speed: low, med, high")]
pub speed: Speed,
}
/// Byte value for setting the built-in mode.
///
/// Enum corresponds to the required integer value
#[derive(Options, Deserialize, Serialize)]
pub enum SetAuraBuiltin {
#[options(help = "set a single static colour")]
Static(SingleColour),
#[options(help = "pulse between one or two colours")]
Breathe(TwoColourSpeed),
#[options(help = "strobe through all colours")]
Strobe(SingleSpeed),
#[options(help = "rainbow cycling in one of four directions")]
Rainbow(SingleSpeedDirection),
#[options(help = "rain pattern mimicking raindrops")]
Star(TwoColourSpeed),
#[options(help = "rain pattern of three preset colours")]
Rain(SingleSpeed),
#[options(help = "pressed keys are highlighted to fade")]
Highlight(SingleColourSpeed),
#[options(help = "pressed keys generate horizontal laser")]
Laser(SingleColourSpeed),
#[options(help = "pressed keys ripple outwards like a splash")]
Ripple(SingleColourSpeed),
#[options(help = "set a rapid pulse")]
Pulse(SingleColour),
#[options(help = "set a vertical line zooming from left")]
Comet(SingleColour),
#[options(help = "set a wide vertical line zooming from left")]
Flash(SingleColour),
#[options(help = "4-zone multi-colour")]
MultiStatic(MultiColour),
}
impl Default for SetAuraBuiltin {
fn default() -> Self {
SetAuraBuiltin::Static(SingleColour {
help: false,
colour: Colour(255, 0, 0),
})
}
}

View File

@@ -1,312 +0,0 @@
use super::*;
use crate::fancy::KeyColourArray;
use crate::profile::ProfileEvent;
use ctrl_gfx::vendors::GfxVendors;
use dbus::{blocking::Connection, Message};
use std::error::Error;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
use std::{thread, time::Duration};
use crate::dbus_charge::{OrgAsuslinuxDaemonNotifyCharge, OrgAsuslinuxDaemon as OrgAsuslinuxDaemonCharge};
use crate::dbus_gfx::{
OrgAsuslinuxDaemon as OrgAsuslinuxDaemonGfx, OrgAsuslinuxDaemonNotifyAction,
OrgAsuslinuxDaemonNotifyGfx,
};
use crate::dbus_ledmode::{
OrgAsuslinuxDaemon as OrgAsuslinuxDaemonLed, OrgAsuslinuxDaemonNotifyLed,
};
use crate::dbus_profile::{
OrgAsuslinuxDaemon as OrgAsuslinuxDaemonProfile, OrgAsuslinuxDaemonNotifyProfile,
};
// Signals separated out
pub struct CtrlSignals {
pub gfx_vendor_signal: Arc<Mutex<Option<String>>>,
pub gfx_action_signal: Arc<Mutex<Option<String>>>,
pub profile_signal: Arc<Mutex<Option<String>>>,
pub ledmode_signal: Arc<Mutex<Option<AuraModes>>>,
pub charge_signal: Arc<Mutex<Option<u8>>>,
}
impl CtrlSignals {
#[inline]
pub fn new(connection: &Connection) -> Result<Self, Box<dyn Error>> {
let proxy = connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Gfx",
Duration::from_millis(5000),
);
let gfx_vendor_signal = Arc::new(Mutex::new(None));
let gfx_res1 = gfx_vendor_signal.clone();
let _x = proxy.match_signal(
move |sig: OrgAsuslinuxDaemonNotifyGfx, _: &Connection, _: &Message| {
if let Ok(mut lock) = gfx_res1.lock() {
*lock = Some(sig.vendor);
}
true
},
)?;
let gfx_action_signal = Arc::new(Mutex::new(None));
let gfx_res1 = gfx_action_signal.clone();
let _x = proxy.match_signal(
move |sig: OrgAsuslinuxDaemonNotifyAction, _: &Connection, _: &Message| {
if let Ok(mut lock) = gfx_res1.lock() {
*lock = Some(sig.action);
}
true
},
)?;
//
let proxy = connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Profile",
Duration::from_millis(5000),
);
let profile_signal = Arc::new(Mutex::new(None));
let prof_res1 = profile_signal.clone();
let _x = proxy.match_signal(
move |sig: OrgAsuslinuxDaemonNotifyProfile, _: &Connection, _: &Message| {
if let Ok(mut lock) = prof_res1.lock() {
*lock = Some(sig.profile);
}
true
},
)?;
//
let proxy = connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Led",
Duration::from_millis(5000),
);
let ledmode_signal = Arc::new(Mutex::new(None));
let led_res1 = ledmode_signal.clone();
let _x = proxy.match_signal(
move |sig: OrgAsuslinuxDaemonNotifyLed, _: &Connection, _: &Message| {
if let Ok(mut lock) = led_res1.lock() {
if let Ok(dat) = serde_json::from_str(&sig.data) {
*lock = Some(dat);
}
}
true
},
)?;
//
let proxy = connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Charge",
Duration::from_millis(5000),
);
let charge_signal = Arc::new(Mutex::new(None));
let charge_res1 = charge_signal.clone();
let _x = proxy.match_signal(
move |sig: OrgAsuslinuxDaemonNotifyCharge, _: &Connection, _: &Message| {
if let Ok(mut lock) = charge_res1.lock() {
*lock = Some(sig.limit);
}
true
},
)?;
Ok(CtrlSignals {
gfx_vendor_signal,
gfx_action_signal,
profile_signal,
ledmode_signal,
charge_signal,
})
}
}
/// Simplified way to write a effect block
pub struct AuraDbusClient {
connection: Box<Connection>,
block_time: u64,
stop: Arc<AtomicBool>,
signals: CtrlSignals,
}
impl AuraDbusClient {
#[inline]
pub fn new() -> Result<Self, Box<dyn Error>> {
let connection = Connection::new_system()?;
let stop = Arc::new(AtomicBool::new(false));
let match_rule = dbus::message::MatchRule::new_signal(DBUS_IFACE, "NotifyLed");
let stop1 = stop.clone();
connection.add_match(match_rule, move |_: (), _, msg| {
if msg.read1::<&str>().is_ok() {
stop1.clone().store(true, Ordering::Relaxed);
}
true
})?;
let signals = CtrlSignals::new(&connection)?;
Ok(AuraDbusClient {
connection: Box::new(connection),
block_time: 33333,
stop,
signals,
})
}
pub fn wait_gfx_changed(&self) -> Result<String, Box<dyn Error>> {
loop {
self.connection.process(Duration::from_micros(500))?;
if let Ok(lock) = self.signals.gfx_action_signal.lock() {
if let Some(stuff) = lock.as_ref() {
return Ok(stuff.to_string());
}
}
}
}
/// This method must always be called before the very first write to initialise
/// the keyboard LED EC in the correct mode
#[inline]
pub fn init_effect(&self) -> Result<(), Box<dyn std::error::Error>> {
let mode = AuraModes::PerKey(vec![vec![]]);
let proxy = self.connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Led",
Duration::from_millis(5000),
);
proxy.set_led_mode(&serde_json::to_string(&mode)?)?;
Ok(())
}
/// Write a single colour block.
///
/// Intentionally blocks for 10ms after sending to allow the block to
/// be written to the keyboard EC. This should not be async.
#[inline]
pub fn write_colour_block(
&mut self,
key_colour_array: &KeyColourArray,
) -> Result<(), Box<dyn Error>> {
let group = key_colour_array.get();
let mut vecs = Vec::with_capacity(group.len());
for v in group {
vecs.push(v.to_vec());
}
let mode = AuraModes::PerKey(vecs);
self.write_keyboard_leds(&mode)?;
thread::sleep(Duration::from_micros(self.block_time));
self.connection.process(Duration::from_micros(500))?;
if self.stop.load(Ordering::Relaxed) {
println!("Keyboard backlight was changed, exiting");
std::process::exit(1)
}
Ok(())
}
#[inline]
pub fn write_keyboard_leds(&self, mode: &AuraModes) -> Result<(), Box<dyn std::error::Error>> {
let proxy = self.connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Led",
Duration::from_millis(5000),
);
proxy.set_led_mode(&serde_json::to_string(mode)?)?;
Ok(())
}
#[inline]
pub fn get_gfx_pwr(&self) -> Result<String, Box<dyn std::error::Error>> {
let proxy = self.connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Gfx",
Duration::from_millis(5000),
);
let x = proxy.power().unwrap();
Ok(x)
}
#[inline]
pub fn get_gfx_mode(&self) -> Result<String, Box<dyn std::error::Error>> {
let proxy = self.connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Gfx",
Duration::from_millis(5000),
);
let x = proxy.vendor().unwrap();
Ok(x)
}
#[inline]
pub fn write_gfx_mode(&self, vendor: GfxVendors) -> Result<(), Box<dyn std::error::Error>> {
let proxy = self.connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Gfx",
Duration::from_millis(5000),
);
proxy.set_vendor(<&str>::from(&vendor))?;
Ok(())
}
#[inline]
pub fn write_fan_mode(&self, level: u8) -> Result<(), Box<dyn std::error::Error>> {
let proxy = self.connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Profile",
Duration::from_millis(5000),
);
proxy.set_profile(&serde_json::to_string(&ProfileEvent::ChangeMode(level))?)?;
Ok(())
}
#[inline]
pub fn write_profile_command(
&self,
cmd: &ProfileEvent,
) -> Result<(), Box<dyn std::error::Error>> {
let proxy = self.connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Profile",
Duration::from_millis(5000),
);
proxy.set_profile(&serde_json::to_string(cmd)?)?;
Ok(())
}
#[inline]
pub fn write_charge_limit(&self, level: u8) -> Result<(), Box<dyn std::error::Error>> {
let proxy = self.connection.with_proxy(
"org.asuslinux.Daemon",
"/org/asuslinux/Charge",
Duration::from_millis(5000),
);
proxy.set_limit(level)?;
Ok(())
}
#[inline]
pub fn write_builtin_mode(&self, mode: &AuraModes) -> Result<(), Box<dyn std::error::Error>> {
self.write_keyboard_leds(mode)
}
#[inline]
pub fn write_brightness(&self, level: u8) -> Result<String, Box<dyn std::error::Error>> {
self.write_keyboard_leds(&AuraModes::LedBrightness(level))?;
Ok(String::new())
}
}

View File

@@ -1,46 +0,0 @@
// This code was autogenerated with `dbus-codegen-rust -s -d org.asuslinux.Daemon -f org.asuslinux.Daemon -c blocking -p /org/asuslinux/Charge -m None`, see https://github.com/diwic/dbus-rs
use dbus as dbus;
#[allow(unused_imports)]
use dbus::arg;
use dbus::blocking;
pub trait OrgAsuslinuxDaemon {
fn set_limit(&self, limit: u8) -> Result<(), dbus::Error>;
fn limit(&self) -> Result<i16, dbus::Error>;
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgAsuslinuxDaemon for blocking::Proxy<'a, C> {
fn set_limit(&self, limit: u8) -> Result<(), dbus::Error> {
self.method_call("org.asuslinux.Daemon", "SetLimit", (limit, ))
}
fn limit(&self) -> Result<i16, dbus::Error> {
self.method_call("org.asuslinux.Daemon", "Limit", ())
.and_then(|r: (i16, )| Ok(r.0, ))
}
}
#[derive(Debug)]
pub struct OrgAsuslinuxDaemonNotifyCharge {
pub limit: u8,
}
impl arg::AppendAll for OrgAsuslinuxDaemonNotifyCharge {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.limit, i);
}
}
impl arg::ReadAll for OrgAsuslinuxDaemonNotifyCharge {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgAsuslinuxDaemonNotifyCharge {
limit: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgAsuslinuxDaemonNotifyCharge {
const NAME: &'static str = "NotifyCharge";
const INTERFACE: &'static str = "org.asuslinux.Daemon";
}

View File

@@ -1,76 +0,0 @@
// This code was autogenerated with `dbus-codegen-rust -s -d org.asuslinux.Daemon -p /org/asuslinux/Gfx -m None -f org.asuslinux.Daemon -c blocking`, see https://github.com/diwic/dbus-rs
use dbus as dbus;
#[allow(unused_imports)]
use dbus::arg;
use dbus::blocking;
pub trait OrgAsuslinuxDaemon {
fn vendor(&self) -> Result<String, dbus::Error>;
fn power(&self) -> Result<String, dbus::Error>;
fn set_vendor(&self, vendor: &str) -> Result<(), dbus::Error>;
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgAsuslinuxDaemon for blocking::Proxy<'a, C> {
fn vendor(&self) -> Result<String, dbus::Error> {
self.method_call("org.asuslinux.Daemon", "Vendor", ())
.and_then(|r: (String, )| Ok(r.0, ))
}
fn power(&self) -> Result<String, dbus::Error> {
self.method_call("org.asuslinux.Daemon", "Power", ())
.and_then(|r: (String, )| Ok(r.0, ))
}
fn set_vendor(&self, vendor: &str) -> Result<(), dbus::Error> {
self.method_call("org.asuslinux.Daemon", "SetVendor", (vendor, ))
}
}
#[derive(Debug)]
pub struct OrgAsuslinuxDaemonNotifyGfx {
pub vendor: String,
}
impl arg::AppendAll for OrgAsuslinuxDaemonNotifyGfx {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.vendor, i);
}
}
impl arg::ReadAll for OrgAsuslinuxDaemonNotifyGfx {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgAsuslinuxDaemonNotifyGfx {
vendor: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgAsuslinuxDaemonNotifyGfx {
const NAME: &'static str = "NotifyGfx";
const INTERFACE: &'static str = "org.asuslinux.Daemon";
}
#[derive(Debug)]
pub struct OrgAsuslinuxDaemonNotifyAction {
pub action: String,
}
impl arg::AppendAll for OrgAsuslinuxDaemonNotifyAction {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.action, i);
}
}
impl arg::ReadAll for OrgAsuslinuxDaemonNotifyAction {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgAsuslinuxDaemonNotifyAction {
action: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgAsuslinuxDaemonNotifyAction {
const NAME: &'static str = "NotifyAction";
const INTERFACE: &'static str = "org.asuslinux.Daemon";
}

View File

@@ -1,52 +0,0 @@
// This code was autogenerated with `dbus-codegen-rust -s -d org.asuslinux.Daemon -f org.asuslinux.Daemon -c blocking -p /org/asuslinux/Led -m None`, see https://github.com/diwic/dbus-rs
use dbus as dbus;
#[allow(unused_imports)]
use dbus::arg;
use dbus::blocking;
pub trait OrgAsuslinuxDaemon {
fn set_led_mode(&self, data: &str) -> Result<(), dbus::Error>;
fn led_mode(&self) -> Result<String, dbus::Error>;
fn led_modes(&self) -> Result<String, dbus::Error>;
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgAsuslinuxDaemon for blocking::Proxy<'a, C> {
fn set_led_mode(&self, data: &str) -> Result<(), dbus::Error> {
self.method_call("org.asuslinux.Daemon", "SetLedMode", (data, ))
}
fn led_mode(&self) -> Result<String, dbus::Error> {
self.method_call("org.asuslinux.Daemon", "LedMode", ())
.and_then(|r: (String, )| Ok(r.0, ))
}
fn led_modes(&self) -> Result<String, dbus::Error> {
self.method_call("org.asuslinux.Daemon", "LedModes", ())
.and_then(|r: (String, )| Ok(r.0, ))
}
}
#[derive(Debug)]
pub struct OrgAsuslinuxDaemonNotifyLed {
pub data: String,
}
impl arg::AppendAll for OrgAsuslinuxDaemonNotifyLed {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.data, i);
}
}
impl arg::ReadAll for OrgAsuslinuxDaemonNotifyLed {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgAsuslinuxDaemonNotifyLed {
data: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgAsuslinuxDaemonNotifyLed {
const NAME: &'static str = "NotifyLed";
const INTERFACE: &'static str = "org.asuslinux.Daemon";
}

View File

@@ -1,51 +0,0 @@
// This code was autogenerated with `dbus-codegen-rust -s -d org.asuslinux.Daemon -f org.asuslinux.Daemon -c blocking -p /org/asuslinux/Profile -m None`, see https://github.com/diwic/dbus-rs
use dbus;
#[allow(unused_imports)]
use dbus::arg;
use dbus::blocking;
pub trait OrgAsuslinuxDaemon {
fn set_profile(&self, profile: &str) -> Result<(), dbus::Error>;
fn profile(&self) -> Result<String, dbus::Error>;
fn profiles(&self) -> Result<String, dbus::Error>;
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgAsuslinuxDaemon
for blocking::Proxy<'a, C>
{
fn set_profile(&self, profile: &str) -> Result<(), dbus::Error> {
self.method_call("org.asuslinux.Daemon", "SetProfile", (profile,))
}
fn profile(&self) -> Result<String, dbus::Error> {
self.method_call("org.asuslinux.Daemon", "Profile", ())
.and_then(|r: (String,)| Ok(r.0))
}
fn profiles(&self) -> Result<String, dbus::Error> {
self.method_call("org.asuslinux.Daemon", "Profiles", ())
.and_then(|r: (String,)| Ok(r.0))
}
}
#[derive(Debug)]
pub struct OrgAsuslinuxDaemonNotifyProfile {
pub profile: String,
}
impl arg::AppendAll for OrgAsuslinuxDaemonNotifyProfile {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.profile, i);
}
}
impl arg::ReadAll for OrgAsuslinuxDaemonNotifyProfile {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgAsuslinuxDaemonNotifyProfile { profile: i.read()? })
}
}
impl dbus::message::SignalArgs for OrgAsuslinuxDaemonNotifyProfile {
const NAME: &'static str = "NotifyProfile";
const INTERFACE: &'static str = "org.asuslinux.Daemon";
}

View File

@@ -1,40 +0,0 @@
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum AuraError {
ParseColour,
ParseSpeed,
ParseDirection,
ParseBrightness,
}
impl fmt::Display for AuraError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AuraError::ParseColour => write!(f, "Could not parse colour"),
AuraError::ParseSpeed => write!(f, "Could not parse speed"),
AuraError::ParseDirection => write!(f, "Could not parse direction"),
AuraError::ParseBrightness => write!(f, "Could not parse brightness"),
}
}
}
impl Error for AuraError {}
#[derive(Debug)]
pub enum GraphicsError {
ParseVendor,
}
impl fmt::Display for GraphicsError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GraphicsError::ParseVendor => write!(f, "Could not parse vendor name"),
}
}
}
impl Error for GraphicsError {}

View File

@@ -1,259 +0,0 @@
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
pub static DBUS_PATH: &str = "/org/asuslinux/Daemon";
pub static DBUS_IFACE: &str = "org.asuslinux.Daemon";
pub const LED_MSG_LEN: usize = 17;
pub mod aura_modes;
use aura_modes::AuraModes;
pub mod profile;
/// Contains mostly only what is required for parsing CLI options
pub mod cli_options;
/// Enables you to create fancy RGB effects
pub mod fancy;
/// The main dbus group for system controls, e.g, fan control, keyboard LED's
pub mod core_dbus;
/// Specific dbus for writing to the AniMe Matrix display (if supported)
pub mod anime_dbus;
/// Helper functions for the AniMe display
pub mod anime_matrix;
pub mod dbus_gfx;
pub mod dbus_ledmode;
pub mod dbus_profile;
pub mod dbus_charge;
pub mod error;
// static LED_INIT1: [u8; 2] = [0x5d, 0xb9];
// static LED_INIT2: &str = "]ASUS Tech.Inc."; // ] == 0x5d
// static LED_INIT3: [u8; 6] = [0x5d, 0x05, 0x20, 0x31, 0, 0x08];
// static LED_INIT4: &str = "^ASUS Tech.Inc."; // ^ == 0x5e
// static LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
/// Writes aout the correct byte string for brightness
///
/// The HID descriptor looks like:
///
/// ```ignore
/// 0x06, 0x31, 0xFF, // Usage Page (Vendor Defined 0xFF31)
/// 0x09, 0x76, // Usage (0x76)
/// 0xA1, 0x01, // Collection (Application)
/// 0x85, 0x5A, // Report ID (90)
/// 0x19, 0x00, // Usage Minimum (0x00)
/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
/// 0x15, 0x00, // Logical Minimum (0)
/// 0x26, 0xFF, 0x00, // Logical Maximum (255)
/// 0x75, 0x08, // Report Size (8)
/// 0x95, 0x05, // Report Count (5)
/// 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
/// 0x19, 0x00, // Usage Minimum (0x00)
/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
/// 0x15, 0x00, // Logical Minimum (0)
/// 0x26, 0xFF, 0x00, // Logical Maximum (255)
/// 0x75, 0x08, // Report Size (8)
/// 0x95, 0x3F, // Report Count (63)
/// 0xB1, 0x00, // Feature (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
/// 0xC0, // End Collection
/// ```
pub fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
// TODO: check brightness range
[
0x5A, 0xBA, 0xC5, 0xC4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]
}
/// Parses `AuraCommands` in to packet data
///
/// Byte structure:
///
/// ```ignore
/// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12|
/// |---|---|---|---|---|---|---|---|---|---|---|---|---|
/// |5d |b3 |00 |03 |ff |00 |00 |00 |00 |00 |00 |ff |00 |
/// ```
///
/// Bytes 0 and 1 should always be 5d, b3
///
/// On multizone laptops byte 2 is the zone number, RGB in usual
/// place, byte 3 set to zero
///
/// Byte 3 sets the mode type:
/// - 00 = static
/// - 01 = breathe (can set two colours)
/// - 02 = strobe (through all colours)
/// - 03 = rainbow
/// - 04 = star (byte 9 sets rain colour)
/// - 05 = rain keys, red, white, turquoise
/// - 06 = pressed keys light up and fade
/// - 07 = pressed key emits laser
/// - 08 = pressed key emits water ripple
/// - 09 = no effect/not used
/// - 0a fast pulse (no speed setting)
/// - 0b vertical line racing to right (no speed setting)
/// - 0c wider vertical line racing to right (no speed setting)
///
/// Bytes 4, 5, 6 are Red, Green, Blue
///
/// Byte 7 sets speed from
/// - 0x00 = Off
/// - 0xe1 = Slow
/// - 0xeb = Medium
/// - 0xf5 = Fast
///
/// Byte 8 sets rainbow direction:
/// - 0x00 = rightwards
/// - 0x01 = leftwards
/// - 0x02 = upwards
/// - 0x03 = downwards
///
/// Bytes 10, 11, 12 are Red, Green, Blue for second colour if mode supports it
///
/// The HID descriptor looks like:
/// ```ignore
/// 0x06, 0x31, 0xFF, // Usage Page (Vendor Defined 0xFF31)
/// 0x09, 0x79, // Usage (0x79)
/// 0xA1, 0x01, // Collection (Application)
/// 0x85, 0x5D, // Report ID (93)
/// 0x19, 0x00, // Usage Minimum (0x00)
/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
/// 0x15, 0x00, // Logical Minimum (0)
/// 0x26, 0xFF, 0x00, // Logical Maximum (255)
/// 0x75, 0x08, // Report Size (8)
/// 0x95, 0x1F, // Report Count (31)
/// 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
/// 0x19, 0x00, // Usage Minimum (0x00)
/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
/// 0x15, 0x00, // Logical Minimum (0)
/// 0x26, 0xFF, 0x00, // Logical Maximum (255)
/// 0x75, 0x08, // Report Size (8)
/// 0x95, 0x3F, // Report Count (63)
/// 0x91, 0x00, // Output (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
/// 0x19, 0x00, // Usage Minimum (0x00)
/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
/// 0x15, 0x00, // Logical Minimum (0)
/// 0x26, 0xFF, 0x00, // Logical Maximum (255)
/// 0x75, 0x08, // Report Size (8)
/// 0x95, 0x3F, // Report Count (63)
/// 0xB1, 0x00, // Feature (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
/// 0xC0, // End Collection
/// ```
///
/// This descriptor is also used for the per-key LED settings
impl From<&AuraModes> for [u8; LED_MSG_LEN] {
fn from(mode: &AuraModes) -> Self {
let mut msg = [0u8; LED_MSG_LEN];
msg[0] = 0x5d;
msg[1] = 0xb3;
match mode {
AuraModes::LedBrightness(n) => return aura_brightness_bytes(*n),
AuraModes::Static(_) => msg[3] = 0x00,
AuraModes::Breathe(_) => msg[3] = 0x01,
AuraModes::Strobe(_) => msg[3] = 0x02,
AuraModes::Rainbow(_) => msg[3] = 0x03,
AuraModes::Star(_) => msg[3] = 0x04,
AuraModes::Rain(_) => msg[3] = 0x05,
AuraModes::Highlight(_) => msg[3] = 0x06,
AuraModes::Laser(_) => msg[3] = 0x07,
AuraModes::Ripple(_) => msg[3] = 0x08,
AuraModes::Pulse(_) => msg[3] = 0x0a,
AuraModes::Comet(_) => msg[3] = 0x0b,
AuraModes::Flash(_) => msg[3] = 0x0c,
_ => panic!("Mode not convertable to 1D array: {}", <&str>::from(mode)),
}
match mode {
AuraModes::Rainbow(settings) => {
msg[7] = settings.speed as u8;
msg[8] = settings.direction as u8;
}
AuraModes::Star(settings) => {
msg[4] = settings.colour.0;
msg[5] = settings.colour.1;
msg[6] = settings.colour.2;
msg[7] = settings.speed as u8;
msg[9] = settings.colour2.2;
}
AuraModes::Breathe(settings) => {
msg[4] = settings.colour.0;
msg[5] = settings.colour.1;
msg[6] = settings.colour.2;
msg[7] = settings.speed as u8;
msg[10] = settings.colour2.0;
msg[11] = settings.colour2.1;
msg[12] = settings.colour2.2;
}
AuraModes::Strobe(settings) | AuraModes::Rain(settings) => {
msg[7] = settings.speed as u8;
}
AuraModes::Highlight(settings)
| AuraModes::Laser(settings)
| AuraModes::Ripple(settings) => {
msg[4] = settings.colour.0;
msg[5] = settings.colour.1;
msg[6] = settings.colour.2;
msg[7] = settings.speed as u8;
}
AuraModes::Static(settings)
| AuraModes::Pulse(settings)
| AuraModes::Comet(settings)
| AuraModes::Flash(settings) => {
msg[4] = settings.colour.0;
msg[5] = settings.colour.1;
msg[6] = settings.colour.2;
}
_ => panic!("Mode not convertable to 1D array: {}", <&str>::from(mode)),
}
msg
}
}
impl From<AuraModes> for [u8; LED_MSG_LEN] {
#[inline]
fn from(mode: AuraModes) -> Self {
<[u8; LED_MSG_LEN]>::from(&mode)
}
}
impl From<AuraModes> for [[u8; LED_MSG_LEN]; 4] {
#[inline]
fn from(mode: AuraModes) -> Self {
<[[u8; LED_MSG_LEN]; 4]>::from(&mode)
}
}
impl From<&AuraModes> for [[u8; LED_MSG_LEN]; 4] {
#[inline]
fn from(mode: &AuraModes) -> Self {
let mut msg = [[0u8; LED_MSG_LEN]; 4];
for (i, row) in msg.iter_mut().enumerate() {
row[0] = 0x5d;
row[1] = 0xb3;
row[2] = i as u8 + 1;
}
match mode {
AuraModes::MultiStatic(settings) => {
msg[0][4] = settings.colour1.0;
msg[0][5] = settings.colour1.1;
msg[0][6] = settings.colour1.2;
msg[1][4] = settings.colour2.0;
msg[1][5] = settings.colour2.1;
msg[1][6] = settings.colour2.2;
msg[2][4] = settings.colour3.0;
msg[2][5] = settings.colour3.1;
msg[2][6] = settings.colour3.2;
msg[3][4] = settings.colour4.0;
msg[3][5] = settings.colour4.1;
msg[3][6] = settings.colour4.2;
}
_ => panic!("Mode not convertable to 2D array: {}", <&str>::from(mode)),
}
msg
}
}

View File

@@ -1,95 +0,0 @@
use gumdrop::Options;
use rog_fan_curve::{Curve, Fan};
use serde_derive::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Serialize, Deserialize)]
pub enum ProfileEvent {
Cli(ProfileCommand),
ChangeMode(u8),
Toggle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FanLevel {
Normal,
Boost,
Silent,
}
impl FromStr for FanLevel {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"normal" => Ok(FanLevel::Normal),
"boost" => Ok(FanLevel::Boost),
"silent" => Ok(FanLevel::Silent),
_ => Err("Invalid fan level"),
}
}
}
impl From<u8> for FanLevel {
fn from(n: u8) -> Self {
match n {
0 => FanLevel::Normal,
1 => FanLevel::Boost,
2 => FanLevel::Silent,
_ => FanLevel::Normal,
}
}
}
impl From<FanLevel> for u8 {
fn from(n: FanLevel) -> Self {
match n {
FanLevel::Normal => 0,
FanLevel::Boost => 1,
FanLevel::Silent => 2,
}
}
}
impl From<&FanLevel> for u8 {
fn from(n: &FanLevel) -> Self {
match n {
FanLevel::Normal => 0,
FanLevel::Boost => 1,
FanLevel::Silent => 2,
}
}
}
fn parse_fan_curve(data: &str) -> Result<Curve, String> {
let curve = Curve::from_config_str(data)?;
if let Err(err) = curve.check_safety(Fan::Cpu) {
return Err(format!("Unsafe curve {:?}", err));
}
if let Err(err) = curve.check_safety(Fan::Gpu) {
return Err(format!("Unsafe curve {:?}", err));
}
Ok(curve)
}
#[derive(Debug, Clone, Options, Serialize, Deserialize)]
pub struct ProfileCommand {
#[options(help = "print help message")]
help: bool,
#[options(help = "create the profile if it doesn't exist")]
pub create: bool,
#[options(help = "enable or disable cpu turbo")]
pub turbo: Option<bool>,
#[options(help = "set min cpu scaling (intel)")]
pub min_percentage: Option<u8>,
#[options(help = "set max cpu scaling (intel)")]
pub max_percentage: Option<u8>,
#[options(meta = "PWR", help = "<silent, normal, boost>")]
pub preset: Option<FanLevel>,
#[options(parse(try_from_str = "parse_fan_curve"), help = "set fan curve")]
pub curve: Option<Curve>,
#[options(free)]
pub profile: Option<String>,
}

View File

@@ -1,17 +1,22 @@
[package]
name = "asus-notify"
version = "1.0.1"
version = "3.1.0"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
zbus = "^2.2"
# serialisation
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
notify-rust = "^4.0.0"
dbus = { version = "^0.8" }
asus-nb = { path = "../asus-nb" }
asus-nb-ctrl = { path = "../asus-nb-ctrl" }
rog_dbus = { path = "../rog-dbus" }
rog_aura = { path = "../rog-aura" }
rog_supported = { path = "../rog-supported" }
rog_profiles = { path = "../rog-profiles" }
smol = "^1.2"
[dependencies.notify-rust]
version = "^4.3"
default-features = false
features = ["z"]

View File

@@ -1,87 +1,146 @@
use asus_nb::core_dbus::CtrlSignals;
use daemon::config::{Config, Profile};
use dbus::blocking::Connection;
use notify_rust::{Hint, Notification, NotificationHandle};
use std::error::Error;
use std::time::Duration;
use rog_aura::AuraEffect;
use rog_dbus::{
zbus_charge::ChargeProxy, zbus_led::LedProxy, zbus_profile::ProfileProxy,
zbus_rogbios::RogBiosProxy,
};
use rog_profiles::Profile;
use smol::{future, Executor};
use std::{
error::Error,
sync::{Arc, Mutex},
};
use zbus::export::futures_util::StreamExt;
const NOTIF_HEADER: &str = "ROG Control";
macro_rules! notify {
($notifier:ident, $last_notif:ident, $data:expr) => {
if let Some(notif) = $last_notif.take() {
notif.close();
}
if let Ok(x) = $notifier($data) {
$last_notif.replace(x);
}
};
}
macro_rules! base_notification {
($body:expr) => {
Notification::new()
.summary(NOTIF_HEADER)
.body($body)
.timeout(2000)
.show()
};
}
type SharedHandle = Arc<Mutex<Option<NotificationHandle>>>;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut cfg = Config::read_new()?;
let mut last_profile = String::new();
println!("asus-notify version {}", env!("CARGO_PKG_VERSION"));
println!(" rog-dbus version {}", rog_dbus::VERSION);
let connection = Connection::new_system()?;
let signals = CtrlSignals::new(&connection)?;
let last_notification: SharedHandle = Arc::new(Mutex::new(None));
let mut last_profile_notif: Option<NotificationHandle> = None;
let mut last_led_notif: Option<NotificationHandle> = None;
let mut last_gfx_notif: Option<NotificationHandle> = None;
let mut last_chrg_notif: Option<NotificationHandle> = None;
let 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();
// Charge notif
let x = last_notification.clone();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = ChargeProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_charge().await {
p.for_each(|e| {
if let Ok(out) = e.args() {
if let Ok(ref mut lock) = x.try_lock() {
notify!(do_charge_notif, lock, &out.limit);
}
}
future::ready(())
})
.await;
};
})
.detach();
// Profile notif
let x = last_notification.clone();
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = ProfileProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_profile().await {
p.for_each(|e| {
if let Ok(out) = e.args() {
if let Ok(ref mut lock) = x.try_lock() {
notify!(do_thermal_notif, lock, &out.profile);
}
}
future::ready(())
})
.await;
};
})
.detach();
// LED notif
executor
.spawn(async move {
let conn = zbus::Connection::system().await.unwrap();
let proxy = LedProxy::new(&conn).await.unwrap();
if let Ok(p) = proxy.receive_notify_led().await {
p.for_each(|e| {
if let Ok(out) = e.args() {
if let Ok(ref mut lock) = last_notification.try_lock() {
notify!(do_led_notif, lock, &out.data);
}
}
future::ready(())
})
.await;
};
})
.detach();
loop {
std::thread::sleep(Duration::from_millis(100));
connection.process(std::time::Duration::from_millis(200))?;
if let Ok(mut lock) = signals.gfx_vendor_signal.lock() {
if let Some(vendor) = lock.take() {
if let Some(notif) = last_gfx_notif.take() {
notif.close();
}
let x = do_notif(&format!("Graphics mode changed to {}", vendor))?;
last_gfx_notif = Some(x);
}
}
if let Ok(mut lock) = signals.charge_signal.lock() {
if let Some(limit) = lock.take() {
if let Some(notif) = last_chrg_notif.take() {
notif.close();
}
let x = do_notif(&format!("Battery charge limit changed to {}", limit))?;
last_led_notif = Some(x);
}
}
if let Ok(mut lock) = signals.ledmode_signal.lock() {
if let Some(ledmode) = lock.take() {
if let Some(notif) = last_led_notif.take() {
notif.close();
}
let x = do_notif(&format!(
"Keyboard LED mode changed to {}",
<&str>::from(&ledmode)
))?;
last_led_notif = Some(x);
}
}
// We need to do the config read because of a limitation preventing
// easy dbus notification from the profile controller
cfg.read();
if last_profile != cfg.active_profile {
if let Some(notif) = last_profile_notif.take() {
notif.close();
}
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
let x = do_thermal_notif(&profile, &cfg.active_profile)?;
last_profile_notif = Some(x);
last_profile = cfg.active_profile.clone();
}
}
smol::block_on(executor.tick());
}
}
fn do_thermal_notif(profile: &Profile, label: &str) -> Result<NotificationHandle, Box<dyn Error>> {
let fan = profile.fan_preset;
let turbo = if profile.turbo { "enabled" } else { "disabled" };
let icon = match fan {
0 => "asus_notif_yellow",
1 => "asus_notif_red",
2 => "asus_notif_green",
_ => "asus_notif_red",
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
let icon = match profile {
Profile::Balanced => "asus_notif_yellow",
Profile::Performance => "asus_notif_red",
Profile::Quiet => "asus_notif_green",
};
let profile: &str = (*profile).into();
let x = Notification::new()
.summary("ASUS ROG")
.body(&format!("Thermal profile changed to {}, turbo {}", label.to_uppercase(), turbo))
.body(&format!(
"Thermal profile changed to {}",
profile.to_uppercase(),
))
.hint(Hint::Resident(true))
.timeout(2000)
.hint(Hint::Category("device".into()))
@@ -91,11 +150,17 @@ fn do_thermal_notif(profile: &Profile, label: &str) -> Result<NotificationHandle
Ok(x)
}
fn do_notif(body: &str) -> Result<NotificationHandle, Box<dyn Error>> {
let x = Notification::new()
.summary("ASUS ROG")
.body(body)
.timeout(2000)
.show()?;
Ok(x)
fn do_led_notif(ledmode: &AuraEffect) -> Result<NotificationHandle, notify_rust::error::Error> {
base_notification!(&format!(
"Keyboard LED mode changed to {}",
ledmode.mode_name()
))
}
fn do_charge_notif(limit: &u8) -> Result<NotificationHandle, notify_rust::error::Error> {
base_notification!(&format!("Battery charge limit changed to {}", limit))
}
fn do_post_sound_notif(on: &bool) -> Result<NotificationHandle, notify_rust::error::Error> {
base_notification!(&format!("BIOS Post sound {}", on))
}

26
asusctl/Cargo.toml Normal file
View File

@@ -0,0 +1,26 @@
[package]
name = "asusctl"
version = "4.0.7"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
zbus = "^2.2"
rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_dbus = { path = "../rog-dbus" }
rog_profiles = { path = "../rog-profiles" }
rog_supported = { path = "../rog-supported" }
daemon = { path = "../daemon" }
gumdrop = "^0.8"
toml = "^0.5.8"
sysfs-class = "^0.1.2"
[dev-dependencies]
tinybmp = "^0.3.3"
glam = "0.20.5"
rog_dbus = { path = "../rog-dbus" }
gif = "^0.11.2"

View File

@@ -0,0 +1,26 @@
use std::{env, error::Error, path::Path, process::exit};
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
use rog_dbus::RogDbusClientBlocking;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClientBlocking::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 3 {
println!("Usage: <filepath> <brightness>");
println!("e.g, asusctl/examples/doom_large.png 0.8");
exit(-1);
}
let matrix =
AnimeDiagonal::from_png(Path::new(&args[1]), None, args[2].parse::<f32>().unwrap())?;
client
.proxies()
.anime()
.write(<AnimeDataBuffer>::from(&matrix))
.unwrap();
Ok(())
}

View File

@@ -0,0 +1,32 @@
use std::{thread::sleep, time::Duration};
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
use rog_dbus::RogDbusClientBlocking;
// In usable data:
// Top row start at 1, ends at 32
// 74w x 36h diagonal used by the windows app
fn main() {
let (client, _) = RogDbusClientBlocking::new().unwrap();
for step in (2..50).rev() {
let mut matrix = AnimeDiagonal::new(None);
for c in (0..60).into_iter().step_by(step) {
for i in matrix.get_mut().iter_mut() {
i[c] = 50;
}
}
for c in (0..35).into_iter().step_by(step) {
for i in matrix.get_mut()[c].iter_mut() {
*i = 50;
}
}
let m = <AnimeDataBuffer>::from(&matrix);
client.proxies().anime().write(m).unwrap();
sleep(Duration::from_millis(300));
}
}

View File

@@ -0,0 +1,42 @@
use std::{env, path::Path, thread::sleep};
use rog_anime::{ActionData, ActionLoader, Sequences};
use rog_dbus::RogDbusClientBlocking;
fn main() {
let (client, _) = RogDbusClientBlocking::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 3 {
println!("Please supply filepath and brightness");
return;
}
let path = Path::new(&args[1]);
let brightness = args[2].parse::<f32>().unwrap();
let mut seq = Sequences::new();
seq.insert(
0,
&ActionLoader::AsusAnimation {
file: path.into(),
time: rog_anime::AnimTime::Infinite,
brightness,
},
)
.unwrap();
loop {
for action in seq.iter() {
if let ActionData::Animation(frames) = action {
for frame in frames.frames() {
client
.proxies()
.anime()
.write(frame.frame().clone())
.unwrap();
sleep(frame.delay());
}
}
}
}
}

View File

@@ -0,0 +1,44 @@
use rog_anime::{AnimeDataBuffer, AnimeGrid};
use rog_dbus::RogDbusClientBlocking;
// In usable data:
// Top row start at 1, ends at 32
// 74w x 36h diagonal used by the windows app
fn main() {
let (client, _) = RogDbusClientBlocking::new().unwrap();
let mut matrix = AnimeGrid::new(None);
let tmp = matrix.get_mut();
let mut i = 0;
for (y, row) in tmp.iter_mut().enumerate() {
if y % 2 == 0 && i + 1 != row.len() - 1 {
i += 1;
dbg!(i);
}
row[row.len() - i] = 0x22;
if i > 5 {
row[row.len() - i + 5] = 0x22;
}
if i > 10 {
row[row.len() - i + 10] = 0x22;
}
if i > 15 {
row[row.len() - i + 15] = 0x22;
}
if i > 20 {
row[row.len() - i + 20] = 0x22;
}
if i > 25 {
row[row.len() - i + 25] = 0x22;
}
}
let matrix = <AnimeDataBuffer>::from(matrix);
client.proxies().anime().write(matrix).unwrap();
}

View File

@@ -0,0 +1,129 @@
use rog_anime::AnimeDataBuffer;
use rog_dbus::RogDbusClientBlocking;
// In usable data:
// Top row start at 1, ends at 32
fn main() {
let (client, _) = RogDbusClientBlocking::new().unwrap();
let mut matrix = AnimeDataBuffer::new();
matrix.get_mut()[1] = 100; // start = 1
for n in matrix.get_mut()[2..32].iter_mut() {
*n = 250;
}
matrix.get_mut()[32] = 100; // end
matrix.get_mut()[34] = 100; // start x = 0
matrix.get_mut()[66] = 100; // end
matrix.get_mut()[69] = 100; // start x = 1
matrix.get_mut()[101] = 100; // end
matrix.get_mut()[102] = 100; // start
matrix.get_mut()[134] = 100; // end
matrix.get_mut()[137] = 100; // start
matrix.get_mut()[169] = 100; // end
matrix.get_mut()[170] = 100; // start
matrix.get_mut()[202] = 100; // end
matrix.get_mut()[204] = 100; // start
matrix.get_mut()[236] = 100; // end
matrix.get_mut()[237] = 100; // start
matrix.get_mut()[268] = 100; // end
matrix.get_mut()[270] = 100; // start
matrix.get_mut()[301] = 100; // end
matrix.get_mut()[302] = 100; // start
matrix.get_mut()[332] = 100; // end
matrix.get_mut()[334] = 100; // start
matrix.get_mut()[364] = 100; // end
matrix.get_mut()[365] = 100; // start
matrix.get_mut()[394] = 100; // end
matrix.get_mut()[396] = 100; // start
matrix.get_mut()[425] = 100; // end
matrix.get_mut()[426] = 100; // start
matrix.get_mut()[454] = 100; // end
matrix.get_mut()[456] = 100; // start
matrix.get_mut()[484] = 100; // end
matrix.get_mut()[485] = 100; // start
matrix.get_mut()[512] = 100; // end
matrix.get_mut()[514] = 100; // start
matrix.get_mut()[541] = 100; // end
matrix.get_mut()[542] = 100; // start
matrix.get_mut()[568] = 100; // end
matrix.get_mut()[570] = 100; // start
matrix.get_mut()[596] = 100; // end
matrix.get_mut()[597] = 100; // start
matrix.get_mut()[622] = 100; // end
matrix.get_mut()[624] = 100; // start
matrix.get_mut()[649] = 100; // end
matrix.get_mut()[650] = 100; // start
matrix.get_mut()[674] = 100; // end
matrix.get_mut()[676] = 100; // start
matrix.get_mut()[700] = 100; // end
matrix.get_mut()[701] = 100; // start
matrix.get_mut()[724] = 100; // end
matrix.get_mut()[726] = 100; // start
matrix.get_mut()[749] = 100; // end
matrix.get_mut()[750] = 100; // start
matrix.get_mut()[772] = 100; // end
matrix.get_mut()[774] = 100; // start
matrix.get_mut()[796] = 100; // end
matrix.get_mut()[797] = 100; // start
matrix.get_mut()[818] = 100; // end
matrix.get_mut()[820] = 100; // start
matrix.get_mut()[841] = 100; // end
matrix.get_mut()[842] = 100; // start
matrix.get_mut()[862] = 100; // end
matrix.get_mut()[864] = 100; // start
matrix.get_mut()[884] = 100; // end
matrix.get_mut()[885] = 100; // start
matrix.get_mut()[904] = 100; // end
matrix.get_mut()[906] = 100; // start
matrix.get_mut()[925] = 100; // end
matrix.get_mut()[926] = 100; // start
matrix.get_mut()[944] = 100; // end
matrix.get_mut()[946] = 100; // start
matrix.get_mut()[964] = 100; // end
matrix.get_mut()[965] = 100; // start
matrix.get_mut()[982] = 100; // end
matrix.get_mut()[984] = 100; // start
matrix.get_mut()[1001] = 100; // end
matrix.get_mut()[1002] = 100; // start
matrix.get_mut()[1018] = 100; // end
matrix.get_mut()[1020] = 100; // start
matrix.get_mut()[1036] = 100; // end
matrix.get_mut()[1037] = 100; // start
matrix.get_mut()[1052] = 100; // end
matrix.get_mut()[1054] = 100; // start
matrix.get_mut()[1069] = 100; // end
matrix.get_mut()[1070] = 100; // start
matrix.get_mut()[1084] = 100; // end
matrix.get_mut()[1086] = 100; // start
matrix.get_mut()[1100] = 100; // end
matrix.get_mut()[1101] = 100; // start
matrix.get_mut()[1114] = 100; // end
matrix.get_mut()[1116] = 100; // start
matrix.get_mut()[1129] = 100; // end
matrix.get_mut()[1130] = 100; // start
matrix.get_mut()[1142] = 100; // end
matrix.get_mut()[1144] = 100; // start
matrix.get_mut()[1156] = 100; // end
matrix.get_mut()[1157] = 100; // start
matrix.get_mut()[1168] = 100; // end
matrix.get_mut()[1170] = 100; // start
matrix.get_mut()[1181] = 100; // end
matrix.get_mut()[1182] = 100; // start
matrix.get_mut()[1192] = 100; // end
matrix.get_mut()[1194] = 100; // start
matrix.get_mut()[1204] = 100; // end
matrix.get_mut()[1205] = 100; // start
matrix.get_mut()[1214] = 100; // end
matrix.get_mut()[1216] = 100; // start
matrix.get_mut()[1225] = 100; // end
matrix.get_mut()[1226] = 100; // start
matrix.get_mut()[1234] = 100; // end
matrix.get_mut()[1236] = 100; // start
for n in matrix.get_mut()[1237..1244].iter_mut() {
*n = 250;
}
matrix.get_mut()[1244] = 100; // end
println!("{:?}", &matrix);
client.proxies().anime().write(matrix).unwrap();
}

View File

@@ -0,0 +1,36 @@
use std::{env, error::Error, path::Path, process::exit};
use rog_anime::{
AnimeDataBuffer, {AnimeImage, Vec2},
};
use rog_dbus::RogDbusClientBlocking;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClientBlocking::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 7 {
println!("Usage: <filepath> <scale> <angle> <x pos> <y pos> <brightness>");
println!("e.g, asusctl/examples/doom_large.png 0.9 0.4 0.0 0.0 0.8");
exit(-1);
}
let matrix = AnimeImage::from_png(
Path::new(&args[1]),
args[2].parse::<f32>().unwrap(),
args[3].parse::<f32>().unwrap(),
Vec2::new(
args[4].parse::<f32>().unwrap(),
args[5].parse::<f32>().unwrap(),
),
args[6].parse::<f32>().unwrap(),
)?;
client
.proxies()
.anime()
.write(<AnimeDataBuffer>::from(&matrix))
.unwrap();
Ok(())
}

View File

@@ -0,0 +1,45 @@
use std::{
env, error::Error, f32::consts::PI, path::Path, process::exit, thread::sleep, time::Duration,
};
use rog_anime::{
AnimeDataBuffer, {AnimeImage, Vec2},
};
use rog_dbus::RogDbusClientBlocking;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClientBlocking::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 7 {
println!("Usage: <filepath> <scale> <angle> <x pos> <y pos> <brightness>");
println!("e.g, asusctl/examples/doom_large.png 0.9 0.4 0.0 0.0 0.8");
exit(-1);
}
let mut matrix = AnimeImage::from_png(
Path::new(&args[1]),
args[2].parse::<f32>().unwrap(),
args[3].parse::<f32>().unwrap(),
Vec2::new(
args[4].parse::<f32>().unwrap(),
args[5].parse::<f32>().unwrap(),
),
args[6].parse::<f32>().unwrap(),
)?;
loop {
matrix.angle += 0.05;
if matrix.angle > PI * 2.0 {
matrix.angle = 0.0
}
matrix.update();
client
.proxies()
.anime()
.write(<AnimeDataBuffer>::from(&matrix))
.unwrap();
sleep(Duration::from_micros(500));
}
}

View File

@@ -1,7 +1,5 @@
use asus_nb::{
core_dbus::AuraDbusClient,
fancy::{GX502Layout, Key, KeyColourArray, KeyLayout},
};
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
use std::collections::LinkedList;
#[derive(Debug, Clone)]
@@ -54,7 +52,7 @@ impl Ball {
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut writer = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut colours = KeyColourArray::new();
@@ -62,7 +60,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut balls = [Ball::new(2, 1, 12), Ball::new(4, 6, 12)];
writer.init_effect()?;
dbus.proxies().led().init_effect()?;
let rows = layout.get_rows();
loop {
@@ -89,10 +87,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
*c.2 = 255;
};
}
dbus.proxies().led().set_per_key(&colours)?;
writer.write_colour_block(&colours)?;
// can change 100 times per second, so need to slow it down
std::thread::sleep(std::time::Duration::from_millis(30));
std::thread::sleep(std::time::Duration::from_millis(10));
}
}

View File

@@ -1,14 +1,12 @@
use asus_nb::{
core_dbus::AuraDbusClient,
fancy::{GX502Layout, KeyColourArray, KeyLayout},
};
use rog_aura::{GX502Layout, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut writer = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let layout = GX502Layout::default();
writer.init_effect()?;
dbus.proxies().led().init_effect()?;
let rows = layout.get_rows();
let mut column = 0;
@@ -25,7 +23,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
column += 1;
}
writer.write_colour_block(&key_colours)?;
std::thread::sleep(std::time::Duration::from_millis(30));
dbus.proxies().led().set_per_key(&key_colours)?;
}
}

View File

@@ -1,15 +1,13 @@
use asus_nb::{
core_dbus::AuraDbusClient,
fancy::{GX502Layout, Key, KeyColourArray, KeyLayout},
};
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut writer = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut key_colours = KeyColourArray::new();
let layout = GX502Layout::default();
writer.init_effect()?;
dbus.proxies().led().init_effect()?;
let rows = layout.get_rows();
loop {
for (r, row) in rows.iter().enumerate() {
@@ -48,7 +46,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
*key_colours.key(Key::S).unwrap().0 = 255;
*key_colours.key(Key::D).unwrap().0 = 255;
writer.write_colour_block(&key_colours)?;
dbus.proxies().led().set_per_key(&key_colours)?;
std::thread::sleep(std::time::Duration::from_millis(100));
}
}

View File

@@ -1,33 +1,31 @@
use asus_nb::{
core_dbus::AuraDbusClient,
fancy::{Key, KeyColourArray},
};
use rog_aura::{Key, KeyColourArray};
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut writer = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut key_colours = KeyColourArray::new();
writer.init_effect()?;
dbus.proxies().led().init_effect()?;
loop {
let count = 49;
for _ in 0..count {
*key_colours.key(Key::ROG).unwrap().0 += 5;
*key_colours.key(Key::Rog).unwrap().0 += 5;
*key_colours.key(Key::L).unwrap().0 += 5;
*key_colours.key(Key::I).unwrap().0 += 5;
*key_colours.key(Key::N).unwrap().0 += 5;
*key_colours.key(Key::U).unwrap().0 += 5;
*key_colours.key(Key::X).unwrap().0 += 5;
writer.write_colour_block(&key_colours)?;
dbus.proxies().led().set_per_key(&key_colours)?;
}
for _ in 0..count {
*key_colours.key(Key::ROG).unwrap().0 -= 5;
*key_colours.key(Key::Rog).unwrap().0 -= 5;
*key_colours.key(Key::L).unwrap().0 -= 5;
*key_colours.key(Key::I).unwrap().0 -= 5;
*key_colours.key(Key::N).unwrap().0 -= 5;
*key_colours.key(Key::U).unwrap().0 -= 5;
*key_colours.key(Key::X).unwrap().0 -= 5;
writer.write_colour_block(&key_colours)?;
dbus.proxies().led().set_per_key(&key_colours)?;
}
}
}

View File

@@ -1,15 +1,13 @@
use asus_nb::{
core_dbus::AuraDbusClient,
fancy::{GX502Layout, KeyColourArray, KeyLayout},
};
use rog_aura::{GX502Layout, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut writer = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut key_colours = KeyColourArray::new();
let layout = GX502Layout::default();
writer.init_effect()?;
dbus.proxies().led().init_effect()?;
let rows = layout.get_rows();
let mut fade = 50;
@@ -23,7 +21,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
writer.write_colour_block(&key_colours)?;
dbus.proxies().led().set_per_key(&key_colours)?;
if flip {
if fade > 1 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
asusctl/examples/doom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
asusctl/examples/ferris.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
asusctl/examples/nudoom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
asusctl/examples/rust.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

101
asusctl/src/anime_cli.rs Normal file
View File

@@ -0,0 +1,101 @@
use gumdrop::Options;
#[derive(Options)]
pub struct AnimeCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(
meta = "",
help = "enable/disable the panel LEDs (does not erase last image)"
)]
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(command)]
pub command: Option<AnimeActions>,
}
#[derive(Options)]
pub enum AnimeActions {
#[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)]
pub struct AnimeImage {
#[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,
}
#[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,
}

329
asusctl/src/aura_cli.rs Normal file
View File

@@ -0,0 +1,329 @@
use gumdrop::Options;
use rog_aura::{error::Error, AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed};
use std::str::FromStr;
#[derive(Options)]
pub struct LedBrightness {
level: Option<u32>,
}
impl LedBrightness {
pub fn new(level: Option<u32>) -> Self {
LedBrightness { level }
}
pub fn level(&self) -> Option<u32> {
self.level
}
}
impl FromStr for LedBrightness {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"off" => Ok(LedBrightness { level: Some(0x00) }),
"low" => Ok(LedBrightness { level: Some(0x01) }),
"med" => Ok(LedBrightness { level: Some(0x02) }),
"high" => Ok(LedBrightness { level: Some(0x03) }),
_ => {
print!("Invalid argument, must be one of: off, low, med, high");
Err(Error::ParseBrightness)
}
}
}
}
impl ToString for LedBrightness {
fn to_string(&self) -> String {
let s = match self.level {
Some(0x00) => "low",
Some(0x01) => "med",
Some(0x02) => "high",
_ => "unknown",
};
s.to_string()
}
}
#[derive(Debug, Clone, Options, Default)]
pub struct SingleSpeed {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "WORD", help = "set the speed: low, med, high")]
pub speed: Speed,
}
#[derive(Debug, Clone, Options, Default)]
pub struct SingleSpeedDirection {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "", help = "set the direction: up, down, left, right")]
pub direction: Direction,
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
pub speed: Speed,
}
#[derive(Debug, Clone, Default, Options)]
pub struct SingleColour {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour: Colour,
}
#[derive(Debug, Clone, Default, Options)]
pub struct SingleColourSpeed {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour: Colour,
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
pub speed: Speed,
}
#[derive(Debug, Clone, Options, Default)]
pub struct TwoColourSpeed {
#[options(help = "print help message")]
help: bool,
#[options(no_long, meta = "", help = "set the first RGB value e.g, ff00ff")]
pub colour: Colour,
#[options(no_long, meta = "", help = "set the second RGB value e.g, ff00ff")]
pub colour2: Colour,
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
pub speed: Speed,
}
#[derive(Debug, Clone, Default, Options)]
pub struct MultiColour {
#[options(help = "print help message")]
help: bool,
#[options(short = "a", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour1: Colour,
#[options(short = "b", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour2: Colour,
#[options(short = "c", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour3: Colour,
#[options(short = "d", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour4: Colour,
}
#[derive(Debug, Clone, Default, Options)]
pub struct MultiColourSpeed {
#[options(help = "print help message")]
help: bool,
#[options(short = "a", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour1: Colour,
#[options(short = "b", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour2: Colour,
#[options(short = "c", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour3: Colour,
#[options(short = "d", meta = "", help = "set the RGB value e.g, ff00ff")]
pub colour4: Colour,
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
pub speed: Speed,
}
/// Byte value for setting the built-in mode.
///
/// Enum corresponds to the required integer value
///
// NOTE: The option names here must match those in rog-aura crate
#[derive(Options)]
pub enum SetAuraBuiltin {
#[options(help = "set a single static colour")]
Static(SingleColour),
#[options(help = "pulse between one or two colours")]
Breathe(TwoColourSpeed),
#[options(help = "strobe through all colours")]
Strobe(SingleSpeed),
#[options(help = "rainbow cycling in one of four directions")]
Rainbow(SingleSpeedDirection),
#[options(help = "rain pattern mimicking raindrops")]
Stars(TwoColourSpeed),
#[options(help = "rain pattern of three preset colours")]
Rain(SingleSpeed),
#[options(help = "pressed keys are highlighted to fade")]
Highlight(SingleColourSpeed),
#[options(help = "pressed keys generate horizontal laser")]
Laser(SingleColourSpeed),
#[options(help = "pressed keys ripple outwards like a splash")]
Ripple(SingleColourSpeed),
#[options(help = "set a rapid pulse")]
Pulse(SingleColour),
#[options(help = "set a vertical line zooming from left")]
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 {
fn default() -> Self {
SetAuraBuiltin::Static(SingleColour::default())
}
}
impl From<&SingleColour> for AuraEffect {
fn from(aura: &SingleColour) -> Self {
Self {
colour1: aura.colour,
..Default::default()
}
}
}
impl From<&SingleSpeed> for AuraEffect {
fn from(aura: &SingleSpeed) -> Self {
Self {
speed: aura.speed,
..Default::default()
}
}
}
impl From<&SingleColourSpeed> for AuraEffect {
fn from(aura: &SingleColourSpeed) -> Self {
Self {
colour1: aura.colour,
speed: aura.speed,
..Default::default()
}
}
}
impl From<&TwoColourSpeed> for AuraEffect {
fn from(aura: &TwoColourSpeed) -> Self {
Self {
colour1: aura.colour,
colour2: aura.colour2,
..Default::default()
}
}
}
impl From<&SingleSpeedDirection> for AuraEffect {
fn from(aura: &SingleSpeedDirection) -> Self {
Self {
speed: aura.speed,
direction: aura.direction,
..Default::default()
}
}
}
impl From<&SetAuraBuiltin> for AuraEffect {
fn from(aura: &SetAuraBuiltin) -> Self {
match aura {
SetAuraBuiltin::Static(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Static;
data
}
SetAuraBuiltin::Breathe(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Breathe;
data
}
SetAuraBuiltin::Strobe(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Strobe;
data
}
SetAuraBuiltin::Rainbow(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Rainbow;
data
}
SetAuraBuiltin::Stars(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Star;
data
}
SetAuraBuiltin::Rain(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Rain;
data
}
SetAuraBuiltin::Highlight(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Highlight;
data
}
SetAuraBuiltin::Laser(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Laser;
data
}
SetAuraBuiltin::Ripple(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Ripple;
data
}
SetAuraBuiltin::Pulse(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Pulse;
data
}
SetAuraBuiltin::Comet(x) => {
let mut data: AuraEffect = x.into();
data.mode = AuraModeNum::Comet;
data
}
SetAuraBuiltin::Flash(x) => {
let mut data: AuraEffect = x.into();
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
}
}

101
asusctl/src/cli_opts.rs Normal file
View File

@@ -0,0 +1,101 @@
use crate::{
anime_cli::AnimeCommand,
aura_cli::{LedBrightness, SetAuraBuiltin},
profiles_cli::{FanCurveCommand, ProfileCommand},
};
use gumdrop::Options;
#[derive(Default, Options)]
pub struct CliStart {
#[options(help_flag, help = "print help message")]
pub help: bool,
#[options(help = "show program version number")]
pub version: bool,
#[options(help = "show supported functions of this laptop")]
pub show_supported: bool,
#[options(meta = "", help = "<off, low, med, high>")]
pub kbd_bright: Option<LedBrightness>,
#[options(help = "Toggle to next keyboard brightness")]
pub next_kbd_bright: bool,
#[options(help = "Toggle to previous keyboard brightness")]
pub prev_kbd_bright: bool,
#[options(meta = "", help = "Set your battery charge limit <20-100>")]
pub chg_limit: Option<u8>,
#[options(command)]
pub command: Option<CliCommand>,
}
#[derive(Options)]
pub enum CliCommand {
#[options(help = "Set the keyboard lighting from built-in modes")]
LedMode(LedModeCommand),
#[options(help = "Set or select platform_profile")]
Profile(ProfileCommand),
#[options(help = "Set, select, or modify fan curves if supported")]
FanCurve(FanCurveCommand),
#[options(help = "Set the graphics mode (obsoleted by supergfxctl)")]
Graphics(GraphicsCommand),
#[options(name = "anime", help = "Manage AniMe Matrix")]
Anime(AnimeCommand),
#[options(help = "Change bios settings")]
Bios(BiosCommand),
}
#[derive(Options)]
pub struct LedModeCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(help = "switch to next aura mode")]
pub next_mode: bool,
#[options(help = "switch to previous aura mode")]
pub prev_mode: bool,
#[options(
meta = "",
help = "set the keyboard LED to enabled while the device is awake"
)]
pub boot_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(
meta = "",
help = "set the full keyboard LEDs (keys and side) to enabled"
)]
pub all_leds_enable: Option<bool>,
#[options(meta = "", help = "set the keyboard keys LEDs to enabled")]
pub keys_leds_enable: Option<bool>,
#[options(meta = "", help = "set the keyboard side LEDs to enabled")]
pub side_leds_enable: Option<bool>,
#[options(command)]
pub command: Option<SetAuraBuiltin>,
}
#[derive(Options)]
pub struct GraphicsCommand {
#[options(help = "print help message")]
pub help: bool,
}
#[derive(Options, Debug)]
pub struct BiosCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(
meta = "",
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")]
pub post_sound_get: bool,
#[options(
meta = "",
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")]
pub dedicated_gfx_get: bool,
}

610
asusctl/src/main.rs Normal file
View File

@@ -0,0 +1,610 @@
use std::process::Command;
use std::thread::sleep;
use std::{env::args, path::Path};
use gumdrop::{Opt, Options};
use anime_cli::{AnimeActions, AnimeCommand};
use profiles_cli::{FanCurveCommand, ProfileCommand};
use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, Vec2};
use rog_aura::{self, AuraEffect};
use rog_dbus::RogDbusClientBlocking;
use rog_profiles::error::ProfileError;
use rog_supported::SupportedFunctions;
use rog_supported::{
AnimeSupportedFunctions, LedSupportedFunctions, PlatformProfileFunctions,
RogBiosSupportedFunctions,
};
use crate::aura_cli::{LedBrightness, SetAuraBuiltin};
use crate::cli_opts::*;
mod anime_cli;
mod aura_cli;
mod cli_opts;
mod profiles_cli;
const CONFIG_ADVICE: &str = "A config file need to be removed so a new one can be generated";
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = args().skip(1).collect();
let parsed: CliStart;
let missing_argument_k = gumdrop::Error::missing_argument(Opt::Short('k'));
match CliStart::parse_args_default(&args) {
Ok(p) => {
parsed = p;
}
Err(err) if err.to_string() == missing_argument_k.to_string() => {
parsed = CliStart {
kbd_bright: Some(LedBrightness::new(None)),
..Default::default()
};
}
Err(err) => {
eprintln!("source {}", err);
std::process::exit(2);
}
}
let (dbus, _) = RogDbusClientBlocking::new()
.map_err(|e| {
print_error_help(Box::new(e), None);
std::process::exit(3);
})
.unwrap();
let supported = dbus
.proxies()
.supported()
.supported_functions()
.map_err(|e| {
print_error_help(Box::new(e), None);
std::process::exit(4);
})
.unwrap();
if parsed.version {
print_versions();
println!();
print_laptop_info();
return Ok(());
}
if let Err(err) = do_parsed(&parsed, &supported, &dbus) {
print_error_help(err, Some(&supported));
}
Ok(())
}
fn print_error_help(err: Box<dyn std::error::Error>, supported: Option<&SupportedFunctions>) {
if do_diagnose("asusd") {
println!("\nError: {}\n", err);
print_versions();
println!();
print_laptop_info();
if let Some(supported) = supported {
println!();
println!("Supported laptop functions:\n\n{}", supported);
}
}
}
fn print_versions() {
println!("App and daemon versions:");
println!(" asusctl v{}", env!("CARGO_PKG_VERSION"));
println!(" asusd v{}", daemon::VERSION);
println!("\nComponent crate versions:");
println!(" rog-anime v{}", rog_anime::VERSION);
println!(" rog-aura v{}", rog_aura::VERSION);
println!(" rog-dbus v{}", rog_dbus::VERSION);
println!(" rog-profiles v{}", rog_profiles::VERSION);
println!("rog-supported v{}", rog_supported::VERSION);
}
fn print_laptop_info() {
let dmi = sysfs_class::DmiId::default();
let board_name = dmi.board_name().expect("Could not get board_name");
let prod_family = dmi.product_family().expect("Could not get product_family");
println!("Product family: {}", prod_family.trim());
println!("Board name: {}", board_name.trim());
}
fn do_diagnose(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",
name, name
);
return true;
} else if !check_systemd_unit_active(name) {
println!(
"\n\x1b[0;31m{} is not running, start it with `systemctl start {}\x1b[0m",
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
}
fn do_parsed(
parsed: &CliStart,
supported: &SupportedFunctions,
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::Profile(cmd)) => handle_profile(dbus, &supported.platform_profile, cmd)?,
Some(CliCommand::FanCurve(cmd)) => {
handle_fan_curve(dbus, &supported.platform_profile, cmd)?
}
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 => {
if (!parsed.show_supported
&& parsed.kbd_bright.is_none()
&& parsed.chg_limit.is_none()
&& !parsed.next_kbd_bright
&& !parsed.prev_kbd_bright)
|| parsed.help
{
println!("{}", CliStart::usage());
println!();
if let Some(cmdlist) = CliStart::command_list() {
println!("{}", cmdlist);
}
}
}
}
if let Some(brightness) = &parsed.kbd_bright {
match brightness.level() {
None => {
let level = dbus.proxies().led().led_brightness()?;
println!("Current keyboard led brightness: {}", level);
}
Some(level) => dbus
.proxies()
.led()
.set_brightness(<rog_aura::LedBrightness>::from(level))?,
}
}
if parsed.next_kbd_bright {
dbus.proxies().led().next_led_brightness()?;
}
if parsed.prev_kbd_bright {
dbus.proxies().led().prev_led_brightness()?;
}
if parsed.show_supported {
println!("Supported laptop functions:\n\n{}", supported);
}
if let Some(chg_limit) = parsed.chg_limit {
dbus.proxies().charge().set_limit(chg_limit)?;
}
Ok(())
}
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: &RogDbusClientBlocking,
_supported: &AnimeSupportedFunctions,
cmd: &AnimeCommand,
) -> Result<(), Box<dyn std::error::Error>> {
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.enable {
dbus.proxies().anime().set_on_off(anime_turn)?
}
if let Some(anime_boot) = cmd.boot_enable {
dbus.proxies().anime().set_boot_on_off(anime_boot)?
}
if let Some(bright) = cmd.brightness {
dbus.proxies().anime().set_brightness(bright as f32)?
}
if let Some(action) = cmd.command.as_ref() {
match action {
AnimeActions::Image(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);
}
let matrix = AnimeImage::from_png(
Path::new(&image.path),
image.scale,
image.angle,
Vec2::new(image.x_pos, image.y_pos),
image.bright,
)?;
dbus.proxies()
.anime()
.write(<AnimeDataBuffer>::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);
}
let matrix = AnimeDiagonal::from_png(Path::new(&image.path), None, image.bright)?;
dbus.proxies()
.anime()
.write(<AnimeDataBuffer>::from(&matrix))?;
}
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);
}
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,
)?;
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);
}
let matrix = AnimeGif::from_diagonal_gif(
Path::new(&gif.path),
AnimTime::Count(1),
gif.bright,
)?;
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 handle_led_mode(
dbus: &RogDbusClientBlocking,
supported: &LedSupportedFunctions,
mode: &LedModeCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if mode.command.is_none()
&& !mode.prev_mode
&& !mode.next_mode
&& mode.boot_enable.is_none()
&& mode.sleep_enable.is_none()
&& mode.all_leds_enable.is_none()
&& mode.keys_leds_enable.is_none()
&& mode.side_leds_enable.is_none()
{
if !mode.help {
println!("Missing arg or command\n");
}
println!("{}\n", mode.self_usage());
println!("Commands available");
if let Some(cmdlist) = LedModeCommand::command_list() {
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_string()).collect();
for command in commands.iter().filter(|command| {
for mode in &supported.stock_led_modes {
if command
.trim()
.starts_with(&<&str>::from(mode).to_lowercase())
{
return true;
}
}
if supported.multizone_led_mode && command.trim().starts_with("multi") {
return true;
}
false
}) {
println!("{}", command);
}
}
println!("\nHelp can also be requested on modes, e.g: static --help");
return Ok(());
}
if mode.next_mode && mode.prev_mode {
println!("Please specify either next or previous");
return Ok(());
}
if mode.next_mode {
dbus.proxies().led().next_led_mode()?;
} else if mode.prev_mode {
dbus.proxies().led().prev_led_mode()?;
} else if let Some(mode) = mode.command.as_ref() {
if mode.help_requested() {
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))?,
}
}
if let Some(enable) = mode.boot_enable {
dbus.proxies().led().set_boot_enabled(enable)?;
}
if let Some(enable) = mode.sleep_enable {
dbus.proxies().led().set_sleep_enabled(enable)?;
}
if let Some(enable) = mode.all_leds_enable {
dbus.proxies().led().set_all_leds_enabled(enable)?;
}
if let Some(enable) = mode.keys_leds_enable {
dbus.proxies().led().set_keys_leds_enabled(enable)?;
}
if let Some(enable) = mode.side_leds_enable {
dbus.proxies().led().set_side_leds_enabled(enable)?;
}
Ok(())
}
fn handle_profile(
dbus: &RogDbusClientBlocking,
supported: &PlatformProfileFunctions,
cmd: &ProfileCommand,
) -> Result<(), Box<dyn std::error::Error>> {
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");
}
println!("{}", ProfileCommand::usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
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: &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 (unlreleased) 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");
}
println!("{}", FanCurveCommand::usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
std::process::exit(1);
}
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);
}
if cmd.get_enabled {
let res = dbus.proxies().profile().enabled_fan_profiles()?;
println!("{:?}", res);
}
if cmd.default {
dbus.proxies().profile().set_active_curve_to_defaults()?;
}
if let Some(profile) = cmd.mod_profile {
if cmd.enabled.is_none() && cmd.data.is_none() {
let data = dbus.proxies().profile().fan_curve_data(profile)?;
let data = toml::to_string(&data)?;
println!("\nFan curves for {:?}\n\n{}", profile, data);
}
if let Some(enabled) = cmd.enabled {
dbus.proxies()
.profile()
.set_fan_curve_enabled(profile, enabled)?;
}
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(profile, curve)?;
}
}
Ok(())
}
fn handle_bios_option(
dbus: &RogDbusClientBlocking,
supported: &RogBiosSupportedFunctions,
cmd: &BiosCommand,
) -> Result<(), Box<dyn std::error::Error>> {
{
if (cmd.dedicated_gfx_set.is_none()
&& !cmd.dedicated_gfx_get
&& cmd.post_sound_set.is_none()
&& !cmd.post_sound_get)
|| cmd.help
{
println!("Missing arg or command\n");
let usage: Vec<String> = BiosCommand::usage()
.lines()
.map(|s| s.to_string())
.collect();
for line in usage.iter().filter(|line| {
line.contains("sound") && supported.post_sound_toggle
|| line.contains("GPU") && supported.dedicated_gfx_toggle
}) {
println!("{}", line);
}
}
if let Some(opt) = cmd.post_sound_set {
dbus.proxies().rog_bios().set_post_boot_sound(opt)?;
}
if cmd.post_sound_get {
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_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!(
"NOTE: on reboot your display manager will be forced to use Nvidia drivers"
);
} else {
println!("NOTE: after reboot you can then select regular graphics modes");
}
}
if cmd.dedicated_gfx_get {
let res = dbus.proxies().rog_bios().dedicated_graphic_mode()? == 1;
println!("Bios dedicated GPU on: {}", res);
}
}
Ok(())
}
fn check_systemd_unit_active(name: &str) -> bool {
if let Ok(out) = Command::new("systemctl")
.arg("is-active")
.arg(name)
.output()
{
let buf = String::from_utf8_lossy(&out.stdout);
return !buf.contains("inactive") && !buf.contains("failed");
}
false
}
fn check_systemd_unit_enabled(name: &str) -> bool {
if let Ok(out) = Command::new("systemctl")
.arg("is-enabled")
.arg(name)
.output()
{
let buf = String::from_utf8_lossy(&out.stdout);
return buf.contains("enabled");
}
false
}

View File

@@ -0,0 +1,51 @@
use gumdrop::Options;
use rog_profiles::{fan_curve_set::CurveData, FanCurvePU, Profile};
#[derive(Debug, Clone, Options)]
pub struct ProfileCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(help = "toggle to next profile in list")]
pub next: bool,
#[options(help = "list available profiles")]
pub list: bool,
#[options(help = "get profile")]
pub profile_get: bool,
#[options(meta = "", help = "set the active profile")]
pub profile_set: Option<Profile>,
}
#[derive(Debug, Clone, Options)]
pub struct FanCurveCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(help = "get enabled fan profiles")]
pub get_enabled: bool,
#[options(help = "set the active profile's fan curve to default")]
pub default: bool,
#[options(
meta = "",
help = "profile to modify fan-curve for. Shows data if no options provided"
)]
pub mod_profile: Option<Profile>,
#[options(
meta = "",
help = "enable or disable <true/false> fan curve. `mod-profile` required"
)]
pub enabled: Option<bool>,
#[options(
meta = "",
help = "select fan <cpu/gpu> to modify. `mod-profile` required"
)]
pub fan: Option<FanCurvePU>,
#[options(
meta = "",
help = "data format = 30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%.
`--mod-profile` required. If '%' is omitted the fan range is 0-255"
)]
pub data: Option<CurveData>,
}

View File

@@ -1,19 +0,0 @@
[package]
name = "ctrl-gfx"
version = "2.1.0"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
description = "Fine control of laptop GPU"
edition = "2018"
[dependencies]
sysfs-class = "^0.1.2"
log = "^0.4"
zbus = { version = "1.1.1", optional = true }
zvariant = { version = "2.2.0", optional = true }
[features]
default = ["use-zbus"]
use-zbus = ["zbus", "zvariant"]

View File

@@ -1,364 +0,0 @@
use log::{error, info, warn};
use std::error::Error;
use std::io::Write;
use std::iter::FromIterator;
use std::path::Path;
use std::process::Command;
use std::str::FromStr;
use sysfs_class::{PciDevice, SysClass};
use zbus::dbus_interface;
use crate::vendors::*;
use crate::*;
use crate::{error::GfxError, system::*};
pub struct CtrlGraphics {
bus: PciBus,
_amd: Vec<GraphicsDevice>,
_intel: Vec<GraphicsDevice>,
nvidia: Vec<GraphicsDevice>,
#[allow(dead_code)]
other: Vec<GraphicsDevice>,
initfs_cmd: Option<Command>,
}
trait Dbus {
fn vendor(&self) -> String;
fn power(&self) -> String;
fn set_vendor(&mut self, vendor: String);
fn notify_gfx(&self, vendor: &str) -> zbus::Result<()>;
fn notify_action(&self, action: &str) -> zbus::Result<()>;
}
#[cfg(feature = "use-zbus")]
use std::convert::TryInto;
#[cfg(feature = "use-zbus")]
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl Dbus for CtrlGraphics {
fn vendor(&self) -> String {
Self::get_vendor()
.map_err(|err| format!("Get vendor failed: {}", err))
.unwrap()
}
fn power(&self) -> String {
Self::get_runtime_status()
.map_err(|err| format!("Get power status failed: {}", err))
.unwrap()
}
fn set_vendor(&mut self, vendor: String) {
if let Ok(tmp) = GfxVendors::from_str(&vendor) {
let action = self.set(tmp).unwrap_or_else(|err| {
warn!("{}", err);
format!("Failed: {}", err.to_string())
});
self.notify_gfx(&vendor)
.unwrap_or_else(|err| warn!("{}", err));
self.notify_action(&action)
.unwrap_or_else(|err| warn!("{}", err));
}
}
#[dbus_interface(signal)]
fn notify_gfx(&self, vendor: &str) -> zbus::Result<()>;
#[dbus_interface(signal)]
fn notify_action(&self, action: &str) -> zbus::Result<()>;
}
impl CtrlGraphics {
pub fn new() -> std::io::Result<CtrlGraphics> {
let bus = PciBus::new()?;
info!("Rescanning PCI bus");
bus.rescan()?;
let devs = PciDevice::all()?;
let functions = |parent: &PciDevice| -> Vec<PciDevice> {
let mut functions = Vec::new();
if let Some(parent_slot) = parent.id().split('.').next() {
for func in devs.iter() {
if let Some(func_slot) = func.id().split('.').next() {
if func_slot == parent_slot {
info!("{}: Function for {}", func.id(), parent.id());
functions.push(func.clone());
}
}
}
}
functions
};
let mut amd = Vec::new();
let mut intel = Vec::new();
let mut nvidia = Vec::new();
let mut other = Vec::new();
for dev in devs.iter() {
let c = dev.class()?;
if 0x03 == (c >> 16) & 0xFF {
match dev.vendor()? {
0x1002 => {
info!("{}: AMD graphics", dev.id());
amd.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
}
0x10DE => {
info!("{}: NVIDIA graphics", dev.id());
nvidia.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
}
0x8086 => {
info!("{}: Intel graphics", dev.id());
intel.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
}
vendor => {
info!("{}: Other({:X}) graphics", dev.id(), vendor);
other.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
}
}
}
}
let mut initfs_cmd = None;
if Path::new(INITRAMFS_PATH).exists() {
let mut cmd = Command::new("update-initramfs");
cmd.arg("-u");
initfs_cmd = Some(cmd);
info!("Using initramfs update command 'update-initramfs'");
}
// } else if Path::new(DRACUT_PATH).exists() {
// let mut cmd = Command::new("dracut");
// cmd.arg("-f");
// initfs_cmd = Some(cmd);
// info!("Using initramfs update command 'dracut'");
// }
Ok(CtrlGraphics {
bus,
_amd: amd,
_intel: intel,
nvidia,
other,
initfs_cmd,
})
}
#[cfg(feature = "use-zbus")]
pub fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/Gfx".try_into().unwrap(), self)
.unwrap();
}
pub fn reload(&mut self) -> Result<(), Box<dyn Error>> {
self.auto_power()?;
info!("Reloaded gfx mode: {:?}", CtrlGraphics::get_vendor()?);
Ok(())
}
// fn can_switch(&self) -> bool {
// !self.nvidia.is_empty() && (!self.intel.is_empty() || !self.amd.is_empty())
// }
fn get_prime_discrete() -> Result<String, GfxError> {
let s = std::fs::read_to_string(PRIME_DISCRETE_PATH)
.map_err(|err| GfxError::Read(PRIME_DISCRETE_PATH.into(), err))?
.trim()
.to_owned();
Ok(s)
}
fn set_prime_discrete(mode: &str) -> Result<(), GfxError> {
std::fs::write(PRIME_DISCRETE_PATH, mode)
.map_err(|err| GfxError::Read(PRIME_DISCRETE_PATH.into(), err))?;
Ok(())
}
/// Associated method to get which vendor mode is set
pub fn get_vendor() -> Result<String, GfxError> {
let modules = Module::all().map_err(|err| GfxError::Read("get_vendor".into(), err))?;
let vendor = if modules
.iter()
.any(|module| module.name == "nouveau" || module.name == "nvidia")
{
info!("nvidia or nouveau module found");
let mode = match Self::get_prime_discrete() {
Ok(m) => m,
Err(_) => "nvidia".to_string(),
};
if mode == "on-demand" {
"hybrid".to_string()
} else if mode == "off" {
"compute".to_string()
} else {
"nvidia".to_string()
}
} else {
info!("No dGPU driver (nouveau or nvidia) loaded");
"integrated".to_string()
};
Ok(vendor)
}
pub fn is_switching_prime_modes(vendor: &GfxVendors) -> Result<bool, GfxError> {
let prev_mode = GfxVendors::from_str(&Self::get_vendor()?)?;
let x = (prev_mode == GfxVendors::Hybrid || prev_mode == GfxVendors::Nvidia)
&& (*vendor == GfxVendors::Hybrid || *vendor == GfxVendors::Nvidia);
Ok(x)
}
/// Write out config files if required, enable/disable relevant services, and update the ramdisk
pub fn set(&mut self, vendor: GfxVendors) -> Result<String, GfxError> {
//self.switchable_or_fail()?;
let mode = if vendor == GfxVendors::Hybrid {
"on-demand\n"
} else if vendor == GfxVendors::Nvidia {
"on\n"
} else {
// Integrated or Compute
"off\n"
};
info!("Setting {} to {}", PRIME_DISCRETE_PATH, mode);
Self::set_prime_discrete(mode)?;
// Switching from hybrid to/from nvidia shouldn't require a ramdisk update
// or a reboot.
let no_reboot = Self::is_switching_prime_modes(&vendor)?;
{
info!("Writing {}", MODPROBE_PATH);
let mut file = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(MODPROBE_PATH)
.map_err(|err| GfxError::Path(MODPROBE_PATH.into(), err))?;
let text = if vendor == GfxVendors::Hybrid {
MODPROBE_HYBRID
} else if vendor == GfxVendors::Compute {
MODPROBE_COMPUTE
} else if vendor == GfxVendors::Nvidia {
MODPROBE_NVIDIA
} else {
MODPROBE_INTEGRATED
};
file.write_all(text)
.and_then(|_| file.sync_all())
.map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?;
}
info!("Writing {}", PRIMARY_GPU_XORG_PATH);
// begin section for non-separated Nvidia xorg modules
// eg, not put in their own directory
let mut file = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(PRIMARY_GPU_XORG_PATH)
.map_err(|err| GfxError::Write(PRIMARY_GPU_XORG_PATH.into(), err))?;
let text = if vendor == GfxVendors::Nvidia {
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_NVIDIA, PRIMARY_GPU_END].concat()
} else {
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_END].concat()
};
file.write_all(&text)
.and_then(|_| file.sync_all())
.map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?;
let action = if vendor == GfxVendors::Nvidia {
info!("Enabling nvidia-fallback.service");
"enable"
} else {
info!("Disabling nvidia-fallback.service");
"disable"
};
let status = Command::new("systemctl")
.arg(action)
.arg("nvidia-fallback.service")
.status()
.map_err(|err| GfxError::Command("systemctl".into(), err))?;
if !status.success() {
// Error is ignored in case this service is removed
warn!(
"systemctl: {} (ignore warning if service does not exist!)",
status
);
}
let mut required_action = GfxCtrlAction::None;
if !no_reboot {
info!("Updating initramfs");
if let Some(cmd) = self.initfs_cmd.as_mut() {
let status = cmd
.status()
.map_err(|err| GfxError::Write(format!("{:?}", cmd), err))?;
if !status.success() {
error!("Ram disk update failed");
} else {
info!("Successfully updated iniramfs");
}
}
required_action = GfxCtrlAction::Reboot;
} else if no_reboot {
required_action = GfxCtrlAction::RestartX;
}
Ok(required_action.into())
}
// pub fn get_power(&self) -> Option<bool> {
// if self.can_switch() {
// return Some(self.nvidia.iter().any(GraphicsDevice::exists));
// }
// None
// }
pub fn get_runtime_status() -> Result<String, GfxError> {
const PATH: &str = "/sys/bus/pci/devices/0000:01:00.0/power/runtime_status";
let buf = std::fs::read_to_string(PATH)
.map_err(|err| GfxError::Read(PATH.into(), err))?;
Ok(buf)
}
fn set_power(&self, power: bool) -> Result<(), GfxError> {
// self.switchable_or_fail()?;
if power {
info!("Enabling graphics power");
self.bus
.rescan()
.map_err(|err| GfxError::Bus("bus rescan error".into(), err))?;
} else {
info!("Disabling graphics power");
// Unbind NVIDIA graphics devices and their functions
let unbinds = self.nvidia.iter().map(|dev| dev.unbind());
// Remove NVIDIA graphics devices and their functions
let removes = self.nvidia.iter().map(|dev| dev.remove());
Result::from_iter(unbinds.chain(removes))
.map_err(|err| GfxError::Command("device unbind error".into(), err))?;
}
Ok(())
}
fn auto_power(&self) -> Result<(), GfxError> {
let vendor = CtrlGraphics::get_vendor()?;
self.set_power(vendor != "integrated")
}
}

View File

@@ -1,30 +0,0 @@
use std::error;
use std::fmt;
#[derive(Debug)]
pub enum GfxError {
ParseVendor,
Path(String, std::io::Error),
Read(String, std::io::Error),
Write(String, std::io::Error),
Module(String, std::io::Error),
Bus(String, std::io::Error),
Command(String, std::io::Error),
}
impl fmt::Display for GfxError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GfxError::ParseVendor => write!(f, "Could not parse vendor name"),
GfxError::Path(path, error) => write!(f, "Path {}: {}", path, error),
GfxError::Read(path, error) => write!(f, "Read {}: {}", path, error),
GfxError::Write(path, error) => write!(f, "Write {}: {}", path, error),
GfxError::Module(func, error) => write!(f, "Module error: {}: {}", func, error),
GfxError::Bus(func, error) => write!(f, "Bus error: {}: {}", func, error),
GfxError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
}
}
}
impl error::Error for GfxError {}

View File

@@ -1,58 +0,0 @@
pub mod vendors;
pub mod error;
pub mod ctrl_gfx;
pub mod system;
const PRIME_DISCRETE_PATH: &str = "/etc/prime-discrete";
const MODPROBE_PATH: &str = "/etc/modprobe.d/asusd.conf";
const INITRAMFS_PATH: &str = "/usr/sbin/update-initramfs";
//const DRACUT_PATH: &str = "/usr/bin/dracut";
static MODPROBE_NVIDIA: &[u8] = MODPROBE_HYBRID;
static MODPROBE_HYBRID: &[u8] = br#"# Automatically generated by asusd
blacklist i2c_nvidia_gpu
alias i2c_nvidia_gpu off
options nvidia NVreg_DynamicPowerManagement=0x02
options nvidia-drm modeset=1
"#;
static MODPROBE_COMPUTE: &[u8] = br#"# Automatically generated by asusd
blacklist i2c_nvidia_gpu
alias i2c_nvidia_gpu off
options nvidia NVreg_DynamicPowerManagement=0x02
options nvidia-drm modeset=0
"#;
static MODPROBE_INTEGRATED: &[u8] = br#"# Automatically generated by asusd
blacklist i2c_nvidia_gpu
blacklist nouveau
blacklist nvidia
blacklist nvidia-drm
blacklist nvidia-modeset
alias i2c_nvidia_gpu off
alias nouveau off
alias nvidia off
alias nvidia-drm off
alias nvidia-modeset off
"#;
const PRIMARY_GPU_XORG_PATH: &str = "/etc/X11/xorg.conf.d/90-nvidia-primary.conf";
static PRIMARY_GPU_BEGIN: &[u8] = br#"# Automatically generated by asusd
Section "OutputClass"
Identifier "nvidia"
MatchDriver "nvidia-drm"
Driver "nvidia"
Option "AllowEmptyInitialConfiguration"
Option "AllowExternalGpus""#;
static PRIMARY_GPU_NVIDIA: &[u8] = br#"
Option "PrimaryGPU" "true""#;
static PRIMARY_GPU_END: &[u8] = br#"
EndSection"#;

View File

@@ -1,127 +0,0 @@
use log::{error, info, warn};
use std::fs::read_to_string;
use std::{fs::write, io, path::PathBuf};
use sysfs_class::{PciDevice, SysClass};
pub struct Module {
pub name: String,
}
impl Module {
fn parse(line: &str) -> io::Result<Module> {
let mut parts = line.split(' ');
let name = parts
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "module name not found"))?;
Ok(Module {
name: name.to_string(),
})
}
pub fn all() -> io::Result<Vec<Module>> {
let mut modules = Vec::new();
let data = read_to_string("/proc/modules")?;
for line in data.lines() {
let module = Module::parse(line)?;
modules.push(module);
}
Ok(modules)
}
}
pub struct PciBus {
path: PathBuf,
}
impl PciBus {
pub fn new() -> io::Result<PciBus> {
let path = PathBuf::from("/sys/bus/pci");
if path.is_dir() {
Ok(PciBus { path })
} else {
Err(io::Error::new(
io::ErrorKind::NotFound,
"pci directory not found",
))
}
}
pub fn rescan(&self) -> io::Result<()> {
write(self.path.join("rescan"), "1")
}
}
pub struct GraphicsDevice {
id: String,
functions: Vec<PciDevice>,
}
impl GraphicsDevice {
pub fn new(id: String, functions: Vec<PciDevice>) -> GraphicsDevice {
GraphicsDevice { id, functions }
}
pub fn exists(&self) -> bool {
self.functions.iter().any(|func| func.path().exists())
}
pub fn unbind(&self) -> Result<(), std::io::Error> {
for func in self.functions.iter() {
if func.path().exists() {
match func.driver() {
Ok(driver) => {
info!("{}: Unbinding {}", driver.id(), func.id());
unsafe {
driver.unbind(&func).map_err(|err| {
error!("gfx unbind: {}", err);
err
})?;
}
}
Err(err) => match err.kind() {
io::ErrorKind::NotFound => (),
_ => {
error!("gfx driver: {:?}, {}", func.path(), err);
return Err(err);
}
},
}
}
}
Ok(())
}
pub fn remove(&self) -> Result<(), std::io::Error> {
for func in self.functions.iter() {
if func.path().exists() {
match func.driver() {
Ok(driver) => {
error!("{}: in use by {}", func.id(), driver.id());
}
Err(why) => match why.kind() {
std::io::ErrorKind::NotFound => {
info!("{}: Removing", func.id());
unsafe {
// ignore errors and carry on
if let Err(err) = func.remove() {
error!("gfx remove: {}", err);
}
}
}
_ => {
error!("Remove device failed");
}
},
}
} else {
warn!("{}: Already removed", func.id());
}
}
info!("Remmoved all gfx devices");
Ok(())
}
}

View File

@@ -1,85 +0,0 @@
#[derive(Debug, PartialEq, Clone)]
pub enum GfxVendors {
Nvidia,
Integrated,
Compute,
Hybrid,
}
use crate::error::GfxError;
use std::str::FromStr;
impl FromStr for GfxVendors {
type Err = GfxError;
fn from_str(s: &str) -> Result<Self, GfxError> {
match s.to_lowercase().as_str() {
"nvidia" => Ok(GfxVendors::Nvidia),
"hybrid" => Ok(GfxVendors::Hybrid),
"compute" => Ok(GfxVendors::Compute),
"integrated" => Ok(GfxVendors::Integrated),
"nvidia\n" => Ok(GfxVendors::Nvidia),
"hybrid\n" => Ok(GfxVendors::Hybrid),
"compute\n" => Ok(GfxVendors::Compute),
"integrated\n" => Ok(GfxVendors::Integrated),
_ => Err(GfxError::ParseVendor),
}
}
}
impl From<&GfxVendors> for &str {
fn from(mode: &GfxVendors) -> Self {
match mode {
GfxVendors::Nvidia => "nvidia",
GfxVendors::Hybrid => "hybrid",
GfxVendors::Compute => "compute",
GfxVendors::Integrated => "integrated",
}
}
}
#[derive(Debug)]
pub enum GfxCtrlAction {
Reboot,
RestartX,
None,
}
impl FromStr for GfxCtrlAction {
type Err = GfxError;
fn from_str(s: &str) -> Result<Self, GfxError> {
match s.to_lowercase().as_str() {
"reboot" => Ok(GfxCtrlAction::Reboot),
"restartx" => Ok(GfxCtrlAction::RestartX),
"none" => Ok(GfxCtrlAction::None),
_ => Err(GfxError::ParseVendor),
}
}
}
impl From<&GfxCtrlAction> for &str {
fn from(mode: &GfxCtrlAction) -> Self {
match mode {
GfxCtrlAction::Reboot => "reboot",
GfxCtrlAction::RestartX => "restartx",
GfxCtrlAction::None => "none",
}
}
}
impl From<&GfxCtrlAction> for String {
fn from(mode: &GfxCtrlAction) -> Self {
match mode {
GfxCtrlAction::Reboot => "reboot".into(),
GfxCtrlAction::RestartX => "restartx".into(),
GfxCtrlAction::None => "none".into(),
}
}
}
impl From<GfxCtrlAction> for String {
fn from(mode: GfxCtrlAction) -> Self {
(&mode).into()
}
}

32
daemon-user/Cargo.toml Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = "daemon-user"
version = "1.3.0"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
description = "Usermode daemon for user settings, anime, per-key lighting"
[lib]
name = "rog_user"
path = "src/lib.rs"
[[bin]]
name = "asusd-user"
path = "src/daemon.rs"
[dependencies]
# serialisation
serde = "^1.0"
serde_json = "^1.0"
serde_derive = "^1.0"
rog_anime = { path = "../rog-anime" }
rog_dbus = { path = "../rog-dbus" }
rog_supported = { path = "../rog-supported" }
dirs = "^4.0"
zbus = "^2.2"
zvariant = "^3.0"
zvariant_derive = "^3.0"
smol = "^1.2"

14
daemon-user/README.md Normal file
View File

@@ -0,0 +1,14 @@
# daemon-user
This crate is for the binary of `asusd-user` and its helper lib.
The purpose of `asusd-user` is to run in userland and provide the user + third-party apps an interface for such things as creating AniMe sequences (and more in future, see todo list).
`asusd-user` should try to be as simple as possible while allowing a decent degree of control.
## TODO
- [ ] CLI for basic settings/interaction
- [ ] RGB keyboard per-key programs
- [ ] User profiles (fan, cpu etc). These would be replacing the system-daemon profiles only when the user is active, otherwise system-daemon defaults to system settings.
- [ ] Audio EQ visualiser - for use with anime + keyboard lighting

View File

@@ -0,0 +1,372 @@
use rog_anime::error::AnimeError;
use rog_anime::{ActionData, ActionLoader, AnimTime, Fade, Sequences, Vec2};
use rog_dbus::RogDbusClientBlocking;
use serde_derive::{Deserialize, Serialize};
use std::time::Duration;
use std::{
path::Path,
sync::{
atomic::{AtomicBool, Ordering},
Mutex,
},
};
use std::{sync::Arc, thread::sleep, time::Instant};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use zvariant_derive::Type;
use crate::{error::Error, user_config::UserAnimeConfig};
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
pub struct Timer {
type_of: TimeType,
/// If time type is Timer then this is milliseonds, otherwise it is animation loop count
count: u64,
/// Used only for `TimeType::Timer`, milliseonds to fade the image in for
fade_in: Option<u64>,
/// Used only for `TimeType::Timer`, milliseonds to fade the image out for
fade_out: Option<u64>,
}
impl From<Timer> for AnimTime {
fn from(time: Timer) -> Self {
match time.type_of {
TimeType::Timer => {
if time.fade_in.is_some() || time.fade_out.is_some() {
let fade_in = time
.fade_in
.map_or(Duration::from_secs(0), Duration::from_millis);
let fade_out = time
.fade_out
.map_or(Duration::from_secs(0), Duration::from_millis);
let show_for = if time.count != 0 {
Some(Duration::from_millis(time.count))
} else {
None
};
AnimTime::Fade(Fade::new(fade_in, show_for, fade_out))
} else {
AnimTime::Time(Duration::from_millis(time.count))
}
}
TimeType::Count => AnimTime::Count(time.count as u32),
TimeType::Infinite => AnimTime::Infinite,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
pub enum TimeType {
Timer,
Count,
Infinite,
}
/// The inner object exists to allow the zbus proxy to share it with a runner thread
/// and a zbus server behind `Arc<Mutex<T>>`
pub struct CtrlAnimeInner<'a> {
sequences: Sequences,
client: RogDbusClientBlocking<'a>,
do_early_return: Arc<AtomicBool>,
}
impl<'a> CtrlAnimeInner<'static> {
pub fn new(
sequences: Sequences,
client: RogDbusClientBlocking<'static>,
do_early_return: Arc<AtomicBool>,
) -> Result<Self, Error> {
Ok(Self {
sequences,
client,
do_early_return,
})
}
/// To be called on each main loop iteration to pump out commands to the anime
pub fn run(&'a self) -> Result<(), Error> {
if self.do_early_return.load(Ordering::SeqCst) {
return Ok(());
}
for action in self.sequences.iter() {
match action {
ActionData::Animation(frames) => {
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) => {
self.client
.proxies()
.anime()
.write(image.as_ref().clone())
.ok();
}
ActionData::Pause(duration) => {
let start = Instant::now();
'pause: loop {
if self.do_early_return.load(Ordering::SeqCst) {
return Ok(());
}
if Instant::now().duration_since(start) > *duration {
break 'pause;
}
sleep(Duration::from_millis(1));
}
}
ActionData::AudioEq => {}
ActionData::SystemInfo => {}
ActionData::TimeDate => {}
ActionData::Matrix => {}
}
}
Ok(())
}
}
pub struct CtrlAnime<'a> {
config: Arc<Mutex<UserAnimeConfig>>,
client: RogDbusClientBlocking<'a>,
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
/// Must be the same Atomic as in CtrlAnimeInner
inner_early_return: Arc<AtomicBool>,
}
impl<'a> CtrlAnime<'static> {
pub fn new(
config: Arc<Mutex<UserAnimeConfig>>,
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
client: RogDbusClientBlocking<'static>,
inner_early_return: Arc<AtomicBool>,
) -> Result<Self, Error> {
Ok(CtrlAnime {
config,
client,
inner,
inner_early_return,
})
}
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
})
.ok();
}
}
// The pattern for a zbus method is:
// - Get config lock if required
// - Set inner_early_return to stop the inner run loop temporarily
// - Do actions
// - Write config if required
// - Unset inner_early_return
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnime<'static> {
pub fn insert_asus_gif(
&mut self,
index: u32,
file: String,
time: Timer,
brightness: f32,
) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
let time: AnimTime = time.into();
let file = Path::new(&file);
let action = ActionLoader::AsusAnimation {
file: file.into(),
brightness,
time,
};
// Must make the inner run loop return early
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller
.sequences
.insert(index as usize, &action)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
}
config.anime.push(action);
config.write()?;
let json = serde_json::to_string_pretty(&*config).expect("Parse config to JSON failed");
// Release the inner run loop again
self.inner_early_return.store(false, Ordering::SeqCst);
return Ok(json);
}
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
}
#[allow(clippy::too_many_arguments)]
pub fn insert_image_gif(
&mut self,
index: u32,
file: String,
scale: f32,
angle: f32,
xy: (f32, f32),
time: Timer,
brightness: f32,
) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
let time: AnimTime = time.into();
let file = Path::new(&file);
let translation = Vec2::new(xy.0, xy.1);
let action = ActionLoader::ImageAnimation {
file: file.into(),
scale,
angle,
translation,
brightness,
time,
};
// Must make the inner run loop return early
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller
.sequences
.insert(index as usize, &action)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
}
config.anime.push(action);
config.write()?;
let json =
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
// Release the inner run loop again
self.inner_early_return.store(false, Ordering::SeqCst);
return Ok(json);
}
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
}
#[allow(clippy::too_many_arguments)]
pub fn insert_image(
&mut self,
index: u32,
file: String,
scale: f32,
angle: f32,
xy: (f32, f32),
time: Timer,
brightness: f32,
) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
let file = Path::new(&file);
let time = time.into();
let action = ActionLoader::Image {
file: file.into(),
scale,
angle,
translation: Vec2::new(xy.0, xy.1),
brightness,
time,
};
// Must make the inner run loop return early
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller
.sequences
.insert(index as usize, &action)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
}
config.anime.push(action);
config.write()?;
let json =
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
// Release the inner run loop again
self.inner_early_return.store(false, Ordering::SeqCst);
return Ok(json);
}
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
}
pub fn insert_pause(&mut self, index: u32, millis: u64) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
let action = ActionLoader::Pause(Duration::from_millis(millis));
// Must make the inner run loop return early
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller
.sequences
.insert(index as usize, &action)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
}
config.anime.push(action);
config.write()?;
let json =
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
// Release the inner run loop again
self.inner_early_return.store(false, Ordering::SeqCst);
return Ok(json);
}
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
}
pub fn remove_item(&mut self, index: u32) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
// Must make the inner run loop return early
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller.sequences.remove_item(index as usize);
}
if (index as usize) < config.anime.len() {
config.anime.remove(index as usize);
}
config.write()?;
let json =
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
// Release the inner run loop again
self.inner_early_return.store(false, Ordering::SeqCst);
return Ok(json);
}
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
}
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_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_on_off(on).ok();
}
Ok(())
}
}

70
daemon-user/src/daemon.rs Normal file
View File

@@ -0,0 +1,70 @@
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 zbus::Connection;
use std::sync::atomic::AtomicBool;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" user daemon v{}", rog_user::VERSION);
println!(" rog-anime v{}", rog_anime::VERSION);
println!(" rog-dbus v{}", rog_dbus::VERSION);
println!("rog-supported v{}", rog_supported::VERSION);
let (client, _) = RogDbusClientBlocking::new()?;
let supported = client.proxies().supported().supported_functions()?;
let mut config = UserConfig::new();
config.load_config()?;
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 anime_config = UserAnimeConfig::load_config(config.active_anime)?;
let anime = anime_config.create_anime()?;
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 {
// executor
// .spawn(async move {
// //
// })
// .detach();
// }
loop {
smol::block_on(executor.tick());
}
}

45
daemon-user/src/error.rs Normal file
View File

@@ -0,0 +1,45 @@
use std::fmt;
use rog_anime::error::AnimeError;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
ConfigLoadFail,
ConfigLockFail,
XdgVars,
Anime(AnimeError),
}
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::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"),
Error::Anime(err) => write!(f, "Anime error: {}", err),
}
}
}
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<AnimeError> for Error {
fn from(err: AnimeError) -> Self {
Error::Anime(err)
}
}
impl From<Error> for zbus::fdo::Error {
fn from(err: Error) -> Self {
zbus::fdo::Error::Failed(format!("Anime zbus error: {}", err))
}
}

11
daemon-user/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
pub mod user_config;
pub mod error;
pub mod ctrl_anime;
pub mod zbus_anime;
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
pub static VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -0,0 +1,225 @@
use std::{
fs::{create_dir, OpenOptions},
io::{Read, Write},
time::Duration,
};
use rog_anime::{ActionLoader, AnimTime, Fade, Sequences, Vec2};
use serde_derive::{Deserialize, Serialize};
use crate::error::Error;
#[derive(Debug, Deserialize, Serialize)]
pub struct UserAnimeConfig {
pub name: String,
pub anime: Vec<ActionLoader>,
}
impl UserAnimeConfig {
pub fn create_anime(&self) -> Result<Sequences, Error> {
let mut seq = Sequences::new();
for (idx, action) in self.anime.iter().enumerate() {
seq.insert(idx, action)?;
}
Ok(seq)
}
pub fn write(&self) -> Result<(), Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push("rog");
if !path.exists() {
create_dir(path.clone())?;
}
let name = self.name.clone();
path.push(name + ".cfg");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let json = serde_json::to_string_pretty(&self).unwrap();
file.write_all(json.as_bytes())?;
Ok(())
}
pub fn load_config(name: String) -> Result<UserAnimeConfig, Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push("rog");
if !path.exists() {
create_dir(path.clone())?;
}
path.push(name.clone() + ".cfg");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)?;
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
let default = UserAnimeConfig {
name,
..Default::default()
};
let json = serde_json::to_string_pretty(&default).unwrap();
file.write_all(json.as_bytes())?;
return Ok(default);
} else if let Ok(data) = serde_json::from_str::<UserAnimeConfig>(&buf) {
return Ok(data);
}
}
Err(Error::ConfigLoadFail)
}
}
impl Default for UserAnimeConfig {
fn default() -> Self {
Self {
name: "default".to_string(),
anime: vec![
ActionLoader::AsusImage {
file: "/usr/share/asusd/anime/custom/diagonal-template.png".into(),
brightness: 1.0,
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
None,
Duration::from_secs(2),
)),
},
ActionLoader::AsusAnimation {
file: "/usr/share/asusd/anime/asus/rog/Sunset.gif".into(),
brightness: 0.5,
time: AnimTime::Fade(Fade::new(
Duration::from_secs(6),
None,
Duration::from_secs(3),
)),
},
ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
scale: 0.9,
angle: 0.65,
translation: Vec2::default(),
brightness: 0.5,
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(2)),
Duration::from_secs(2),
)),
},
ActionLoader::Image {
file: "/usr/share/asusd/anime/custom/rust.png".into(),
scale: 1.0,
angle: 0.0,
translation: Vec2::default(),
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(1)),
Duration::from_secs(2),
)),
brightness: 0.6,
},
ActionLoader::Pause(Duration::from_secs(1)),
ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
scale: 0.9,
angle: 0.0,
translation: Vec2::new(3.0, 2.0),
brightness: 0.5,
time: AnimTime::Count(2),
},
],
}
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct UserConfig {
/// Name of active anime config file in the user config directory
pub active_anime: String,
}
impl UserConfig {
pub fn new() -> Self {
Self {
active_anime: "anime-default".to_string(),
}
}
pub fn load_config(&mut self) -> Result<(), Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push("rog");
if !path.exists() {
create_dir(path.clone())?;
}
path.push("rog-user.cfg");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)?;
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
let json = serde_json::to_string_pretty(&self).unwrap();
file.write_all(json.as_bytes())?;
} else if let Ok(data) = serde_json::from_str::<UserConfig>(&buf) {
self.active_anime = data.active_anime;
return Ok(());
}
}
Ok(())
}
pub fn write(&self) -> Result<(), Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push("rog");
if !path.exists() {
create_dir(path.clone())?;
}
path.push("rog-user.cfg");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let json = serde_json::to_string_pretty(&self).unwrap();
file.write_all(json.as_bytes())?;
Ok(())
}
}

View File

@@ -0,0 +1,69 @@
//! # 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/Anime' from service 'org.asuslinux.Daemon' on session bus`.
//!
//! You may prefer to adapt it, instead of using it verbatim.
//!
//! More information can be found in the
//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)
//! section of the zbus documentation.
//!
//! This DBus object implements
//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
//!
//! * [`zbus::fdo::PeerProxy`]
//! * [`zbus::fdo::IntrospectableProxy`]
//! * [`zbus::fdo::PropertiesProxy`]
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
#![allow(clippy::too_many_arguments)]
use zbus::dbus_proxy;
#[dbus_proxy(interface = "org.asuslinux.Daemon")]
trait Daemon {
/// InsertAsusGif method
fn insert_asus_gif(
&self,
index: u32,
file: &str,
time: u32,
count: u32,
brightness: f64,
) -> zbus::Result<String>;
/// InsertImage method
fn insert_image(
&self,
index: u32,
file: &str,
scale: f64,
angle: f64,
xy: &(f64, f64),
brightness: f64,
) -> zbus::Result<String>;
/// InsertImageGif method
fn insert_image_gif(
&self,
index: u32,
file: &str,
scale: f64,
angle: f64,
xy: &(f64, f64),
time: u32,
count: u32,
brightness: f64,
) -> zbus::Result<String>;
/// InsertPause method
fn insert_pause(&self, index: u32, millis: u64) -> zbus::Result<String>;
/// RemoveItem method
fn remove_item(&self, index: u32) -> zbus::Result<String>;
/// SetState method
fn set_state(&self, on: bool) -> zbus::Result<()>;
}

View File

@@ -1,6 +1,6 @@
[package]
name = "asus-nb-ctrl"
version = "2.0.2"
name = "daemon"
version = "4.1.1"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -13,40 +13,36 @@ edition = "2018"
name = "daemon"
path = "src/lib.rs"
[[bin]]
name = "asusctl"
path = "src/main.rs"
[[bin]]
name = "asusd"
path = "src/daemon.rs"
[dependencies]
ctrl-gfx = { path = "../ctrl-gfx" }
asus-nb = { path = "../asus-nb" }
rusb = "^0.6.0"
udev = "^0.4.0"
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" }
async-trait = "^0.1"
smol = "^1.2"
rusb = "^0.9"
udev = "^0.6"
# cli and logging
gumdrop = "^0.8.0"
log = "^0.4.8"
env_logger = "^0.7.1"
log = "^0.4"
env_logger = "^0.9"
# async
zbus = "1.1.1"
zvariant = "2.2.0"
#tokio = { version = "^0.2.4", features = ["rt-threaded", "sync"] }
zbus = "^2.2"
zvariant = "^3.2"
logind-zbus = { version = "^3.0" } #, default-features = false, features = ["non_blocking"] }
# serialisation
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
toml = "0.4.6"
toml = "^0.5.8"
# Device control
sysfs-class = "^0.1.2" # used for backlight control and baord ID
rog_fan_curve = { version = "0.1.5", features = ["serde"] }
# cpu power management
intel-pstate = "^0.2.1"
yansi-term = "^0.1"

80
daemon/src/config.rs Normal file
View File

@@ -0,0 +1,80 @@
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)]
pub struct Config {
/// Save charge limit for restoring on boot
pub bat_charge_limit: u8,
}
impl Config {
fn new() -> Self {
Config {
bat_charge_limit: 100,
}
}
/// `load` will attempt to read the config, and panic if the dir is missing
pub fn load() -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&PathBuf::from(CONFIG_PATH))
.unwrap_or_else(|e| panic!("Error opening {}, {}", CONFIG_PATH, e)); // okay to cause panic here
let mut buf = String::new();
let config;
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
config = Self::new();
} else if let Ok(data) = serde_json::from_str(&buf) {
config = data;
} else {
warn!(
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
CONFIG_PATH, CONFIG_PATH
);
let cfg_old = CONFIG_PATH.to_string() + "-old";
std::fs::rename(CONFIG_PATH, cfg_old).unwrap_or_else(|err| {
panic!(
"Could not rename. Please remove {} then restart service: Error {}",
CONFIG_PATH, err
)
});
config = Self::new();
}
} else {
config = Self::new()
}
config.write();
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", CONFIG_PATH);
} else {
*self = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", CONFIG_PATH));
}
}
}
pub fn write(&self) {
let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
}

View File

@@ -0,0 +1,255 @@
use crate::VERSION;
use log::{error, info, warn};
use rog_anime::Fade;
use rog_anime::{error::AnimeError, ActionData, ActionLoader, AnimTime, Vec2};
use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::time::Duration;
pub static ANIME_CONFIG_PATH: &str = "/etc/asusd/anime.conf";
pub static ANIME_CACHE_PATH: &str = "/etc/asusd/anime-cache.conf";
#[derive(Deserialize, Serialize)]
pub struct AnimeConfigV341 {
pub system: Option<ActionLoader>,
pub boot: Option<ActionLoader>,
pub suspend: Option<ActionLoader>,
pub shutdown: Option<ActionLoader>,
}
impl AnimeConfigV341 {
pub(crate) fn into_current(self) -> AnimeConfig {
AnimeConfig {
system: if let Some(ani) = self.system {
vec![ani]
} else {
vec![]
},
boot: if let Some(ani) = self.boot {
vec![ani]
} else {
vec![]
},
wake: if let Some(ani) = self.suspend {
vec![ani]
} else {
vec![]
},
shutdown: if let Some(ani) = self.shutdown {
vec![ani]
} else {
vec![]
},
brightness: 1.0,
awake_enabled: true,
boot_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AnimeConfigV352 {
pub system: Vec<ActionLoader>,
pub boot: Vec<ActionLoader>,
pub wake: Vec<ActionLoader>,
pub shutdown: Vec<ActionLoader>,
pub brightness: f32,
}
impl AnimeConfigV352 {
pub(crate) fn into_current(self) -> AnimeConfig {
AnimeConfig {
system: self.system,
boot: self.boot,
wake: self.wake,
shutdown: self.shutdown,
brightness: 1.0,
awake_enabled: true,
boot_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize, Default)]
pub struct AnimeConfigCached {
pub system: Vec<ActionData>,
pub boot: Vec<ActionData>,
pub wake: Vec<ActionData>,
pub shutdown: Vec<ActionData>,
}
impl AnimeConfigCached {
pub fn init_from_config(&mut self, config: &AnimeConfig) -> Result<(), AnimeError> {
let mut sys = Vec::with_capacity(config.system.len());
for ani in config.system.iter() {
sys.push(ActionData::from_anime_action(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)?);
}
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)?);
}
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)?);
}
self.shutdown = shutdown;
Ok(())
}
}
/// Config for base system actions for the anime display
#[derive(Deserialize, Serialize)]
pub struct AnimeConfig {
pub system: Vec<ActionLoader>,
pub boot: Vec<ActionLoader>,
pub wake: Vec<ActionLoader>,
pub shutdown: Vec<ActionLoader>,
pub brightness: f32,
pub awake_enabled: bool,
pub boot_anim_enabled: bool,
}
impl Default for AnimeConfig {
fn default() -> Self {
AnimeConfig {
system: Vec::new(),
boot: Vec::new(),
wake: Vec::new(),
shutdown: Vec::new(),
brightness: 1.0,
awake_enabled: true,
boot_anim_enabled: true,
}
}
}
impl AnimeConfig {
/// `load` will attempt to read the config, and panic if the dir is missing
pub fn load() -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&ANIME_CONFIG_PATH)
.unwrap_or_else(|_| {
panic!(
"The file {} or directory /etc/asusd/ is missing",
ANIME_CONFIG_PATH
)
}); // okay to cause panic here
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
return AnimeConfig::create_default(&mut file);
} else {
if let Ok(data) = serde_json::from_str(&buf) {
return data;
} else if let Ok(data) = serde_json::from_str::<AnimeConfigV341>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<AnimeConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
}
warn!(
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
ANIME_CONFIG_PATH, ANIME_CONFIG_PATH
);
let cfg_old = ANIME_CONFIG_PATH.to_string() + "-old";
std::fs::rename(ANIME_CONFIG_PATH, cfg_old).unwrap_or_else(|err| {
panic!(
"Could not rename. Please remove {} then restart service: Error {}",
ANIME_CONFIG_PATH, err
)
});
}
}
AnimeConfig::create_default(&mut file)
}
fn create_default(file: &mut File) -> Self {
// create a default config here
let config = AnimeConfig {
system: vec![],
boot: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
scale: 0.9,
angle: 0.65,
translation: Vec2::default(),
brightness: 1.0,
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(2)),
Duration::from_secs(2),
)),
}],
wake: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
scale: 0.9,
angle: 0.65,
translation: Vec2::default(),
brightness: 1.0,
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(2)),
Duration::from_secs(2),
)),
}],
shutdown: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
scale: 0.9,
angle: 0.0,
translation: Vec2::new(3.0, 2.0),
brightness: 1.0,
time: AnimTime::Infinite,
}],
brightness: 1.0,
awake_enabled: true,
boot_anim_enabled: true,
};
// Should be okay to unwrap this as is since it is a Default
let json = serde_json::to_string_pretty(&config).unwrap();
file.write_all(json.as_bytes())
.unwrap_or_else(|_| panic!("Could not write {}", ANIME_CONFIG_PATH));
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&ANIME_CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", ANIME_CONFIG_PATH, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", ANIME_CONFIG_PATH);
} else {
let x: AnimeConfig = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", ANIME_CONFIG_PATH));
*self = x;
}
}
}
pub fn write(&self) {
let mut file = File::create(ANIME_CONFIG_PATH).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
}

View File

@@ -0,0 +1,426 @@
pub mod config;
pub mod zbus;
use ::zbus::Connection;
use async_trait::async_trait;
use log::{error, info, warn};
use logind_zbus::manager::ManagerProxy;
use rog_anime::{
error::AnimeError,
usb::{
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,
};
use rog_supported::AnimeSupportedFunctions;
use rusb::{Device, DeviceHandle};
use smol::{stream::StreamExt, Executor};
use std::{
cell::RefCell,
error::Error,
sync::{Arc, Mutex},
thread::sleep,
};
use std::{
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use crate::{error::RogError, GetSupported};
use self::config::{AnimeConfig, AnimeConfigCached};
impl GetSupported for CtrlAnime {
type A = AnimeSupportedFunctions;
fn get_supported() -> Self::A {
AnimeSupportedFunctions(CtrlAnime::get_device(VENDOR_ID, PROD_ID).is_ok())
}
}
pub struct CtrlAnime {
_node: String,
handle: RefCell<DeviceHandle<rusb::GlobalContext>>,
cache: AnimeConfigCached,
config: AnimeConfig,
// set to force thread to exit
thread_exit: Arc<AtomicBool>,
// Set to false when the thread exits
thread_running: Arc<AtomicBool>,
}
impl CtrlAnime {
#[inline]
pub fn new(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
let node = Self::find_node("193b")?;
let device = Self::get_dev_handle()?;
info!("Device has an AniMe Matrix display");
let mut cache = AnimeConfigCached::default();
cache.init_from_config(&config)?;
let ctrl = CtrlAnime {
_node: node,
handle: RefCell::new(device),
cache,
config,
thread_exit: Arc::new(AtomicBool::new(false)),
thread_running: Arc::new(AtomicBool::new(false)),
};
ctrl.do_initialization();
Ok(ctrl)
}
fn 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)?;
let mut device = device.open()?;
device.reset()?;
device.set_auto_detach_kernel_driver(true).map_err(|err| {
error!("Auto-detach kernel driver failed: {}", err);
err
})?;
device.claim_interface(0).map_err(|err| {
error!("Could not claim device interface: {}", err);
err
})?;
Ok(device)
}
fn get_device(vendor: u16, product: u16) -> Result<Device<rusb::GlobalContext>, rusb::Error> {
for device in rusb::devices()?.iter() {
let device_desc = device.device_descriptor()?;
if device_desc.vendor_id() == vendor && device_desc.product_id() == product {
return Ok(device);
}
}
Err(rusb::Error::NoDevice)
}
/// Start an action thread. This is classed as a singleton and there should be only
/// one running - so the thread uses atomics to signal run/exit.
///
/// Because this also writes to the usb device, other write tries (display only) *must*
/// get the mutex lock and set the thread_exit atomic.
fn run_thread(inner: Arc<Mutex<CtrlAnime>>, actions: Vec<ActionData>, mut once: bool) {
if actions.is_empty() {
warn!("AniMe system actions was empty");
return;
}
// Loop rules:
// - Lock the mutex **only when required**. That is, the lock must be held for the shortest duration possible.
// - An AtomicBool used for thread exit should be checked in every loop, including nested
// The only reason for this outer thread is to prevent blocking while waiting for the
// next spawned thread to exit
std::thread::Builder::new()
.name("AniMe system thread start".into())
.spawn(move || {
info!("AniMe new system thread started");
// Getting copies of these Atomics is done *in* the thread to ensure
// we don't block other threads/main
let thread_exit;
let thread_running;
loop {
if let Ok(lock) = inner.try_lock() {
thread_exit = lock.thread_exit.clone();
thread_running = lock.thread_running.clone();
break;
}
}
// First two loops are to ensure we *do* aquire a lock on the mutex
// The reason the loop is required is because the USB writes can block
// for up to 10ms. We can't fail to get the atomics.
while thread_running.load(Ordering::SeqCst) {
// Make any running loop exit first
thread_exit.store(true, Ordering::SeqCst);
}
info!("AniMe no previous system thread running (now)");
thread_exit.store(false, Ordering::SeqCst);
'main: loop {
thread_running.store(true, Ordering::SeqCst);
for action in actions.iter() {
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
match action {
ActionData::Animation(frames) => {
if let Err(err) = rog_anime::run_animation(frames, &|frame| {
if thread_exit.load(Ordering::Acquire) {
info!("rog-anime: frame-loop was asked to exit");
return Ok(true); // Do safe exit
}
inner
.try_lock()
.map(|lock| {
lock.write_data_buffer(frame);
false // Don't exit yet
})
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
AnimeError::NoFrames
})
}) {
warn!("rog_anime::run_animation:Animation {}", err);
break 'main;
};
}
ActionData::Image(image) => {
once = false;
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(image.as_ref().clone())
}
}
ActionData::Pause(duration) => sleep(*duration),
ActionData::AudioEq => {}
ActionData::SystemInfo => {}
ActionData::TimeDate => {}
ActionData::Matrix => {}
}
}
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
if once || actions.is_empty() {
break 'main;
}
}
// Clear the display on exit
if let Ok(lock) = inner.try_lock() {
let data = AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec());
lock.write_data_buffer(data);
}
// Loop ended, set the atmonics
thread_running.store(false, Ordering::SeqCst);
info!("AniMe system thread exited");
})
.map(|err| info!("AniMe system thread: {:?}", err))
.ok();
}
fn write_bytes(&self, message: &[u8]) {
// if let Ok(mut file) = OpenOptions::new().write(true).open(&self.node) {
// println!("write: {:02x?}", &message);
// return file
// .write_all(message).unwrap();
// }
let mut error = false;
match self.handle.borrow().write_control(
0x21, // request_type
0x09, // request
0x35e, // value
0x00, // index
message,
Duration::from_millis(200),
) {
Ok(_) => {}
Err(err) => match err {
rusb::Error::Timeout => {}
_ => {
error = true;
error!("Failed to write to led interrupt: {}", err);
}
},
}
if error {
warn!("Will attempt to get AniMe device handle again");
match Self::get_dev_handle() {
Ok(dev) => {
self.handle.replace(dev);
}
Err(err) => {
error!("Failed to get AniMe device: {}", err);
}
}
}
}
/// Write only a data packet. This will modify the leds brightness using the
/// global brightness set in config.
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) {
for led in buffer.get_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);
for row in data.iter() {
self.write_bytes(row);
}
self.write_bytes(&pkt_for_flush());
}
fn do_initialization(&self) {
let pkts = pkts_for_init();
self.write_bytes(&pkts[0]);
self.write_bytes(&pkts[1]);
}
}
pub struct CtrlAnimeTask {
inner: Arc<Mutex<CtrlAnime>>,
}
impl CtrlAnimeTask {
pub async fn new(inner: Arc<Mutex<CtrlAnime>>) -> CtrlAnimeTask {
Self { inner }
}
}
#[async_trait]
impl crate::CtrlTask for CtrlAnimeTask {
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
let connection = Connection::system()
.await
.expect("CtrlAnimeTask could not create dbus connection");
let manager = ManagerProxy::new(&connection)
.await
.expect("CtrlAnimeTask could not create ManagerProxy");
let 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() {
if args.start {
loop {
// Loop is required to try an attempt to get the mutex *without* blocking
// other threads - it is possible to end up with deadlocks otherwise.
if let Ok(lock) = inner.clone().try_lock() {
info!("CtrlAnimeTask running sleep animation");
CtrlAnime::run_thread(
inner.clone(),
lock.cache.shutdown.clone(),
true,
);
break;
}
}
} else {
loop {
if let Ok(lock) = inner.clone().try_lock() {
info!("CtrlAnimeTask running wake animation");
CtrlAnime::run_thread(
inner.clone(),
lock.cache.wake.clone(),
true,
);
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() {
if args.start {
loop {
if let Ok(lock) = inner.clone().try_lock() {
info!("CtrlAnimeTask running sleep animation");
CtrlAnime::run_thread(
inner.clone(),
lock.cache.shutdown.clone(),
true,
);
break;
}
}
} else {
// If waking up - intention is to catch hibernation event
loop {
if let Ok(lock) = inner.clone().lock() {
info!("CtrlAnimeTask running wake animation");
CtrlAnime::run_thread(
inner.clone(),
lock.cache.wake.clone(),
true,
);
break;
}
}
}
}
})
.await;
}
})
.detach();
Ok(())
}
}
pub struct CtrlAnimeReloader(pub Arc<Mutex<CtrlAnime>>);
impl crate::Reloadable for CtrlAnimeReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_on(lock.config.awake_enabled));
lock.write_bytes(&pkt_for_apply());
lock.write_bytes(&pkt_for_set_boot(lock.config.boot_anim_enabled));
lock.write_bytes(&pkt_for_apply());
let action = lock.cache.boot.clone();
CtrlAnime::run_thread(self.0.clone(), action, true);
}
Ok(())
}
}

View File

@@ -0,0 +1,140 @@
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use rog_anime::{
usb::{pkt_for_apply, pkt_for_set_boot, pkt_for_set_on},
AnimeDataBuffer, AnimePowerStates,
};
use zbus::{dbus_interface, Connection, SignalContext};
use std::sync::atomic::Ordering;
use super::CtrlAnime;
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
/// The struct with the main dbus methods requires this trait
#[async_trait]
impl crate::ZbusAdd for CtrlAnimeZbus {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Anime", server).await;
}
}
// None of these calls can be guarnateed to succeed unless we loop until okay
// If the try_lock *does* succeed then any other thread trying to lock will not grab it
// until we finish.
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnimeZbus {
/// Writes a data stream of length. Will force system thread to exit until it is restarted
fn write(&self, input: AnimeDataBuffer) {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
lock.write_data_buffer(input);
break 'outer;
}
}
}
/// Set the global AniMe brightness
fn set_brightness(&self, bright: f32) {
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
let mut bright = bright;
if bright < 0.0 {
bright = 0.0
} else if bright > 254.0 {
bright = 254.0;
}
lock.config.brightness = bright;
lock.config.write();
break 'outer;
}
}
}
/// Set whether the AniMe is displaying images/data
async fn set_on_off(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>, status: bool) {
let states;
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_on(status));
lock.config.awake_enabled = status;
lock.config.write();
states = Some(AnimePowerStates {
brightness: lock.config.brightness.floor() as u8,
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
});
break 'outer;
}
}
if let Some(state) = states {
Self::notify_power_states(&ctxt, state).await.ok();
}
}
/// Set whether the AniMe will show boot, suspend, or off animations
async fn set_boot_on_off(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>, on: bool) {
let states;
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_boot(on));
lock.write_bytes(&pkt_for_apply());
lock.config.boot_anim_enabled = on;
lock.config.write();
states = Some(AnimePowerStates {
brightness: lock.config.brightness.floor() as u8,
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
});
break 'outer;
}
}
if let Some(state) = states {
Self::notify_power_states(&ctxt, state).await.ok();
}
}
/// The main loop is the base system set action if the user isn't running
/// the user daemon
fn run_main_loop(&self, start: bool) {
if start {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false);
break 'outer;
}
}
}
}
/// Get status of if the AniMe LEDs are on/displaying while system is awake
#[dbus_interface(property)]
fn awake_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.awake_enabled;
}
true
}
/// Get the status of if factory system-status animations are enabled
#[dbus_interface(property)]
fn boot_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.boot_anim_enabled;
}
true
}
/// Notify listeners of the status of AniMe LED power and factory system-status animations
#[dbus_interface(signal)]
async fn notify_power_states(
ctxt: &SignalContext<'_>,
data: AnimePowerStates,
) -> zbus::Result<()>;
}

View File

@@ -0,0 +1,293 @@
use crate::laptops::LaptopLedData;
use log::{error, info, warn};
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, LedBrightness, LedPowerStates};
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/aura.conf";
#[derive(Deserialize, Serialize)]
pub struct 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,
power_states: LedPowerStates {
boot_anim: true,
sleep_anim: true,
all_leds: true,
keys_leds: true,
side_leds: true,
},
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraConfigV407 {
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 side_leds_enabled: bool,
}
impl AuraConfigV407 {
pub(crate) fn into_current(self) -> AuraConfig {
AuraConfig {
brightness: self.brightness,
current_mode: self.current_mode,
builtins: self.builtins,
multizone: self.multizone,
power_states: LedPowerStates {
boot_anim: true,
sleep_anim: self.sleep_anim_enabled,
all_leds: self.awake_enabled,
keys_leds: self.awake_enabled,
side_leds: self.side_leds_enabled,
},
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraConfig {
pub brightness: LedBrightness,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
pub power_states: LedPowerStates,
}
impl Default for AuraConfig {
fn default() -> Self {
AuraConfig {
brightness: LedBrightness::Med,
current_mode: AuraModeNum::Static,
builtins: BTreeMap::new(),
multizone: None,
power_states: LedPowerStates {
boot_anim: true,
sleep_anim: true,
all_leds: true,
keys_leds: true,
side_leds: true,
},
}
}
}
impl AuraConfig {
/// `load` will attempt to read the config, and panic if the dir is missing
pub fn load(supported_led_modes: &LaptopLedData) -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&AURA_CONFIG_PATH)
.unwrap_or_else(|_| {
panic!(
"The file {} or directory /etc/asusd/ is missing",
AURA_CONFIG_PATH
)
}); // okay to cause panic here
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
return AuraConfig::create_default(&mut file, supported_led_modes);
} else {
if let Ok(data) = serde_json::from_str(&buf) {
return data;
} else if let Ok(data) = serde_json::from_str::<AuraConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated AuraConfig version");
return config;
} else if let Ok(data) = serde_json::from_str::<AuraConfigV407>(&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",
AURA_CONFIG_PATH, AURA_CONFIG_PATH
);
let cfg_old = AURA_CONFIG_PATH.to_string() + "-old";
std::fs::rename(AURA_CONFIG_PATH, cfg_old).unwrap_or_else(|err| {
panic!(
"Could not rename. Please remove {} then restart service: Error {}",
AURA_CONFIG_PATH, err
)
});
}
}
AuraConfig::create_default(&mut file, supported_led_modes)
}
fn create_default(file: &mut File, support_data: &LaptopLedData) -> Self {
// create a default config here
let mut config = AuraConfig::default();
for n in &support_data.standard {
config
.builtins
.insert(*n, AuraEffect::default_with_mode(*n));
}
// Should be okay to unwrap this as is since it is a Default
let json = serde_json::to_string(&config).unwrap();
file.write_all(json.as_bytes())
.unwrap_or_else(|_| panic!("Could not write {}", AURA_CONFIG_PATH));
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&AURA_CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", AURA_CONFIG_PATH, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", AURA_CONFIG_PATH);
} else {
let x: AuraConfig = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", AURA_CONFIG_PATH));
*self = x;
}
}
}
pub fn write(&self) {
let mut file = File::create(AURA_CONFIG_PATH).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
/// Multipurpose, will accept AuraEffect with zones and put in the correct store
pub fn set_builtin(&mut self, effect: AuraEffect) {
match effect.zone() {
AuraZone::None => {
self.builtins.insert(*effect.mode(), effect);
}
_ => {
if let Some(multi) = self.multizone.as_mut() {
multi.set(effect)
}
}
}
}
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect; 4]> {
if let Some(multi) = &self.multizone {
if aura_type == AuraModeNum::Static {
return Some(multi.static_());
} else if aura_type == AuraModeNum::Breathe {
return Some(multi.breathe());
}
}
None
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraMultiZone {
static_: [AuraEffect; 4],
breathe: [AuraEffect; 4],
}
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,
}
}
}
pub fn static_(&self) -> &[AuraEffect; 4] {
&self.static_
}
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()
},
],
}
}
}

View File

@@ -0,0 +1,435 @@
// Only these two packets must be 17 bytes
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
use crate::{
error::RogError,
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
CtrlTask,
};
use async_trait::async_trait;
use log::{error, info, warn};
use logind_zbus::manager::ManagerProxy;
use rog_aura::usb::leds_message;
use rog_aura::{
usb::{LED_APPLY, LED_SET},
AuraEffect, LedBrightness, LED_MSG_LEN,
};
use rog_supported::LedSupportedFunctions;
use smol::{stream::StreamExt, Executor};
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::Connection;
use crate::GetSupported;
use super::config::AuraConfig;
impl GetSupported for CtrlKbdLed {
type A = LedSupportedFunctions;
fn get_supported() -> Self::A {
// let mode = <&str>::from(&<AuraModes>::from(*mode));
let laptop = LaptopLedData::get_data();
let stock_led_modes = laptop.standard;
let multizone_led_mode = laptop.multizone;
let per_key_led_mode = laptop.per_key;
LedSupportedFunctions {
brightness_set: CtrlKbdLed::get_kbd_bright_path().is_some(),
stock_led_modes,
multizone_led_mode,
per_key_led_mode,
}
}
}
pub struct CtrlKbdLed {
pub led_node: Option<String>,
pub bright_node: String,
pub supported_modes: LaptopLedData,
pub flip_effect_write: bool,
pub config: AuraConfig,
}
pub struct CtrlKbdLedTask {
inner: Arc<Mutex<CtrlKbdLed>>,
}
impl CtrlKbdLedTask {
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
Self { inner }
}
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(&lock.bright_node)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&lock.bright_node).into(), err)
}
_ => RogError::Path((&lock.bright_node).into(), err),
})?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)
.map_err(|err| RogError::Read("buffer".into(), err))?;
if let Some(num) = char::from(buf[0]).to_digit(10) {
if lock.config.brightness != num.into() {
lock.config.read();
lock.config.brightness = num.into();
lock.config.write();
}
return Ok(());
}
Err(RogError::ParseLed)
}
}
#[async_trait]
impl CtrlTask for CtrlKbdLedTask {
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
let connection = Connection::system()
.await
.expect("CtrlKbdLedTask could not create dbus connection");
let manager = ManagerProxy::new(&connection)
.await
.expect("CtrlKbdLedTask could not create ManagerProxy");
let 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() {
// If waking up
if !args.start {
info!("CtrlKbdLedTask reloading brightness and modes");
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() {
// Can't reload brightness due to system setting the brightness on sleep/wake
// and the config update task saving that change.
// lock.set_brightness(lock.config.brightness)
// .map_err(|e| error!("CtrlKbdLedTask: {e}"))
// .ok();
if let Some(mode) =
lock.config.builtins.get(&lock.config.current_mode)
{
lock.write_mode(mode)
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
.ok();
}
break;
}
}
}
}
})
.await;
}
})
.detach();
let inner = self.inner.clone();
self.repeating_task(500, executor, move || loop {
if let Ok(ref mut lock) = inner.try_lock() {
Self::update_config(lock).unwrap();
break;
}
})
.await;
Ok(())
}
}
pub struct CtrlKbdLedReloader(pub Arc<Mutex<CtrlKbdLed>>);
impl crate::Reloadable for CtrlKbdLedReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(mut ctrl) = self.0.try_lock() {
let current = ctrl.config.current_mode;
if let Some(mode) = ctrl.config.builtins.get(&current).cloned() {
ctrl.do_command(mode).ok();
}
ctrl.set_power_states(&ctrl.config)
.map_err(|err| warn!("{err}"))
.ok();
}
Ok(())
}
}
pub struct CtrlKbdLedZbus(pub Arc<Mutex<CtrlKbdLed>>);
impl CtrlKbdLedZbus {
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
Self(inner)
}
}
impl CtrlKbdLed {
#[inline]
pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> {
// TODO: return error if *all* nodes are None
let mut led_node = None;
for prod in ASUS_KEYBOARD_DEVICES.iter() {
match Self::find_led_node(prod) {
Ok(node) => {
led_node = Some(node);
info!("Looked for keyboard controller 0x{prod}: Found");
break;
}
Err(err) => info!("Looked for keyboard controller 0x{prod}: {err}"),
}
}
let bright_node = Self::get_kbd_bright_path();
if led_node.is_none() && bright_node.is_none() {
return Err(RogError::MissingFunction(
"All keyboard features missing, you may require a v5.11 series kernel or newer"
.into(),
));
}
if bright_node.is_none() {
return Err(RogError::MissingFunction(
"No brightness control, you may require a v5.11 series kernel or newer".into(),
));
}
let ctrl = CtrlKbdLed {
led_node,
bright_node: bright_node.unwrap(), // If was none then we already returned above
supported_modes,
flip_effect_write: false,
config,
};
Ok(ctrl)
}
fn get_kbd_bright_path() -> Option<String> {
if Path::new(KBD_BRIGHT_PATH).exists() {
return Some(KBD_BRIGHT_PATH.to_string());
}
None
}
pub(super) fn get_brightness(&self) -> Result<u8, RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(&self.bright_node)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
}
_ => RogError::Path((&self.bright_node).into(), err),
})?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)
.map_err(|err| RogError::Read("buffer".into(), err))?;
Ok(buf[0])
}
pub(super) fn set_brightness(&self, brightness: LedBrightness) -> Result<(), RogError> {
let path = Path::new(&self.bright_node);
let mut file =
OpenOptions::new()
.write(true)
.open(&path)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
}
_ => RogError::Path((&self.bright_node).into(), err),
})?;
file.write_all(&[brightness.as_char_code()])
.map_err(|err| RogError::Read("buffer".into(), err))?;
Ok(())
}
pub fn next_brightness(&mut self) -> Result<(), RogError> {
let mut bright = (self.config.brightness as u32) + 1;
if bright > 3 {
bright = 0;
}
self.config.brightness = <LedBrightness>::from(bright);
self.config.write();
self.set_brightness(self.config.brightness)
}
pub fn prev_brightness(&mut self) -> Result<(), RogError> {
let mut bright = self.config.brightness as u32;
if bright == 0 {
bright = 3;
} else {
bright -= 1;
}
self.config.brightness = <LedBrightness>::from(bright);
self.config.write();
self.set_brightness(self.config.brightness)
}
/// Set combination state for boot animation/sleep animation/all leds/keys leds/side leds LED active
pub(super) fn set_power_states(&self, config: &AuraConfig) -> Result<(), RogError> {
let bytes = leds_message(
config.power_states.boot_anim,
config.power_states.sleep_anim,
config.power_states.all_leds,
config.power_states.keys_leds,
config.power_states.side_leds,
);
// Quite ugly, must be a more idiomatic way to do
let message = [
0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
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> {
let mut enumerator = udev::Enumerator::new().map_err(|err| {
warn!("{}", err);
RogError::Udev("enumerator failed".into(), err)
})?;
enumerator.match_subsystem("hidraw").map_err(|err| {
warn!("{}", err);
RogError::Udev("match_subsystem failed".into(), err)
})?;
for device in enumerator.scan_devices().map_err(|err| {
warn!("{}", err);
RogError::Udev("scan_devices failed".into(), err)
})? {
if let Some(parent) = device
.parent_with_subsystem_devtype("usb", "usb_device")
.map_err(|err| {
warn!("{}", err);
RogError::Udev("parent_with_subsystem_devtype failed".into(), err)
})?
{
if parent
.attribute_value("idProduct")
.ok_or_else(|| RogError::NotFound("LED idProduct".into()))?
== id_product
{
if let Some(dev_node) = device.devnode() {
info!("Using device at: {:?} for LED control", dev_node);
return Ok(dev_node.to_string_lossy().to_string());
}
}
}
}
Err(RogError::MissingFunction(
"ASUS LED device node not found".into(),
))
}
pub(crate) fn do_command(&mut self, mode: AuraEffect) -> Result<(), RogError> {
self.set_and_save(mode)
}
/// Should only be used if the bytes you are writing are verified correct
#[inline]
fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
if let Some(led_node) = &self.led_node {
if let Ok(mut file) = OpenOptions::new().write(true).open(led_node) {
// println!("write: {:02x?}", &message);
return file
.write_all(message)
.map_err(|err| RogError::Write("write_bytes".into(), err));
}
}
Err(RogError::NotSupported)
}
/// Write an effect block
#[inline]
fn _write_effect(&mut self, effect: &[Vec<u8>]) -> Result<(), RogError> {
if self.flip_effect_write {
for row in effect.iter().rev() {
self.write_bytes(row)?;
}
} else {
for row in effect.iter() {
self.write_bytes(row)?;
}
}
self.flip_effect_write = !self.flip_effect_write;
Ok(())
}
/// Used to set a builtin mode and save the settings for it
///
/// This needs to be universal so that settings applied by dbus stick
#[inline]
fn set_and_save(&mut self, mode: AuraEffect) -> Result<(), RogError> {
self.config.read();
self.write_mode(&mode)?;
self.config.current_mode = *mode.mode();
self.config.set_builtin(mode);
self.config.write();
Ok(())
}
#[inline]
pub(super) fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
let current = self.config.current_mode;
if let Some(idx) = self
.supported_modes
.standard
.iter()
.position(|v| *v == current)
{
let mut idx = idx;
// goes past end of array
if reverse {
if idx == 0 {
idx = self.supported_modes.standard.len() - 1;
} else {
idx -= 1;
}
} else {
idx += 1;
if idx == self.supported_modes.standard.len() {
idx = 0;
}
}
let next = self.supported_modes.standard[idx];
self.config.read();
if let Some(data) = self.config.builtins.get(&next) {
self.write_mode(data)?;
self.config.current_mode = next;
}
self.config.write();
}
Ok(())
}
#[inline]
fn write_mode(&self, mode: &AuraEffect) -> Result<(), RogError> {
if !self.supported_modes.standard.contains(mode.mode()) {
return Err(RogError::NotSupported);
}
let bytes: [u8; LED_MSG_LEN] = mode.into();
self.write_bytes(&bytes)?;
self.write_bytes(&LED_SET)?;
// Changes won't persist unless apply is set
self.write_bytes(&LED_APPLY)?;
Ok(())
}
}

View File

@@ -0,0 +1,3 @@
pub mod config;
pub mod controller;
pub mod zbus;

View File

@@ -0,0 +1,312 @@
use async_trait::async_trait;
use log::warn;
use rog_aura::{AuraEffect, LedBrightness, LedPowerStates};
use zbus::{dbus_interface, Connection, SignalContext};
use super::controller::CtrlKbdLedZbus;
#[async_trait]
impl crate::ZbusAdd for CtrlKbdLedZbus {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Led", server).await;
}
}
/// The main interface for changing, reading, or notfying signals
///
/// LED commands are split between Brightness, Modes, Per-Key
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlKbdLedZbus {
/// Set the keyboard brightness level (0-3)
async fn set_brightness(&mut self, brightness: LedBrightness) {
if let Ok(ctrl) = self.0.try_lock() {
ctrl.set_brightness(brightness)
.map_err(|err| warn!("{}", err))
.ok();
}
}
/// Set the keyboard LED to enabled while the device is awake
async fn set_boot_enabled(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
enabled: bool,
) {
let mut states = None;
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.config.power_states.boot_anim = enabled;
ctrl.config.write();
ctrl.set_power_states(&ctrl.config)
.map_err(|err| warn!("{}", err))
.ok();
states = Some(ctrl.config.power_states);
}
// 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));
}
}
/// Set the keyboard LED suspend animation to enabled while the device is suspended
async fn set_sleep_enabled(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
enabled: bool,
) {
let mut states = None;
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.config.power_states.sleep_anim = enabled;
ctrl.config.write();
ctrl.set_power_states(&ctrl.config)
.map_err(|err| warn!("{}", err))
.ok();
states = Some(ctrl.config.power_states);
}
if let Some(states) = states {
Self::notify_power_states(&ctxt, &states)
.await
.unwrap_or_else(|err| warn!("{}", err));
}
}
/// Set all the keyboard LEDs (keys and side) to enabled
async fn set_all_leds_enabled(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
enabled: bool,
) {
let mut states = None;
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.config.power_states.all_leds = enabled;
ctrl.config.power_states.keys_leds = enabled;
ctrl.config.power_states.side_leds = enabled;
ctrl.config.write();
ctrl.set_power_states(&ctrl.config)
.map_err(|err| warn!("{}", err))
.ok();
states = Some(ctrl.config.power_states);
}
// 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));
}
}
/// Set the keyboard keys LEDs to enabled
async fn set_keys_leds_enabled(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
enabled: bool,
) {
let mut states = None;
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.config.power_states.keys_leds = enabled;
ctrl.config.write();
ctrl.set_power_states(&ctrl.config)
.map_err(|err| warn!("{}", err))
.ok();
states = Some(ctrl.config.power_states);
}
// 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));
}
}
/// Set the keyboard side LEDs to enabled
async fn set_side_leds_enabled(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
enabled: bool,
) {
let mut states = None;
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.config.power_states.side_leds = enabled;
ctrl.config.write();
ctrl.set_power_states(&ctrl.config)
.map_err(|err| warn!("{}", err))
.ok();
states = Some(ctrl.config.power_states);
}
// 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));
}
}
async fn set_led_mode(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
effect: AuraEffect,
) {
let mut led = None;
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) {
led = Some(mode.clone());
}
}
Err(err) => {
warn!("{}", err);
}
}
}
if let Some(led) = led {
Self::notify_led(&ctxt, led)
.await
.unwrap_or_else(|err| warn!("{}", err));
}
}
async fn next_led_mode(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
let mut led = None;
if let Ok(mut ctrl) = self.0.lock() {
ctrl.toggle_mode(false)
.unwrap_or_else(|err| warn!("{}", err));
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));
}
}
async fn prev_led_mode(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
let mut led = None;
if let Ok(mut ctrl) = self.0.lock() {
ctrl.toggle_mode(true)
.unwrap_or_else(|err| warn!("{}", err));
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));
}
}
async fn next_led_brightness(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.next_brightness()
.unwrap_or_else(|err| warn!("{}", err));
}
}
async fn prev_led_brightness(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.prev_brightness()
.unwrap_or_else(|err| warn!("{}", err));
}
}
#[dbus_interface(property)]
async fn boot_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.power_states.boot_anim;
}
true
}
#[dbus_interface(property)]
async fn sleep_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.power_states.sleep_anim;
}
true
}
#[dbus_interface(property)]
async fn all_leds_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.power_states.all_leds;
}
true
}
#[dbus_interface(property)]
async fn keys_leds_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.power_states.keys_leds;
}
true
}
#[dbus_interface(property)]
fn side_leds_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.power_states.side_leds;
}
true
}
/// Return the current mode data
#[dbus_interface(property)]
async fn led_mode(&self) -> String {
if let Ok(ctrl) = self.0.try_lock() {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
return json;
}
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not deserialise".to_string()
}
/// Return a list of available modes
#[dbus_interface(property)]
async fn led_modes(&self) -> String {
if let Ok(ctrl) = self.0.try_lock() {
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
return json;
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not serialise".to_string()
}
/// Return the current LED brightness
#[dbus_interface(property)]
async fn led_brightness(&self) -> i8 {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1);
}
warn!("SetKeyBacklight could not serialise");
-1
}
#[dbus_interface(signal)]
async fn notify_led(signal_ctxt: &SignalContext<'_>, data: AuraEffect) -> zbus::Result<()>;
#[dbus_interface(signal)]
async fn notify_power_states(
signal_ctxt: &SignalContext<'_>,
data: &LedPowerStates,
) -> zbus::Result<()>;
}

201
daemon/src/ctrl_charge.rs Normal file
View File

@@ -0,0 +1,201 @@
use crate::CtrlTask;
use crate::{config::Config, error::RogError, GetSupported};
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 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";
static BAT_CHARGE_PATH2: &str = "/sys/class/power_supply/BAT2/charge_control_end_threshold";
impl GetSupported for CtrlCharge {
type A = ChargeSupportedFunctions;
fn get_supported() -> Self::A {
ChargeSupportedFunctions {
charge_level_set: CtrlCharge::get_battery_path().is_ok(),
}
}
}
pub struct CtrlCharge {
config: Arc<Mutex<Config>>,
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlCharge {
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(&ctxt, limit).await?;
Ok(())
}
fn limit(&self) -> i8 {
if let Ok(config) = self.config.try_lock() {
return config.bat_charge_limit as i8;
}
-1
}
#[dbus_interface(signal)]
async fn notify_charge(ctxt: &SignalContext<'_>, limit: u8) -> zbus::Result<()>;
}
#[async_trait]
impl crate::ZbusAdd for CtrlCharge {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Charge", server).await;
}
}
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)?;
}
Ok(())
}
}
impl CtrlCharge {
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
CtrlCharge::get_battery_path()?;
Ok(CtrlCharge { config })
}
fn get_battery_path() -> Result<&'static str, RogError> {
if Path::new(BAT_CHARGE_PATH0).exists() {
Ok(BAT_CHARGE_PATH0)
} else if Path::new(BAT_CHARGE_PATH1).exists() {
Ok(BAT_CHARGE_PATH1)
} else if Path::new(BAT_CHARGE_PATH2).exists() {
Ok(BAT_CHARGE_PATH2)
} else {
Err(RogError::MissingFunction(
"Charge control not available, you may require a v5.8.10 series kernel or newer"
.into(),
))
}
}
pub(super) fn set(limit: u8, config: &mut Config) -> Result<(), RogError> {
if !(20..=100).contains(&limit) {
return Err(RogError::ChargeLimit(limit));
}
let path = Self::get_battery_path()?;
let mut file = OpenOptions::new()
.write(true)
.open(path)
.map_err(|err| RogError::Path(path.into(), err))?;
file.write_all(limit.to_string().as_bytes())
.map_err(|err| RogError::Write(path.into(), err))?;
info!("Battery charge limit: {}", limit);
config.read();
config.bat_charge_limit = limit;
config.write();
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

@@ -0,0 +1,101 @@
use log::{error, warn};
use rog_profiles::{FanCurveProfiles, Profile};
use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
#[derive(Deserialize, Serialize, Debug)]
pub struct ProfileConfig {
#[serde(skip)]
config_path: String,
/// For restore on boot
pub active_profile: Profile,
/// States to restore
pub fan_curves: Option<FanCurveProfiles>,
}
impl ProfileConfig {
fn new(config_path: String) -> Self {
Self {
config_path,
active_profile: Profile::Balanced,
fan_curves: None,
}
}
pub fn set_defaults_and_save(&mut self) {
self.active_profile = Profile::get_active_profile().unwrap_or(Profile::Balanced);
if let Ok(res) = FanCurveProfiles::is_supported() {
if res {
let curves = FanCurveProfiles::default();
self.fan_curves = Some(curves);
}
}
self.write();
}
pub fn load(config_path: String) -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&config_path)
.unwrap_or_else(|_| panic!("The directory /etc/asusd/ is missing")); // okay to cause panic here
let mut buf = String::new();
let mut config;
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
config = Self::new(config_path);
config.set_defaults_and_save();
} else if let Ok(data) = toml::from_str(&buf) {
config = data;
config.config_path = config_path;
} else {
warn!(
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
config_path, config_path
);
let cfg_old = config_path.clone() + "-old";
std::fs::rename(config_path.clone(), cfg_old).unwrap_or_else(|err| {
panic!(
"Could not rename. Please remove {} then restart service: Error {}",
config_path, err
)
});
config = Self::new(config_path);
config.set_defaults_and_save();
}
} else {
config = Self::new(config_path);
config.set_defaults_and_save();
}
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&self.config_path)
.unwrap_or_else(|err| panic!("Error reading {}: {}", self.config_path, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", self.config_path);
} else {
let mut data: ProfileConfig = toml::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", self.config_path));
// copy over serde skipped values
data.config_path = self.config_path.clone();
*self = data;
}
}
}
pub fn write(&self) {
let mut file = File::create(&self.config_path).expect("Couldn't overwrite config");
let data = toml::to_string(self).expect("Parse config to toml failed");
file.write_all(data.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
}

View File

@@ -0,0 +1,157 @@
use std::sync::{Arc, Mutex};
use crate::error::RogError;
use crate::{CtrlTask, GetSupported};
use async_trait::async_trait;
use log::{info, warn};
use rog_profiles::error::ProfileError;
use rog_profiles::{FanCurveProfiles, Profile};
use rog_supported::PlatformProfileFunctions;
use smol::Executor;
use super::config::ProfileConfig;
pub struct CtrlPlatformProfile {
pub config: ProfileConfig,
}
impl GetSupported for CtrlPlatformProfile {
type A = PlatformProfileFunctions;
fn get_supported() -> Self::A {
if !Profile::is_platform_profile_supported() {
warn!(
r#"
platform_profile kernel interface not found, your laptop does not support this, or the interface is missing.
To enable profile support you require a kernel version 5.15.2 minimum.
"#
);
}
let res = FanCurveProfiles::is_supported();
let mut fan_curve_supported = res.is_err();
if let Ok(r) = res {
fan_curve_supported = r;
};
if !fan_curve_supported {
info!(
r#"
fan curves kernel interface not found, your laptop does not support this, or the interface is missing.
To enable fan-curve support you require a kernel with the following patch applied:
https://lkml.org/lkml/2021/10/23/250
This patch has been accepted upstream for 5.17 kernel release.
"#
);
}
PlatformProfileFunctions {
platform_profile: Profile::is_platform_profile_supported(),
fan_curves: fan_curve_supported,
}
}
}
impl crate::Reloadable for CtrlPlatformProfile {
/// Fetch the active profile and use that to set all related components up
fn reload(&mut self) -> Result<(), RogError> {
if let Some(curves) = &mut self.config.fan_curves {
if let Ok(mut device) = FanCurveProfiles::get_device() {
// There is a possibility that the curve was default zeroed, so this call initialises
// the data from system read and we need to save it after
curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?;
self.config.write();
}
}
Ok(())
}
}
impl CtrlPlatformProfile {
pub fn new(config: ProfileConfig) -> Result<Self, RogError> {
if Profile::is_platform_profile_supported() {
info!("Device has profile control available");
if FanCurveProfiles::get_device().is_ok() {
info!("Device has fan curves available");
}
return Ok(CtrlPlatformProfile { config });
}
Err(ProfileError::NotSupported.into())
}
pub fn save_config(&self) {
self.config.write();
}
/// Toggle to next profile in list. This will first read the config, switch, then write out
pub(super) fn set_next_profile(&mut self) -> Result<(), RogError> {
// Read first just incase the user has modified the config before calling this
match self.config.active_profile {
Profile::Balanced => {
Profile::set_profile(Profile::Performance)?;
self.config.active_profile = Profile::Performance;
}
Profile::Performance => {
Profile::set_profile(Profile::Quiet)?;
self.config.active_profile = Profile::Quiet;
}
Profile::Quiet => {
Profile::set_profile(Profile::Balanced)?;
self.config.active_profile = Profile::Balanced;
}
}
self.write_profile_curve_to_platform()?;
Ok(())
}
/// Set the curve for the active profile active
pub(super) fn write_profile_curve_to_platform(&mut self) -> Result<(), RogError> {
if let Some(curves) = &mut self.config.fan_curves {
if let Ok(mut device) = FanCurveProfiles::get_device() {
curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?;
}
}
Ok(())
}
pub(super) fn set_active_curve_to_defaults(&mut self) -> Result<(), RogError> {
if let Some(curves) = self.config.fan_curves.as_mut() {
if let Ok(mut device) = FanCurveProfiles::get_device() {
curves.set_active_curve_to_defaults(self.config.active_profile, &mut device)?;
}
}
Ok(())
}
}
pub struct CtrlProfileTask {
ctrl: Arc<Mutex<CtrlPlatformProfile>>,
}
impl CtrlProfileTask {
pub fn new(ctrl: Arc<Mutex<CtrlPlatformProfile>>) -> Self {
Self { ctrl }
}
}
#[async_trait]
impl CtrlTask for CtrlProfileTask {
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
let ctrl = self.ctrl.clone();
self.repeating_task(666, executor, move || {
if let Ok(ref mut lock) = ctrl.try_lock() {
let new_profile = Profile::get_active_profile().unwrap();
if new_profile != lock.config.active_profile {
lock.config.active_profile = new_profile;
lock.write_profile_curve_to_platform().unwrap();
lock.save_config();
}
}
})
.await;
Ok(())
}
}

View File

@@ -0,0 +1,3 @@
pub mod config;
pub mod controller;
pub mod zbus;

View File

@@ -0,0 +1,187 @@
use async_trait::async_trait;
use log::warn;
use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::fan_curve_set::FanCurveSet;
use rog_profiles::Profile;
use zbus::Connection;
use zbus::SignalContext;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::{dbus_interface, fdo::Error};
use super::controller::CtrlPlatformProfile;
static UNSUPPORTED_MSG: &str =
"Fan curves are not supported on this laptop or you require a patched kernel";
pub struct ProfileZbus {
inner: Arc<Mutex<CtrlPlatformProfile>>,
}
impl ProfileZbus {
pub fn new(inner: Arc<Mutex<CtrlPlatformProfile>>) -> Self {
Self { inner }
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl ProfileZbus {
/// Fetch profile names
fn profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
if let Ok(profiles) = Profile::get_profile_names() {
return Ok(profiles);
}
Err(Error::Failed(
"Failed to get all profile details".to_string(),
))
}
/// Toggle to next platform_profile. Names provided by `Profiles`.
/// If fan-curves are supported will also activate a fan curve for profile.
async fn next_profile(&mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
let mut profile = None;
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.set_next_profile()
.unwrap_or_else(|err| warn!("{}", err));
ctrl.save_config();
profile = Some(ctrl.config.active_profile);
}
if let Some(profile) = profile {
Self::notify_profile(&ctxt, profile).await.ok();
}
}
/// Fetch the active profile name
fn active_profile(&mut self) -> zbus::fdo::Result<Profile> {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read();
return Ok(ctrl.config.active_profile);
}
Err(Error::Failed(
"Failed to get active profile name".to_string(),
))
}
/// Set this platform_profile name as active
async fn set_active_profile(
&self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
profile: Profile,
) {
let mut tmp = None;
if let Ok(mut ctrl) = self.inner.try_lock() {
// Read first just incase the user has modified the config before calling this
ctrl.config.read();
Profile::set_profile(profile)
.map_err(|e| warn!("set_profile, {}", e))
.ok();
ctrl.config.active_profile = profile;
ctrl.write_profile_curve_to_platform()
.map_err(|e| warn!("write_profile_curve_to_platform, {}", e))
.ok();
ctrl.save_config();
tmp = Some(ctrl.config.active_profile);
}
if let Some(profile) = tmp {
Self::notify_profile(&ctxt, profile).await.ok();
}
}
/// Get a list of profiles that have fan-curves enabled.
fn enabled_fan_profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read();
if let Some(curves) = &ctrl.config.fan_curves {
return Ok(curves.get_enabled_curve_profiles().to_vec());
}
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
}
Err(Error::Failed(
"Failed to get enabled fan curve names".to_string(),
))
}
/// Set a profile fan curve enabled status. Will also activate a fan curve if in the
/// same profile mode
fn set_fan_curve_enabled(&mut self, profile: Profile, enabled: bool) -> zbus::fdo::Result<()> {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read();
return if let Some(curves) = &mut ctrl.config.fan_curves {
curves.set_profile_curve_enabled(profile, enabled);
ctrl.write_profile_curve_to_platform()
.map_err(|e| warn!("write_profile_curve_to_platform, {}", e))
.ok();
ctrl.save_config();
Ok(())
} else {
Err(Error::Failed(UNSUPPORTED_MSG.to_string()))
};
}
Err(Error::Failed(
"Failed to get enabled fan curve names".to_string(),
))
}
/// Get the fan-curve data for the currently active Profile
fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result<FanCurveSet> {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read();
if let Some(curves) = &ctrl.config.fan_curves {
let curve = curves.get_fan_curves_for(profile);
return Ok(curve.clone());
}
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
}
Err(Error::Failed("Failed to get fan curve data".to_string()))
}
/// Set the fan curve for the specified profile.
/// Will also activate the fan curve if the user is in the same mode.
fn set_fan_curve(&self, profile: Profile, curve: CurveData) -> zbus::fdo::Result<()> {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read();
if let Some(curves) = &mut ctrl.config.fan_curves {
curves
.save_fan_curve(curve, profile)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
} else {
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
}
ctrl.write_profile_curve_to_platform()
.map_err(|e| warn!("Profile::set_profile, {}", e))
.ok();
ctrl.save_config();
}
Ok(())
}
/// Reset the stored (self) and device curve to the defaults of the platform.
///
/// Each platform_profile has a different default and the defualt can be read
/// only for the currently active profile.
fn set_active_curve_to_defaults(&self) -> zbus::fdo::Result<()> {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read();
ctrl.set_active_curve_to_defaults()
.map_err(|e| warn!("Profile::set_active_curve_to_defaults, {}", e))
.ok();
ctrl.save_config();
}
Ok(())
}
#[dbus_interface(signal)]
async fn notify_profile(signal_ctxt: &SignalContext<'_>, profile: Profile) -> zbus::Result<()> {
}
}
#[async_trait]
impl crate::ZbusAdd for ProfileZbus {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Profile", server).await;
}
}

308
daemon/src/ctrl_rog_bios.rs Normal file
View File

@@ -0,0 +1,308 @@
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;
use std::io::BufRead;
use std::io::{Read, Write};
use std::path::Path;
use std::process::Command;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::Connection;
use zbus::{dbus_interface, SignalContext};
const INITRAMFS_PATH: &str = "/usr/sbin/update-initramfs";
const DRACUT_PATH: &str = "/usr/bin/dracut";
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";
pub struct CtrlRogBios {
_config: Arc<Mutex<Config>>,
}
impl GetSupported for CtrlRogBios {
type A = RogBiosSupportedFunctions;
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(),
}
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlRogBios {
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(&ctxt, dedicated)
.await
.ok();
}
fn dedicated_graphic_mode(&self) -> i8 {
Self::get_gfx_mode()
.map_err(|err| {
warn!("CtrlRogBios: get_gfx_mode {}", err);
err
})
.unwrap_or(-1)
}
#[dbus_interface(signal)]
async fn notify_dedicated_graphic_mode(
signal_ctxt: &SignalContext<'_>,
dedicated: bool,
) -> zbus::Result<()> {
}
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(&ctxt, on).await.ok();
}
fn post_boot_sound(&self) -> i8 {
Self::get_boot_sound()
.map_err(|err| {
warn!("CtrlRogBios: get_boot_sound {}", err);
err
})
.unwrap_or(-1)
}
#[dbus_interface(signal)]
async fn notify_post_boot_sound(ctxt: &SignalContext<'_>, on: 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;
}
}
impl crate::Reloadable for CtrlRogBios {
fn reload(&mut self) -> Result<(), RogError> {
Ok(())
}
}
impl CtrlRogBios {
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
if Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists() {
CtrlRogBios::set_path_mutable(ASUS_SWITCH_GRAPHIC_MODE)?;
} else {
info!("G-Sync Switchable Graphics not detected");
info!("If your laptop is not a G-Sync enabled laptop then you can ignore this. Standard graphics switching will still work.");
}
if Path::new(ASUS_POST_LOGO_SOUND).exists() {
CtrlRogBios::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
} else {
info!("Switch for POST boot sound not detected");
}
Ok(CtrlRogBios { _config: config })
}
fn set_path_mutable(path: &str) -> Result<(), RogError> {
let output = Command::new("/usr/bin/chattr")
.arg("-i")
.arg(path)
.output()
.map_err(|err| RogError::Path(path.into(), err))?;
info!("Set {} writeable: status: {}", path, output.status);
Ok(())
}
pub fn has_dedicated_gfx_toggle() -> bool {
Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists()
}
pub fn get_gfx_mode() -> Result<i8, RogError> {
let path = ASUS_SWITCH_GRAPHIC_MODE;
let mut file = OpenOptions::new()
.read(true)
.open(path)
.map_err(|err| RogError::Path(path.into(), err))?;
let mut data = Vec::new();
file.read_to_end(&mut data)
.map_err(|err| RogError::Read(path.into(), err))?;
let idx = data.len() - 1;
Ok(data[idx] as i8)
}
pub(super) fn set_gfx_mode(&self, dedicated: bool) -> Result<(), RogError> {
let path = ASUS_SWITCH_GRAPHIC_MODE;
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map_err(|err| RogError::Path(path.into(), err))?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
let idx = data.len() - 1;
if dedicated {
data[idx] = 1;
info!("Set system-level graphics mode: Dedicated Nvidia");
} else {
data[idx] = 0;
info!("Set system-level graphics mode: Optimus");
}
file.write_all(&data)
.map_err(|err| RogError::Path(path.into(), err))?;
self.update_initramfs(dedicated)?;
Ok(())
}
pub fn get_boot_sound() -> Result<i8, RogError> {
let path = ASUS_POST_LOGO_SOUND;
let mut file = OpenOptions::new()
.read(true)
.open(path)
.map_err(|err| RogError::Path(path.into(), err))?;
let mut data = Vec::new();
file.read_to_end(&mut data)
.map_err(|err| RogError::Read(path.into(), err))?;
let idx = data.len() - 1;
Ok(data[idx] as i8)
}
pub(super) fn set_boot_sound(on: bool) -> Result<(), RogError> {
let path = ASUS_POST_LOGO_SOUND;
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map_err(|err| RogError::Path(path.into(), err))?;
let mut data = Vec::new();
file.read_to_end(&mut data)
.map_err(|err| RogError::Read(path.into(), err))?;
let idx = data.len() - 1;
if on {
data[idx] = 1;
info!("Set boot POST sound on");
} else {
data[idx] = 0;
info!("Set boot POST sound off");
}
file.write_all(&data)
.map_err(|err| RogError::Path(path.into(), err))?;
Ok(())
}
// required for g-sync mode
fn update_initramfs(&self, dedicated: bool) -> Result<(), RogError> {
let mut initfs_cmd = None;
if Path::new(INITRAMFS_PATH).exists() {
let mut cmd = Command::new("update-initramfs");
cmd.arg("-u");
initfs_cmd = Some(cmd);
info!("Using initramfs update command 'update-initramfs'");
} else if Path::new(DRACUT_PATH).exists() {
let mut cmd = Command::new("dracut");
cmd.arg("-f");
cmd.arg("-q");
initfs_cmd = Some(cmd);
info!("Using initramfs update command 'dracut'");
}
if let Some(mut cmd) = initfs_cmd {
info!("Updating initramfs");
// If switching to Nvidia dedicated we need these modules included
if Path::new(DRACUT_PATH).exists() && dedicated {
cmd.arg("--add-drivers");
cmd.arg("nvidia nvidia-drm nvidia-modeset nvidia-uvm");
info!("System uses dracut, forcing nvidia modules to be included in init");
} else if Path::new(INITRAMFS_PATH).exists() {
let modules = vec![
"nvidia\n",
"nvidia-drm\n",
"nvidia-modeset\n",
"nvidia-uvm\n",
];
let module_include = Path::new("/etc/initramfs-tools/modules");
if dedicated {
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(module_include)
.map_err(|err| {
RogError::Write(module_include.to_string_lossy().to_string(), err)
})?;
// add nvidia modules to module_include
file.write_all(modules.concat().as_bytes())?;
} else {
let file = std::fs::OpenOptions::new()
.read(true)
.open(module_include)
.map_err(|err| {
RogError::Write(module_include.to_string_lossy().to_string(), err)
})?;
let mut buf = Vec::new();
// remove modules
for line in std::io::BufReader::new(file).lines().flatten() {
if !modules.contains(&line.as_str()) {
buf.append(&mut line.as_bytes().to_vec());
}
}
let file = std::fs::OpenOptions::new()
.write(true)
.open(module_include)
.map_err(|err| {
RogError::Write(module_include.to_string_lossy().to_string(), err)
})?;
std::io::BufWriter::new(file).write_all(&buf)?;
}
}
let status = cmd
.status()
.map_err(|err| RogError::Write(format!("{:?}", cmd), err))?;
if !status.success() {
error!("Ram disk update failed");
return Err(RogError::Initramfs("Ram disk update failed".into()));
} else {
info!("Successfully updated initramfs");
}
}
Ok(())
}
}

View File

@@ -0,0 +1,52 @@
use async_trait::async_trait;
use serde_derive::{Deserialize, Serialize};
use zbus::dbus_interface;
use zbus::Connection;
use zvariant::Type;
use crate::{
ctrl_anime::CtrlAnime, ctrl_aura::controller::CtrlKbdLed, ctrl_charge::CtrlCharge,
ctrl_profiles::controller::CtrlPlatformProfile, ctrl_rog_bios::CtrlRogBios, GetSupported,
};
use rog_supported::{
AnimeSupportedFunctions, ChargeSupportedFunctions, LedSupportedFunctions,
PlatformProfileFunctions, RogBiosSupportedFunctions,
};
#[derive(Serialize, Deserialize, Type)]
pub struct SupportedFunctions {
pub anime_ctrl: AnimeSupportedFunctions,
pub charge_ctrl: ChargeSupportedFunctions,
pub platform_profile: PlatformProfileFunctions,
pub keyboard_led: LedSupportedFunctions,
pub rog_bios_ctrl: RogBiosSupportedFunctions,
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl SupportedFunctions {
fn supported_functions(&self) -> &SupportedFunctions {
self
}
}
#[async_trait]
impl crate::ZbusAdd for SupportedFunctions {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, "/org/asuslinux/Supported", server).await;
}
}
impl GetSupported for SupportedFunctions {
type A = SupportedFunctions;
fn get_supported() -> Self::A {
SupportedFunctions {
anime_ctrl: CtrlAnime::get_supported(),
keyboard_led: CtrlKbdLed::get_supported(),
charge_ctrl: CtrlCharge::get_supported(),
platform_profile: CtrlPlatformProfile::get_supported(),
rog_bios_ctrl: CtrlRogBios::get_supported(),
}
}
}

183
daemon/src/daemon.rs Normal file
View File

@@ -0,0 +1,183 @@
use std::env;
use std::error::Error;
use std::io::Write;
use std::sync::{Arc, Mutex};
use ::zbus::Connection;
use daemon::ctrl_profiles::controller::CtrlProfileTask;
use log::LevelFilter;
use log::{error, info, warn};
use smol::Executor;
use daemon::ctrl_anime::config::AnimeConfig;
use daemon::ctrl_anime::zbus::CtrlAnimeZbus;
use daemon::ctrl_anime::*;
use daemon::ctrl_aura::config::AuraConfig;
use daemon::ctrl_aura::controller::{
CtrlKbdLed, CtrlKbdLedReloader, CtrlKbdLedTask, CtrlKbdLedZbus,
};
use daemon::ctrl_charge::CtrlCharge;
use daemon::ctrl_profiles::config::ProfileConfig;
use daemon::ctrl_rog_bios::CtrlRogBios;
use daemon::{
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
};
use daemon::{
ctrl_profiles::{controller::CtrlPlatformProfile, zbus::ProfileZbus},
laptops::LaptopLedData,
};
use daemon::{CtrlTask, Reloadable, ZbusAdd};
use rog_dbus::DBUS_NAME;
use rog_profiles::Profile;
static PROFILE_CONFIG_PATH: &str = "/etc/asusd/profile.conf";
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut logger = env_logger::Builder::new();
logger
.target(env_logger::Target::Stdout)
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
.filter(None, LevelFilter::Info)
.init();
let is_service = match env::var_os("IS_SERVICE") {
Some(val) => val == "1",
None => false,
};
if !is_service {
println!("asusd schould be only run from the right systemd service");
println!(
"do not run in your terminal, if you need an logs please use journalctl -b -u asusd"
);
println!("asusd will now exit");
return Ok(());
}
info!(" daemon v{}", daemon::VERSION);
info!(" rog-anime v{}", rog_anime::VERSION);
info!(" rog-aura v{}", rog_aura::VERSION);
info!(" rog-dbus v{}", rog_dbus::VERSION);
info!(" rog-profiles v{}", rog_profiles::VERSION);
info!("rog-supported v{}", rog_supported::VERSION);
let mut executor = Executor::new();
smol::block_on(start_daemon(&mut executor))?;
Ok(())
}
/// The actual main loop for the daemon
async fn start_daemon(executor: &mut Executor<'_>) -> Result<(), Box<dyn Error>> {
let supported = SupportedFunctions::get_supported();
print_board_info();
println!("{}", serde_json::to_string_pretty(&supported)?);
// Start zbus server
let mut connection = Connection::system().await?;
let config = Config::load();
let config = Arc::new(Mutex::new(config));
supported.add_to_server(&mut connection).await;
match CtrlRogBios::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 connection).await;
}
Err(err) => {
error!("rog_bios_control: {}", err);
}
}
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 connection).await;
let task = CtrlCharge::new(config)?;
task.create_tasks(executor).await.ok();
}
Err(err) => {
error!("charge_control: {}", err);
}
}
if Profile::is_platform_profile_supported() {
let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into());
match CtrlPlatformProfile::new(profile_config) {
Ok(mut ctrl) => {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
let task = CtrlProfileTask::new(tmp.clone());
task.create_tasks(executor).await.ok();
let task = ProfileZbus::new(tmp.clone());
task.add_to_server(&mut connection).await;
}
Err(err) => {
error!("Profile control: {}", err);
}
}
} else {
warn!("platform_profile support not found. This requires kernel 5.15.x or the patch applied: https://lkml.org/lkml/2021/8/18/1022");
}
match CtrlAnime::new(AnimeConfig::load()) {
Ok(ctrl) => {
let inner = Arc::new(Mutex::new(ctrl));
let mut reload = CtrlAnimeReloader(inner.clone());
reload
.reload()
.unwrap_or_else(|err| warn!("AniMe: {}", err));
let zbus = CtrlAnimeZbus(inner.clone());
zbus.add_to_server(&mut connection).await;
let task = CtrlAnimeTask::new(inner).await;
task.create_tasks(executor).await.ok();
}
Err(err) => {
error!("AniMe control: {}", err);
}
}
let laptop = LaptopLedData::get_data();
let aura_config = AuraConfig::load(&laptop);
match CtrlKbdLed::new(laptop, aura_config) {
Ok(ctrl) => {
let inner = Arc::new(Mutex::new(ctrl));
let mut reload = CtrlKbdLedReloader(inner.clone());
reload
.reload()
.unwrap_or_else(|err| warn!("Keyboard LED control: {}", err));
CtrlKbdLedZbus::new(inner.clone())
.add_to_server(&mut connection)
.await;
let task = CtrlKbdLedTask::new(inner);
task.create_tasks(executor).await.ok();
}
Err(err) => {
error!("Keyboard control: {}", err);
}
}
// Request dbus name after finishing initalizing all functions
connection.request_name(DBUS_NAME).await?;
loop {
smol::block_on(executor.tick());
}
}

80
daemon/src/error.rs Normal file
View File

@@ -0,0 +1,80 @@
use rog_profiles::error::ProfileError;
use std::convert::From;
use std::fmt;
#[derive(Debug)]
pub enum RogError {
ParseVendor,
ParseLed,
MissingProfile(String),
Udev(String, std::io::Error),
Path(String, std::io::Error),
Read(String, std::io::Error),
Write(String, std::io::Error),
NotSupported,
NotFound(String),
DoTask(String),
MissingFunction(String),
MissingLedBrightNode(String, std::io::Error),
ReloadFail(String),
Profiles(ProfileError),
Initramfs(String),
Modprobe(String),
Io(std::io::Error),
Zbus(zbus::Error),
ChargeLimit(u8),
}
impl fmt::Display for RogError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RogError::ParseVendor => write!(f, "Parse gfx vendor error"),
RogError::ParseLed => write!(f, "Parse LED error"),
RogError::MissingProfile(profile) => write!(f, "Profile does not exist {}", profile),
RogError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
RogError::Path(path, error) => write!(f, "Path {}: {}", path, error),
RogError::Read(path, error) => write!(f, "Read {}: {}", path, error),
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
RogError::NotSupported => write!(f, "Not supported"),
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
RogError::DoTask(deets) => write!(f, "Task error: {}", deets),
RogError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
RogError::MissingLedBrightNode(path, error) => write!(f, "Led node at {} is missing, please check you have the required patch or dkms module installed: {}", path, error),
RogError::ReloadFail(deets) => write!(f, "Task error: {}", deets),
RogError::Profiles(deets) => write!(f, "Profile error: {}", deets),
RogError::Initramfs(detail) => write!(f, "Initiramfs error: {}", detail),
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),
}
}
}
impl std::error::Error for RogError {}
impl From<ProfileError> for RogError {
fn from(err: ProfileError) -> Self {
RogError::Profiles(err)
}
}
impl From<zbus::Error> for RogError {
fn from(err: zbus::Error) -> Self {
RogError::Zbus(err)
}
}
impl From<std::io::Error> for RogError {
fn from(err: std::io::Error) -> Self {
RogError::Io(err)
}
}
impl From<RogError> for zbus::fdo::Error {
#[inline]
fn from(err: RogError) -> Self {
zbus::fdo::Error::Failed(format!("{}", err))
}
}

103
daemon/src/laptops.rs Normal file
View File

@@ -0,0 +1,103 @@
use log::{info, warn};
use rog_aura::AuraModeNum;
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_KEYBOARD_DEVICES: [&str; 4] = ["1866", "1869", "1854", "19b6"];
pub fn print_board_info() {
let dmi = sysfs_class::DmiId::default();
let board_name = dmi.board_name().expect("Could not get board_name");
let prod_family = dmi.product_family().expect("Could not get product_family");
info!("Product family: {}", prod_family.trim());
info!("Board name: {}", board_name.trim());
}
pub fn print_modes(supported_modes: &[u8]) {
if !supported_modes.is_empty() {
info!("Supported Keyboard LED modes are:");
for mode in supported_modes {
let mode = <&str>::from(&<AuraModeNum>::from(*mode));
info!("- {}", mode);
}
info!(
"If these modes are incorrect you can edit {}",
ASUS_LED_MODE_CONF
);
} else {
info!("No RGB control available");
}
}
#[derive(Debug, Deserialize, Serialize)]
struct LedSupportFile {
led_data: Vec<LaptopLedData>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LaptopLedData {
pub prod_family: String,
pub board_names: Vec<String>,
pub standard: Vec<AuraModeNum>,
pub multizone: bool,
pub per_key: bool,
}
impl LaptopLedData {
pub fn get_data() -> Self {
let dmi = sysfs_class::DmiId::default();
let board_name = dmi.board_name().expect("Could not get board_name");
let prod_family = dmi.product_family().expect("Could not get product_family");
if let Some(modes) = LedSupportFile::load_from_config() {
if let Some(data) = modes.matcher(&prod_family, &board_name) {
return data;
}
}
info!("Using generic LED control for keyboard brightness only");
LaptopLedData {
prod_family,
board_names: vec![board_name],
standard: vec![],
multizone: false,
per_key: false,
}
}
}
impl LedSupportFile {
/// Consumes the LEDModes
fn matcher(self, prod_family: &str, board_name: &str) -> Option<LaptopLedData> {
for config in self.led_data {
if prod_family.contains(&config.prod_family) {
for board in &config.board_names {
if board_name.contains(board) {
info!("Matched to {} {}", config.prod_family, board);
return Some(config);
}
}
}
}
None
}
fn load_from_config() -> Option<Self> {
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)
}));
}
}
}
warn!("Does {} exist?", ASUS_LED_MODE_CONF);
None
}
}

102
daemon/src/lib.rs Normal file
View File

@@ -0,0 +1,102 @@
#![deny(unused_must_use)]
/// Configuration loading, saving
pub mod config;
/// Control of AniMe matrix display
pub mod ctrl_anime;
/// Keyboard LED brightness control, RGB, and LED display modes
pub mod ctrl_aura;
/// Control of battery charge level
pub mod ctrl_charge;
/// Control CPU min/max freq and turbo, fan mode, fan curves
///
/// Intel machines can control:
/// - CPU min/max frequency
/// - CPU turbo enable/disable
/// - Fan mode (normal, boost, silent)
///
/// AMD machines can control:
/// - CPU turbo enable/disable
/// - Fan mode (normal, boost, silent)
/// - Fan min/max RPM curve
pub mod ctrl_profiles;
/// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode
pub mod ctrl_rog_bios;
/// Laptop matching to determine capabilities
pub mod laptops;
/// Fetch all supported functions for the laptop
pub mod ctrl_supported;
pub mod error;
use std::time::Duration;
use crate::error::RogError;
use async_trait::async_trait;
use config::Config;
use log::warn;
use smol::{stream::StreamExt, Executor, Timer};
use zbus::Connection;
use zvariant::ObjectPath;
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
pub trait Reloadable {
fn reload(&mut self) -> Result<(), RogError>;
}
#[async_trait]
pub trait ZbusAdd {
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 {
/// 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 {
type A;
fn do_task(&mut self, config: &mut Config, event: Self::A);
}
pub trait GetSupported {
type A;
fn get_supported() -> Self::A;
}

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