Compare commits

...

206 Commits
3.1.0 ... 4.0.3

Author SHA1 Message Date
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
175 changed files with 13153 additions and 5007 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

@@ -6,6 +6,213 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
# [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

641
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[workspace]
members = ["asusctl", "asus-notify", "daemon", "rog-types", "rog-dbus"]
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.9.0 minimum. It also requires the kernel patch for platform_profile support to be applied form [here](https://lkml.org/lkml/2021/8/18/1022) - this patch is included in the "rog" kernels we build for fedora and arch, and will hit kernel 5.15 upstream.
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/8/29/50) which is still in review as of 29/09/21. As with Profiles, this is included in the kernels we build, and will hit 5.15 kernel upstream.
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.
---

View File

@@ -13,10 +13,9 @@ zshcpl = $(datarootdir)/zsh/site-functions
BIN_C := asusctl
BIN_D := asusd
BIN_U := asusd-user
BIN_N := asus-notify
LEDCFG := asusd-ledmodes.toml
X11CFG := 90-nvidia-screen-G05.conf
PMRULES := 90-asusd-nvidia-pm.rules
SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs')
@@ -42,34 +41,53 @@ distclean:
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/$(PMRULES)" "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)"
$(INSTALL_DATA) "./data/$(BIN_D).rules" "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
$(INSTALL_DATA) "./data/$(LEDCFG)" "$(DESTDIR)/etc/asusd/$(LEDCFG)"
$(INSTALL_DATA) "./data/$(BIN_D).conf" "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
$(INSTALL_DATA) "./data/$(X11CFG)" "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)"
$(INSTALL_DATA) "./data/$(BIN_D).service" "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
$(INSTALL_DATA) "./data/$(BIN_N).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
$(INSTALL_DATA) "./data/$(BIN_U).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_U).service"
$(INSTALL_DATA) "./data/icons/asus_notif_yellow.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
$(INSTALL_DATA) "./data/icons/asus_notif_green.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
$(INSTALL_DATA) "./data/icons/asus_notif_red.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
$(INSTALL_DATA) "./data/icons/scalable/gpu-compute.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-compute.svg"
$(INSTALL_DATA) "./data/icons/scalable/gpu-hybrid.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-hybrid.svg"
$(INSTALL_DATA) "./data/icons/scalable/gpu-integrated.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-integrated.svg"
$(INSTALL_DATA) "./data/icons/scalable/gpu-nvidia.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-nvidia.svg"
$(INSTALL_DATA) "./data/icons/scalable/gpu-vfio.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-vfio.svg"
$(INSTALL_DATA) "./data/icons/scalable/notification-reboot.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/notification-reboot.svg"
$(INSTALL_DATA) "./data/_asusctl" "$(DESTDIR)$(zshcpl)/_asusctl"
$(INSTALL_DATA) "./data/completions/asusctl.fish" "$(DESTDIR)$(datarootdir)/fish/vendor_completions.d/asusctl.fish"
cd rog-anime/data && find "./anime" -type f -exec install -Dm 755 "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
uninstall:
rm -f "$(DESTDIR)$(bindir)/$(BIN_C)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_D)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_N)"
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)"
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
rm -f "$(DESTDIR)/etc/asusd/$(LEDCFG)"
rm -f "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
rm -f "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)"
rm -f "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
rm -r "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-compute.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-hybrid.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-integrated.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-nvidia.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-vfio.svg"
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/notification-reboot.svg"
rm -f "$(DESTDIR)$(zshcpl)/_asusctl"
rm -f "$(DESTDIR)$(datarootdir)/fish/vendor_completions.d/asusctl.fish"
rm -rf "$(DESTDIR)$(datarootdir)/asusd"
update:
cargo update
@@ -80,13 +98,13 @@ 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
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)

249
README.md
View File

@@ -1,37 +1,36 @@
# 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 patches required
This app is developed and tested on fedora only. Support is not provided for Arch or Arch based distros.
1. https://lkml.org/lkml/2021/8/20/232
2. https://lkml.org/lkml/2021/8/18/1022
## Goals
1. To provide an interface for rootless control of some system functions most users wish to control such as fan speeds, keyboard LEDs, graphics modes.
2. Enable third-party apps to use the above with dbus methods
3. To make the above as easy as possible for new users
4. Respect the users resources: be small, light, and fast
Point 3 means that the list of supported distros is very narrow - fedora is explicitly
supported, while Ubuntu and openSUSE are level-2 support. All other distros are *not*
supported (while asusd might still run fine on them). For best support use fedora 32+ Workstation.
Point 4? asusd currently uses a tiny fraction of cpu time, and less than 1Mb of ram, the way
a system-level daemon should.
**NOTICE:**
The following is *not* required for 5.11 kernel versions, as this version includes
all the required patches.
---
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 patch enables the following in kernel:
- All hotkeys (FN+Key combos)
- Control of keyboard brightness using FN+Key combos (not RGB)
- FN+F5 (fan) to toggle fan modes
You will not get RGB control in kernel (yet), and `asusd` + `asusctl` is required
to change modes and RGB settings.
Many other patches for these laptops, AMD and Intel based, are working their way
in to the kernel.
Various patches are required for keyboard support. See [this post](https://asus-linux.org/blog/updates-2021-05-06/) for details on status and which kernels will have which patches.
## Discord
[Discord server link](https://discord.gg/ngbdKabAnP)
[Discord server link](https://discord.gg/4ZKGd7Un5t)
## SUPPORTED LAPTOPS
@@ -52,111 +51,33 @@ 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
- [X] Fan curve control on G14 + G15. Requires kernel patch (should reach 5.15 kernel)
- [X] Toggle bios setting for boot/POST sound
- [X] Toggle bios setting for "dedicated gfx" mode on supported laptops (g-sync)
# FUNCTIONS
## Graphics switching
A new feature has been added to enable switching graphics modes. This can be disabled
in the config with `"manage_gfx": false,`. Additionally there is an extra setting
for laptops capable of g-sync dedicated gfx mode to enable the graphics switching
to switch on dedicated gfx for "nvidia" mode.
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/`.
### 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. This is espeically
true if you manually installed the nvidia drivers.
```
# filename /etc/dracut.conf.d/90-nvidia-dracut-G05.conf
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
# the ramdisk on updates, and to ensure the power-management udev rules run
# on module load
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
```
and run `dracut -f` after creating it.
## KEYBOARD BACKLIGHT MODES
Models GA401, GA502, GU502 support LED brightness change only (no RGB).
If you model isn't getting the correct led modes, you can edit the file
`/etc/asusd/asusd-ledmodes.toml`, the LED Mode numbers are as follows:
```
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.
# Keybinds
To switch to next/previous Aura modes you will need to bind both the aura keys (if available) to one of:
**Next**
```
asusctl led-mode -n
```
**Previous**
```
asusctl led-mode -p
```
To switch Fan/Thermal profiles you need to bind the Fn+F5 key to `asusctl profile -n`.
# BUILDING
Requirements are rust >= 1.40 installed from rustup.io if the distro provided version is too old, and `make`.
Requirements are rust >= 1.47 installed from rustup.io if the distro provided version is too old, and `make`.
**Ubuntu*:** `apt install libclang-dev libudev-dev`
**Ubuntu:** `apt install libclang-dev libudev-dev`
**fedora:** `dnf install clang-devel systemd-devel`
## Installing
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/) for the latest versions of Fedora, Ubuntu, and openSUSE.
Download repositories are available [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/)
### Ubuntu
sudo su -c "echo 'deb https://download.opensuse.org/repositories/home:/luke_nukem:/asus/xUbuntu_21.04/ /' > /etc/apt/sources.list.d/luke_nukem.list"
curl -fsSL https://download.opensuse.org/repositories/home:/luke_nukem:/asus/xUbuntu_21.04/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/home_luke_nukem.gpg > /dev/null
sudo apt-get update
sudo apt-get install asusctl dkms-hid-asus-rog
Alternatively check the releases page for f33 RPM.
---
@@ -173,116 +94,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,115 +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:
```
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Profile org.asuslinux.Daemon.NextProfile
```
## 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

View File

@@ -1,18 +1,22 @@
[package]
name = "asus-notify"
version = "3.0.0"
version = "3.0.2"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
zbus = "^1.9"
# serialisation
serde_json = "^1.0"
rog_dbus = { path = "../rog-dbus" }
daemon = { path = "../daemon" }
rog_aura = { path = "../rog-aura" }
rog_supported = { path = "../rog-supported" }
rog_profiles = { path = "../rog-profiles" }
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", tag = "2.0.0" }
[dependencies.notify-rust]
version = "^4.0"
version = "^4.3"
default-features = false
features = ["z"]
features = ["z"]

View File

@@ -1,95 +1,144 @@
use daemon::config::Profile;
use notify_rust::{Hint, Notification, NotificationHandle};
use rog_aura::AuraEffect;
use rog_dbus::{DbusProxies, Signals};
use rog_profiles::Profile;
use std::error::Error;
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration;
use std::{process, thread};
use supergfxctl::gfx_vendors::{GfxRequiredUserAction, GfxVendors};
use supergfxctl::zbus_proxy::GfxProxy;
use zbus::Connection;
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 = Some(x);
}
};
}
macro_rules! base_notification {
($body:expr) => {
Notification::new()
.summary(NOTIF_HEADER)
.body($body)
.timeout(2000)
.show()
};
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("asus-notify version {}", env!("CARGO_PKG_VERSION"));
println!(" daemon version {}", daemon::VERSION);
println!(" rog-dbus version {}", rog_dbus::VERSION);
// let mut cfg = Config::read_new()?;
// let mut last_profile = String::new();
println!("supergfxctl version {}", supergfxctl::VERSION);
let (proxies, conn) = DbusProxies::new()?;
let signals = Signals::new(&proxies)?;
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 mut last_notification: Option<NotificationHandle> = None;
let recv = proxies.setup_recv(conn);
let mut err_count = 0;
gfx_thread()?;
loop {
std::thread::sleep(Duration::from_millis(100));
recv.next_signal().unwrap();
if let Ok(mut lock) = signals.gfx_vendor.lock() {
if let Some(vendor) = lock.take() {
if let Some(notif) = last_gfx_notif.take() {
notif.close();
}
let x = do_notif(&format!("Graphics mode changed to {}", vendor))?;
last_gfx_notif = Some(x);
sleep(Duration::from_millis(100));
if let Err(err) = recv.next_signal() {
if err_count < 3 {
err_count += 1;
println!("{}", err);
}
if err_count == 3 {
err_count += 1;
println!("Max error count reached. Spooling silently.");
}
sleep(Duration::from_millis(2000));
continue;
}
err_count = 0;
if let Ok(mut lock) = signals.charge.lock() {
if let Some(limit) = lock.take() {
if let Some(notif) = last_chrg_notif.take() {
notif.close();
}
let x = do_notif(&format!("Battery charge limit changed to {}", limit))?;
last_chrg_notif = Some(x);
}
if let Ok(data) = signals.led_mode.try_recv() {
notify!(do_led_notif, last_notification, &data);
}
if let Ok(mut lock) = signals.profile.lock() {
if let Some(profile) = lock.take() {
if let Some(notif) = last_profile_notif.take() {
notif.close();
}
if let Ok(profile) = serde_json::from_str(&profile) {
let profile: Profile = profile;
if let Ok(name) = proxies.profile().active_profile_name() {
let x = do_thermal_notif(&profile, &name)?;
last_profile_notif = Some(x);
}
}
}
if let Ok(data) = signals.profile.try_recv() {
notify!(do_thermal_notif, last_notification, &data);
}
if let Ok(mut lock) = signals.led_mode.lock() {
if let Some(ledmode) = lock.take() {
if let Some(notif) = last_led_notif.take() {
notif.close();
}
let x = do_notif(&format!(
"Keyboard LED mode changed to {}",
<&str>::from(&ledmode)
))?;
last_led_notif = Some(x);
}
if let Ok(data) = signals.charge.try_recv() {
notify!(do_charge_notif, last_notification, &data);
}
}
}
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 gfx_thread() -> Result<(), Box<dyn std::error::Error>> {
let mut last_notification: Option<NotificationHandle> = None;
let conn = Connection::new_system()?;
let proxy = GfxProxy::new(&conn)?;
let (tx1, rx1) = channel();
proxy.connect_notify_gfx(tx1)?;
let (tx2, rx2) = channel();
proxy.connect_notify_action(tx2)?;
thread::spawn(move || loop {
if proxy
.next_signal()
.map_err(|e| println!("Error: {}", e))
.is_err()
{
break;
}
if let Ok(data) = rx1.try_recv() {
notify!(do_gfx_notif, last_notification, &data);
}
if let Ok(data) = rx2.try_recv() {
match data {
GfxRequiredUserAction::Logout | GfxRequiredUserAction::Reboot => {
do_gfx_action_notif(&data)
.map_err(|e| {
println!("Error: {}", e);
})
.ok();
}
GfxRequiredUserAction::Integrated => {
base_notification!(
"You must be in integrated mode first to switch to the requested mode"
)
.map_err(|e| {
println!("Error: {}", e);
})
.ok();
}
GfxRequiredUserAction::None => {}
}
}
});
Ok(())
}
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
let icon = match profile {
Profile::Balanced => "asus_notif_yellow",
Profile::Performance => "asus_notif_red",
Profile::Quiet => "asus_notif_green",
};
let profile: &str = (*profile).into();
let x = Notification::new()
.summary("ASUS ROG")
.body(&format!(
"Thermal profile changed to {}, turbo {}",
label.to_uppercase(),
turbo
"Thermal profile changed to {}",
profile.to_uppercase(),
))
.hint(Hint::Resident(true))
.timeout(2000)
@@ -100,11 +149,62 @@ 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_gfx_notif(vendor: &GfxVendors) -> Result<NotificationHandle, notify_rust::error::Error> {
let icon = match vendor {
GfxVendors::Nvidia => "/usr/share/icons/hicolor/scalable/status/gpu-nvidia.svg",
GfxVendors::Integrated => "/usr/share/icons/hicolor/scalable/status/gpu-integrated.svg",
GfxVendors::Compute => "/usr/share/icons/hicolor/scalable/status/gpu-compute.svg",
GfxVendors::Vfio => "/usr/share/icons/hicolor/scalable/status/gpu-vfio.svg",
GfxVendors::Hybrid => "/usr/share/icons/hicolor/scalable/status/gpu-hybrid.svg",
};
Notification::new()
.summary(NOTIF_HEADER)
.body(&format!(
"Graphics mode changed to {}",
<&str>::from(vendor)
))
.timeout(2000)
.icon(icon)
.show()
}
fn do_gfx_action_notif(vendor: &GfxRequiredUserAction) -> Result<(), notify_rust::error::Error> {
let mut notif = Notification::new()
.summary(NOTIF_HEADER)
.timeout(2000)
.urgency(notify_rust::Urgency::Critical)
.icon("/usr/share/icons/hicolor/scalable/status/notification-reboot.svg")
.finalize();
if matches!(vendor, GfxRequiredUserAction::Logout) {
notif.action("logout", "Logout now?");
} else if matches!(vendor, GfxRequiredUserAction::Reboot) {
notif.action("reboot", "Reboot now?");
}
notif.body("Graphics mode changed");
notif.show()?.wait_for_action(|action| match action {
"logout" => {
process::Command::new("gnome-session-quit").spawn().ok();
}
"reboot" => {
process::Command::new("systemctl")
.arg("reboot")
.spawn()
.ok();
}
_ => (),
});
Ok(())
}

View File

@@ -1,20 +1,27 @@
[package]
name = "asusctl"
version = "3.1.0"
version = "4.0.3"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# serialisation
serde_json = "^1.0"
zbus = "^1.9.1"
rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_dbus = { path = "../rog-dbus" }
rog_types = { path = "../rog-types" }
rog_profiles = { path = "../rog-profiles" }
rog_supported = { path = "../rog-supported" }
daemon = { path = "../daemon" }
gumdrop = "^0.8"
yansi-term = "^0.1"
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", tag = "2.0.0" }
toml = "^0.5.8"
sysfs-class = "^0.1.2"
[dev-dependencies]
tinybmp = "^0.2.3"
rog_dbus = { path = "../rog-dbus" }
glam = "0.14.0"
rog_dbus = { path = "../rog-dbus" }
gif = "^0.11.2"

View File

@@ -1,42 +0,0 @@
use rog_dbus::AuraDbusClient;
use rog_types::anime_matrix::{AniMeImageBuffer, AniMePacketType, HEIGHT, WIDTH};
use tinybmp::{Bmp, Pixel};
fn main() {
let (client, _) = AuraDbusClient::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 = AniMeImageBuffer::new();
// Aligned left
for (i, px) in pixels.iter().enumerate() {
if (px.x as usize / 2) < WIDTH && (px.y as usize) < HEIGHT && px.x % 2 == 0 {
let mut c = px.color as u32;
matrix.get_mut()[px.y as usize][px.x as usize / 2] = c 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());
//client.proxies().anime().set_brightness(&mut matrix).unwrap();
}

View File

@@ -0,0 +1,26 @@
use std::{env, error::Error, path::Path, process::exit};
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClient::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::RogDbusClient;
// In usable data:
// Top row start at 1, ends at 32
// 74w x 36h diagonal used by the windows app
fn main() {
let (client, _) = RogDbusClient::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::RogDbusClient;
fn main() {
let (client, _) = RogDbusClient::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::RogDbusClient;
// In usable data:
// Top row start at 1, ends at 32
// 74w x 36h diagonal used by the windows app
fn main() {
let (client, _) = RogDbusClient::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::RogDbusClient;
// In usable data:
// Top row start at 1, ends at 32
fn main() {
let (client, _) = RogDbusClient::new().unwrap();
let mut matrix = AnimeDataBuffer::new();
matrix.get_mut()[1] = 100; // start = 1
for n in matrix.get_mut()[2..32].iter_mut() {
*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::RogDbusClient;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClient::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::RogDbusClient;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = RogDbusClient::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,5 +1,5 @@
use rog_dbus::AuraDbusClient;
use rog_types::fancy::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
use std::collections::LinkedList;
#[derive(Debug, Clone)]
@@ -52,7 +52,7 @@ impl Ball {
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut colours = KeyColourArray::new();

View File

@@ -1,8 +1,8 @@
use rog_dbus::AuraDbusClient;
use rog_types::fancy::{GX502Layout, KeyColourArray, KeyLayout};
use rog_aura::{GX502Layout, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let layout = GX502Layout::default();

View File

@@ -1,8 +1,8 @@
use rog_dbus::AuraDbusClient;
use rog_types::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 (dbus, _) = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut key_colours = KeyColourArray::new();
let layout = GX502Layout::default();

View File

@@ -1,8 +1,8 @@
use rog_dbus::AuraDbusClient;
use rog_types::fancy::{Key, KeyColourArray};
use rog_aura::{Key, KeyColourArray};
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut key_colours = KeyColourArray::new();
@@ -10,7 +10,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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;
@@ -19,7 +19,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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;

View File

@@ -1,8 +1,8 @@
use rog_dbus::AuraDbusClient;
use rog_types::fancy::{GX502Layout, KeyColourArray, KeyLayout};
use rog_aura::{GX502Layout, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut key_colours = KeyColourArray::new();
let layout = GX502Layout::default();

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
asusctl/examples/nudoom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

BIN
asusctl/examples/rust.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 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

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

@@ -0,0 +1,92 @@
use gumdrop::Options;
use rog_aura::error::Error;
use std::str::FromStr;
#[derive(Copy, Clone, Debug)]
pub enum AnimeStatusValue {
On,
Off,
}
impl FromStr for AnimeStatusValue {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"on" => Ok(AnimeStatusValue::On),
"off" => Ok(AnimeStatusValue::Off),
_ => {
print!("Invalid argument, must be one of: on, off");
Err(Error::ParseAnime)
}
}
}
}
impl From<AnimeStatusValue> for bool {
fn from(value: AnimeStatusValue) -> Self {
match value {
AnimeStatusValue::On => true,
AnimeStatusValue::Off => false,
}
}
}
#[derive(Options)]
pub struct AnimeLeds {
#[options(help = "print help message")]
help: bool,
#[options(
no_long,
required,
short = "b",
meta = "",
help = "set all leds brightness value"
)]
led_brightness: u8,
}
impl AnimeLeds {
pub fn led_brightness(&self) -> u8 {
self.led_brightness
}
}
#[derive(Options)]
pub struct AnimeCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(
meta = "",
help = "turn on/off the panel (accept/reject write requests)"
)]
pub turn: Option<AnimeStatusValue>,
#[options(meta = "", help = "turn on/off the panel at boot (with Asus effect)")]
pub boot: Option<AnimeStatusValue>,
#[options(command)]
pub command: Option<AnimeActions>,
}
#[derive(Options)]
pub enum AnimeActions {
#[options(help = "change all leds brightness")]
Leds(AnimeLeds),
#[options(help = "display an image png")]
Image(AnimeImage),
}
#[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,
}

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

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

@@ -0,0 +1,104 @@
use crate::{
anime_cli::AnimeCommand,
aura_cli::{LedBrightness, SetAuraBuiltin},
profiles_cli::{FanCurveCommand, ProfileCommand},
};
use gumdrop::Options;
use supergfxctl::gfx_vendors::GfxVendors;
#[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 = "<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 suported")]
FanCurve(FanCurveCommand),
#[options(help = "Set the graphics mode")]
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 awake_enable: Option<bool>,
#[options(
meta = "",
help = "set the keyboard LED suspend animation to enabled while the device is suspended"
)]
pub sleep_enable: Option<bool>,
#[options(command)]
pub command: Option<SetAuraBuiltin>,
}
#[derive(Options)]
pub struct GraphicsCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(
meta = "",
help = "Set graphics mode: <nvidia, hybrid, compute, integrated>"
)]
pub mode: Option<GfxVendors>,
#[options(help = "Get the current mode")]
pub get: bool,
#[options(help = "Get the current power status")]
pub pow: bool,
#[options(help = "Do not ask for confirmation")]
pub force: bool,
}
#[derive(Options, Debug)]
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,
}

View File

@@ -1,120 +1,43 @@
mod anime_cli;
mod aura_cli;
mod cli_opts;
mod profiles_cli;
use crate::aura_cli::{LedBrightness, SetAuraBuiltin};
use crate::cli_opts::*;
use anime_cli::{AnimeActions, AnimeCommand};
use gumdrop::{Opt, Options};
use rog_dbus::AuraDbusClient;
use rog_types::{
anime_matrix::{AniMeDataBuffer, FULL_PANE_LEN},
cli_options::{AniMeActions, AniMeStatusValue, LedBrightness, SetAuraBuiltin},
gfx_vendors::GfxVendors,
profile::{FanLevel, ProfileCommand, ProfileEvent},
use profiles_cli::{FanCurveCommand, ProfileCommand};
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2, ANIME_DATA_LEN};
use rog_aura::{self, AuraEffect};
use rog_dbus::RogDbusClient;
use rog_supported::SupportedFunctions;
use rog_supported::{
AnimeSupportedFunctions, LedSupportedFunctions, PlatformProfileFunctions,
RogBiosSupportedFunctions,
};
use std::env::args;
use yansi_term::Colour::Green;
use yansi_term::Colour::Red;
use std::process::Command;
use std::{env::args, path::Path, sync::mpsc::channel};
use supergfxctl::{
gfx_vendors::GfxRequiredUserAction,
special::{get_asus_gsync_gfx_mode, has_asus_gsync_gfx_mode},
zbus_proxy::GfxProxy,
};
use zbus::Connection;
#[derive(Default, Options)]
struct CLIStart {
#[options(help_flag, help = "print help message")]
help: bool,
#[options(help = "show program version number")]
version: bool,
#[options(help = "show supported functions of this laptop")]
show_supported: bool,
#[options(meta = "", help = "<off, low, med, high>")]
kbd_bright: Option<LedBrightness>,
#[options(
meta = "",
help = "<silent, normal, boost>, set fan mode independent of profile"
)]
fan_mode: Option<FanLevel>,
#[options(meta = "", 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),
#[options(name = "anime", help = "Manage AniMe Matrix")]
AniMe(AniMeCommand),
#[options(help = "Change bios settings")]
Bios(BiosCommand),
}
#[derive(Options)]
struct LedModeCommand {
#[options(help = "print help message")]
help: bool,
#[options(help = "switch to next aura mode")]
next_mode: bool,
#[options(help = "switch to previous aura mode")]
prev_mode: bool,
#[options(command)]
command: Option<SetAuraBuiltin>,
}
#[derive(Options)]
struct GraphicsCommand {
#[options(help = "print help message")]
help: bool,
#[options(
meta = "",
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,
}
#[derive(Options)]
struct AniMeCommand {
#[options(help = "print help message")]
help: bool,
#[options(
meta = "",
help = "turn on/off the panel (accept/reject write requests)"
)]
turn: Option<AniMeStatusValue>,
#[options(meta = "", help = "turn on/off the panel at boot (with Asus effect)")]
boot: Option<AniMeStatusValue>,
#[options(command)]
command: Option<AniMeActions>,
}
#[derive(Options, Debug)]
struct BiosCommand {
#[options(help = "print help message")]
help: bool,
#[options(meta = "", no_long, help = "toggle bios POST sound")]
post_sound_set: Option<bool>,
#[options(no_long, help = "read bios POST sound")]
post_sound_get: bool,
#[options(meta = "", no_long, help = "toggle GPU to/from dedicated mode")]
dedicated_gfx_set: Option<bool>,
#[options(no_long, help = "get GPU mode")]
dedicated_gfx_get: bool,
}
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 mut args: Vec<String> = args().collect();
args.remove(0);
let args: Vec<String> = args().skip(1).collect();
let parsed: CLIStart;
let parsed: CliStart;
let missing_argument_k = gumdrop::Error::missing_argument(Opt::Short('k'));
match CLIStart::parse_args_default(&args) {
match CliStart::parse_args_default(&args) {
Ok(p) => {
parsed = p;
}
Err(err) if err.to_string() == missing_argument_k.to_string() => {
parsed = CLIStart {
parsed = CliStart {
kbd_bright: Some(LedBrightness::new(None)),
..Default::default()
};
@@ -125,209 +48,521 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
if parsed.help_requested() {
// As help option don't work with `parse_args_default`
// we will call `parse_args_default_or_exit` instead
CLIStart::parse_args_default_or_exit();
}
let (dbus, _) = RogDbusClient::new()
.map_err(|e| {
print_error_help(Box::new(e), None);
std::process::exit(3);
})
.unwrap();
let supported = dbus
.proxies()
.supported()
.get_supported_functions()
.map_err(|e| {
print_error_help(Box::new(e), None);
std::process::exit(4);
})
.unwrap();
if parsed.version {
println!(" asusctl version {}", env!("CARGO_PKG_VERSION"));
println!(" rog-dbus version {}", rog_dbus::VERSION);
println!("rog-types version {}", rog_types::VERSION);
print_versions();
println!();
print_laptop_info();
return Ok(());
}
let (dbus, _) = AuraDbusClient::new()?;
if let Err(err) = do_parsed(&parsed, &supported, &dbus) {
print_error_help(err, Some(&supported));
}
match parsed.command {
Some(CliCommand::LedMode(mode)) => {
if (mode.command.is_none() && !mode.prev_mode && !mode.next_mode) || mode.help {
println!("Missing arg or command\n\n{}", mode.self_usage());
if let Some(lst) = mode.self_command_list() {
println!("\n{}", lst);
}
println!("\nHelp can also be requested on modes, e.g: static --help");
}
if mode.next_mode && mode.prev_mode {
println!("Please specify either next or previous")
}
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(command) = mode.command {
dbus.proxies().led().set_led_mode(&command.into())?
}
}
Some(CliCommand::Profile(cmd)) => {
if (!cmd.next
&& !cmd.create
&& cmd.curve.is_none()
&& cmd.max_percentage.is_none()
&& cmd.min_percentage.is_none()
&& cmd.preset.is_none()
&& cmd.profile.is_none()
&& cmd.turbo.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 cmd.next {
dbus.proxies().profile().next_fan()?;
} else {
dbus.proxies()
.profile()
.write_command(&ProfileEvent::Cli(cmd))?
}
}
Some(CliCommand::Graphics(cmd)) => do_gfx(cmd, &dbus)?,
Some(CliCommand::AniMe(cmd)) => {
if (cmd.command.is_none() && cmd.boot.is_none() && cmd.turn.is_none()) || cmd.help {
println!("Missing arg or command\n\n{}", cmd.self_usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
}
if let Some(anime_turn) = cmd.turn {
dbus.proxies().anime().toggle_on(anime_turn.into())?
}
if let Some(anime_boot) = cmd.boot {
dbus.proxies().anime().toggle_boot_on(anime_boot.into())?
}
if let Some(action) = cmd.command {
match action {
AniMeActions::Leds(anime_leds) => {
let mut data = AniMeDataBuffer::new();
data.set([anime_leds.led_brightness(); FULL_PANE_LEN]);
dbus.proxies().anime().write_direct(data)?;
}
}
}
}
Some(CliCommand::Bios(cmd)) => {
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\n{}", cmd.self_usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
}
Ok(())
}
if let Some(opt) = cmd.post_sound_set {
dbus.proxies().rog_bios().set_post_sound(opt)?;
}
if cmd.post_sound_get {
let res = if dbus.proxies().rog_bios().get_post_sound()? == 1 {
true
} else {
false
};
println!("Bios POST sound on: {}", res);
}
if let Some(opt) = cmd.dedicated_gfx_set {
dbus.proxies().rog_bios().set_dedicated_gfx(opt)?;
}
if cmd.dedicated_gfx_get {
let res = if dbus.proxies().rog_bios().get_dedicated_gfx()? == 1 {
true
} else {
false
};
println!("Bios dedicated GPU on: {}", res);
println!("You must reboot your system to activate dedicated Nvidia mode");
}
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);
println!(" supergfxctl v{}", supergfxctl::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: &RogDbusClient,
) -> 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(cmd)) => do_gfx(cmd).map_err(|err| {
do_gfx_diagnose();
err
})?,
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.fan_mode.is_none()
&& parsed.chg_limit.is_none())
&& parsed.chg_limit.is_none()
&& !parsed.next_kbd_bright
&& !parsed.prev_kbd_bright)
|| parsed.help
{
println!("{}", CLIStart::usage());
println!("{}", CliStart::usage());
println!();
println!("{}", CLIStart::command_list().unwrap());
if let Some(cmdlist) = CliStart::command_list() {
println!("{}", cmdlist);
}
}
}
}
if let Some(brightness) = parsed.kbd_bright {
if let Some(brightness) = &parsed.kbd_bright {
match brightness.level() {
None => {
let level = dbus.proxies().led().get_led_brightness()?;
println!("Current keyboard led brightness: {}", level.to_string());
}
Some(level) => dbus.proxies().led().set_brightness(level)?,
Some(level) => dbus
.proxies()
.led()
.set_led_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 {
let dat = dbus.proxies().supported().get_supported_functions()?;
println!("Supported laptop functions:\n{}", dat.to_string());
println!("Supported laptop functions:\n\n{}", supported);
}
if let Some(fan_level) = parsed.fan_mode {
dbus.proxies().profile().write_fan_mode(fan_level.into())?;
}
if let Some(chg_limit) = parsed.chg_limit {
dbus.proxies().charge().write_limit(chg_limit)?;
}
Ok(())
}
fn do_gfx(
command: GraphicsCommand,
dbus_client: &AuraDbusClient,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(mode) = command.mode {
println!("Changing graphics modes...");
println!("If this takes longer than 30s, ctrl+c then check `journalctl -b -u asusd`");
fn do_gfx_diagnose() {
println!("\nGraphics mode change error.");
do_diagnose("supergfxd");
println!();
}
if do_gfx_action(
command.force,
"This will restart your display-manager. Please save all work!",
"Setting graphics mode...",
) {
dbus_client.proxies().gfx().gfx_write_mode(mode.into())?;
let res = dbus_client.gfx_wait_changed()?;
println!("{}", res);
std::process::exit(1)
}
std::process::exit(-1)
fn do_gfx(command: &GraphicsCommand) -> Result<(), Box<dyn std::error::Error>> {
if command.mode.is_none() && !command.get && !command.pow && !command.force || command.help {
println!("{}", command.self_usage());
}
let conn = Connection::new_system()?;
let proxy = GfxProxy::new(&conn)?;
let (tx, rx) = channel();
proxy.connect_notify_action(tx)?;
if let Some(mode) = command.mode {
if has_asus_gsync_gfx_mode() && get_asus_gsync_gfx_mode()? == 1 {
println!("You can not change modes until you turn dedicated/G-Sync off and reboot");
std::process::exit(-1);
}
println!(
"If anything fails check `journalctl -b -u asusd` and `journalctl -b -u supergfxd`\n"
);
proxy.gfx_write_mode(&mode)?;
loop {
proxy.next_signal()?;
if let Ok(res) = rx.try_recv() {
match res {
GfxRequiredUserAction::Integrated => {
println!(
"You must change to Integrated before you can change to {}",
<&str>::from(mode)
);
}
GfxRequiredUserAction::Logout | GfxRequiredUserAction::Reboot => {
println!(
"Graphics mode changed to {}. User action required is: {}",
<&str>::from(mode),
<&str>::from(&res)
);
}
GfxRequiredUserAction::None => {
println!("Graphics mode changed to {}", <&str>::from(mode));
}
}
}
std::process::exit(0)
}
}
if command.get {
let res = dbus_client.proxies().gfx().gfx_get_mode()?;
println!("Current graphics mode: {}", res);
let res = proxy.gfx_get_mode()?;
println!("Current graphics mode: {}", <&str>::from(res));
}
if command.pow {
let res = dbus_client.proxies().gfx().gfx_get_pwr()?;
if res.contains("active") {
println!("Current power status: {}", Red.paint(&format!("{}", res)));
} else {
println!("Current power status: {}", Green.paint(&format!("{}", res)));
let res = proxy.gfx_get_pwr()?;
println!("Current power status: {}", <&str>::from(&res));
}
Ok(())
}
fn handle_anime(
dbus: &RogDbusClient,
_supported: &AnimeSupportedFunctions,
cmd: &AnimeCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if (cmd.command.is_none() && cmd.boot.is_none() && cmd.turn.is_none()) || cmd.help {
println!("Missing arg or command\n\n{}", cmd.self_usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
}
if let Some(anime_turn) = cmd.turn {
dbus.proxies().anime().set_led_power(anime_turn.into())?
}
if let Some(anime_boot) = cmd.boot {
dbus.proxies()
.anime()
.set_system_animations(anime_boot.into())?
}
if let Some(action) = cmd.command.as_ref() {
match action {
AnimeActions::Leds(anime_leds) => {
let data = AnimeDataBuffer::from_vec(
[anime_leds.led_brightness(); ANIME_DATA_LEN].to_vec(),
);
dbus.proxies().anime().write(data)?;
}
AnimeActions::Image(image) => {
if image.help_requested() || image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage());
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))?;
}
}
}
Ok(())
}
fn do_gfx_action(no_confirm: bool, ask_msg: &str, ok_msg: &str) -> bool {
println!("{}", Red.paint(&format!("{} Continue?", ask_msg)));
fn handle_led_mode(
dbus: &RogDbusClient,
supported: &LedSupportedFunctions,
mode: &LedModeCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if mode.command.is_none()
&& !mode.prev_mode
&& !mode.next_mode
&& mode.sleep_enable.is_none()
&& mode.awake_enable.is_none()
{
if !mode.help {
println!("Missing arg or command\n");
}
println!("{}\n", mode.self_usage());
println!("Commands available");
let mut buf = String::new();
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.contains(&<&str>::from(mode).to_lowercase()) {
return true;
}
}
if supported.multizone_led_mode {
return true;
}
false
}) {
println!("{}", command);
}
}
std::io::stdin().read_line(&mut buf).expect("Input failed");
let input = buf.chars().next().unwrap() as char;
println!("\nHelp can also be requested on modes, e.g: static --help");
return Ok(());
}
if input == 'Y' || input == 'y' || no_confirm {
println!("{}", Green.paint(&format!("{}", ok_msg)));
return true;
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.awake_enable {
dbus.proxies().led().set_awake_enabled(enable)?;
}
if let Some(enable) = mode.sleep_enable {
dbus.proxies().led().set_sleep_enabled(enable)?;
}
Ok(())
}
fn handle_profile(
dbus: &RogDbusClient,
_supported: &PlatformProfileFunctions,
cmd: &ProfileCommand,
) -> Result<(), Box<dyn std::error::Error>> {
println!("Warning: Profiles now depend on power-profiles-daemon v0.9+");
println!("Warning: Fan-curve support is coming in a 4.1.x release");
if !cmd.next && !cmd.list {
if !cmd.help {
println!("Missing arg or command\n");
}
println!("{}", ProfileCommand::usage());
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
// println!("Note: turbo, frequency, fan preset and fan curve options will apply to");
// println!(" to the currently active profile unless a profile name is specified");
std::process::exit(1);
}
if cmd.next {
dbus.proxies().profile().next_profile()?;
}
if cmd.list {
let res = dbus.proxies().profile().profiles()?;
res.iter().for_each(|p| println!("{:?}", p));
}
Ok(())
}
fn handle_fan_curve(
dbus: &RogDbusClient,
_supported: &PlatformProfileFunctions,
cmd: &FanCurveCommand,
) -> Result<(), Box<dyn std::error::Error>> {
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(curve, profile)?;
}
}
Ok(())
}
fn handle_bios_option(
dbus: &RogDbusClient,
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_sound(opt)?;
}
if cmd.post_sound_get {
let res = dbus.proxies().rog_bios().get_post_sound()? == 1;
println!("Bios POST sound on: {}", res);
}
if let Some(opt) = cmd.dedicated_gfx_set {
println!("Rebuilding initrd to include drivers");
dbus.proxies().rog_bios().set_dedicated_gfx(opt)?;
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().get_dedicated_gfx()? == 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,50 @@
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(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"
)]
pub data: Option<CurveData>,
}

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

@@ -0,0 +1,30 @@
[package]
name = "daemon-user"
version = "1.2.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 = "3.0.1"
zbus = "^1.9.1"
zvariant = "^2.6"
zvariant_derive = "^2.6"

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,366 @@
use rog_anime::error::AnimeError;
use rog_anime::{ActionData, ActionLoader, AnimTime, Fade, Sequences, Vec2};
use rog_dbus::RogDbusClient;
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: RogDbusClient<'a>,
do_early_return: Arc<AtomicBool>,
}
impl<'a> CtrlAnimeInner<'static> {
pub fn new(
sequences: Sequences,
client: RogDbusClient<'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, self.do_early_return.clone(), &|output| {
self.client
.proxies()
.anime()
.write(output)
.map_err(|e| AnimeError::Dbus(format!("{}", e)))
})?;
}
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: RogDbusClient<'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: RogDbusClient<'static>,
inner_early_return: Arc<AtomicBool>,
) -> Result<Self, Error> {
Ok(CtrlAnime {
config,
client,
inner,
inner_early_return,
})
}
pub fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.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_led_power(on)?;
// Let the inner loop run
self.inner_early_return.store(false, Ordering::SeqCst);
} else {
// Must make the inner run loop return early
self.inner_early_return.store(true, Ordering::SeqCst);
self.client.proxies().anime().set_led_power(on)?;
}
Ok(())
}
}

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

@@ -0,0 +1,67 @@
use rog_dbus::RogDbusClient;
use rog_user::{
ctrl_anime::{CtrlAnime, CtrlAnimeInner},
user_config::*,
DBUS_NAME,
};
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use zbus::{fdo, 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, _) = RogDbusClient::new()?;
let supported = client.proxies().supported().get_supported_functions()?;
let mut config = UserConfig::new();
config.load_config()?;
let anime_config = UserAnimeConfig::load_config(config.active_anime)?;
let anime = anime_config.create_anime()?;
let anime_config = Arc::new(Mutex::new(anime_config));
// Create server
let connection = Connection::new_session()?;
fdo::DBusProxy::new(&connection)?
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
let mut server = zbus::ObjectServer::new(&connection);
// Set up the anime data and run loop/thread
if supported.anime_ctrl.0 {
let early_return = Arc::new(AtomicBool::new(false));
// Inner behind mutex required for thread safety
let inner = Arc::new(Mutex::new(CtrlAnimeInner::new(
anime,
client,
early_return.clone(),
)?));
// Need new client object for dbus control part
let (client, _) = RogDbusClient::new()?;
let anime_control = CtrlAnime::new(anime_config, inner.clone(), client, early_return)?;
anime_control.add_to_server(&mut server);
// Thread using inner
let _anime_thread = thread::Builder::new()
.name("Anime User".into())
.spawn(move || loop {
if let Ok(inner) = inner.try_lock() {
inner.run().ok();
}
})?;
}
if supported.keyboard_led.per_key_led_mode {}
loop {
if let Err(err) = server.try_handle_next() {
println!("{}", err);
}
}
}

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 = "daemon"
version = "3.1.0"
version = "4.0.3"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -18,26 +18,28 @@ name = "asusd"
path = "src/daemon.rs"
[dependencies]
rog_types = { path = "../rog-types" }
rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_supported = { path = "../rog-supported" }
rog_profiles = { path = "../rog-profiles" }
rog_dbus = { path = "../rog-dbus" }
rusb = "^0.7"
rusb = "^0.8"
udev = "^0.6"
# cli and logging
log = "^0.4"
env_logger = "^0.8"
zbus = "^1.8"
zvariant = "^2.4"
zbus = "^1.9.1"
zvariant = "^2.6"
zvariant_derive = { version = "^2.6" }
logind-zbus = "^0.7.1"
# serialisation
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
toml = "^0.5"
toml = "^0.5.8"
# Device control
sysfs-class = "^0.1.2" # used for backlight control and baord ID
rog_fan_curve = { version = "0.1", features = ["serde"] }
# cpu power management
intel-pstate = "^0.2"

View File

@@ -1,107 +1,56 @@
use log::{error, info, warn};
use rog_fan_curve::Curve;
use rog_types::{aura_modes::AuraModes, gfx_vendors::GfxVendors};
use log::{error, warn};
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use crate::config_old::*;
use crate::VERSION;
pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
#[derive(Deserialize, Serialize)]
pub struct Config {
pub gfx_mode: GfxVendors,
pub gfx_managed: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
#[serde(skip)]
pub curr_fan_mode: u8,
/// Save charge limit for restoring on boot
pub bat_charge_limit: u8,
pub kbd_led_brightness: u8,
pub kbd_backlight_mode: u8,
pub kbd_backlight_modes: Vec<AuraModes>,
pub power_profiles: BTreeMap<String, Profile>,
}
impl Default for Config {
fn default() -> Self {
let mut pwr = BTreeMap::new();
pwr.insert("normal".into(), Profile::new(0, 100, true, 0, None));
pwr.insert("boost".into(), Profile::new(0, 100, true, 1, None));
pwr.insert("silent".into(), Profile::new(0, 100, true, 2, None));
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_managed: true,
active_profile: "normal".into(),
toggle_profiles: vec!["normal".into(), "boost".into(), "silent".into()],
curr_fan_mode: 0,
bat_charge_limit: 100,
kbd_led_brightness: 1,
kbd_backlight_mode: 0,
kbd_backlight_modes: Vec::new(),
power_profiles: pwr,
}
}
}
impl Config {
fn new() -> Self {
Config {
bat_charge_limit: 100,
}
}
/// `load` will attempt to read the config, and panic if the dir is missing
pub fn load(supported_led_modes: &[u8]) -> Self {
pub fn load() -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&CONFIG_PATH)
.expect(&format!(
"The file {} or directory /etc/asusd/ is missing",
CONFIG_PATH
)); // okay to cause panic here
.unwrap_or_else(|_| panic!("The directory /etc/asusd/ is missing")); // okay to cause panic here
let mut buf = String::new();
let config;
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
return Config::create_default(&mut file, &supported_led_modes);
config = Self::new();
} else if let Ok(data) = serde_json::from_str(&buf) {
config = data;
} else {
if let Ok(data) = serde_json::from_str(&buf) {
return data;
} else if let Ok(data) = serde_json::from_str::<ConfigV301>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV222>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV212>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
}
warn!("Could not deserialise {}", CONFIG_PATH);
panic!("Please remove {} then restart asusd", CONFIG_PATH);
warn!(
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
CONFIG_PATH, CONFIG_PATH
);
let cfg_old = CONFIG_PATH.to_string() + "-old";
std::fs::rename(CONFIG_PATH, cfg_old).unwrap_or_else(|err| {
panic!(
"Could not rename. Please remove {} then restart service: Error {}",
CONFIG_PATH, err
)
});
config = Self::new();
}
} else {
config = Self::new()
}
Config::create_default(&mut file, &supported_led_modes)
}
fn create_default(file: &mut File, supported_led_modes: &[u8]) -> Self {
// create a default config here
let mut config = Config::default();
for n in supported_led_modes {
config.kbd_backlight_modes.push(AuraModes::from(*n))
}
// Should be okay to unwrap this as is since it is a Default
let json = serde_json::to_string_pretty(&config).unwrap();
file.write_all(json.as_bytes())
.unwrap_or_else(|_| panic!("Could not write {}", CONFIG_PATH));
config.write();
config
}
@@ -115,90 +64,16 @@ impl Config {
if l == 0 {
warn!("File is empty {}", CONFIG_PATH);
} else {
let x: Config = serde_json::from_str(&buf)
*self = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", CONFIG_PATH));
*self = x;
}
}
}
pub fn read_new() -> Result<Config, Box<dyn std::error::Error>> {
let mut file = OpenOptions::new()
.read(true)
.open(&CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
let mut buf = String::new();
file.read_to_string(&mut buf)?;
let x: Config = serde_json::from_str(&buf)?;
Ok(x)
}
pub fn write(&self) {
let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
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,
}
}
}
impl Profile {
pub fn new(
min_percentage: u8,
max_percentage: u8,
turbo: bool,
fan_preset: u8,
fan_curve: Option<Curve>,
) -> Self {
Profile {
min_percentage,
max_percentage,
turbo,
fan_preset,
fan_curve,
}
}
}

View File

@@ -1,100 +0,0 @@
use rog_types::{aura_modes::AuraModes, gfx_vendors::GfxVendors};
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use crate::config::{Config, Profile};
/// for parsing old v2.1.2 config
#[derive(Deserialize)]
pub(crate) struct ConfigV212 {
gfx_managed: bool,
bat_charge_limit: u8,
active_profile: String,
toggle_profiles: Vec<String>,
power_profiles: BTreeMap<String, Profile>,
power_profile: u8,
kbd_led_brightness: u8,
kbd_backlight_mode: u8,
kbd_backlight_modes: Vec<AuraModes>,
}
impl ConfigV212 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_managed: self.gfx_managed,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.power_profile,
bat_charge_limit: self.bat_charge_limit,
kbd_led_brightness: self.kbd_led_brightness,
kbd_backlight_mode: self.kbd_backlight_mode,
kbd_backlight_modes: self.kbd_backlight_modes,
power_profiles: self.power_profiles,
}
}
}
/// for parsing old v2.2.2 config
#[derive(Deserialize)]
pub(crate) struct ConfigV222 {
gfx_managed: bool,
bat_charge_limit: u8,
active_profile: String,
toggle_profiles: Vec<String>,
power_profiles: BTreeMap<String, Profile>,
power_profile: u8,
kbd_led_brightness: u8,
kbd_backlight_mode: u8,
kbd_backlight_modes: Vec<AuraModes>,
}
impl ConfigV222 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_managed: self.gfx_managed,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.power_profile,
bat_charge_limit: self.bat_charge_limit,
kbd_led_brightness: self.kbd_led_brightness,
kbd_backlight_mode: self.kbd_backlight_mode,
kbd_backlight_modes: self.kbd_backlight_modes,
power_profiles: self.power_profiles,
}
}
}
#[derive(Deserialize, Serialize)]
pub(crate) struct ConfigV301 {
pub gfx_managed: bool,
pub gfx_nv_mode_is_dedicated: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
// TODO: remove power_profile
#[serde(skip)]
pub curr_fan_mode: u8,
pub bat_charge_limit: u8,
pub kbd_led_brightness: u8,
pub kbd_backlight_mode: u8,
pub kbd_backlight_modes: Vec<AuraModes>,
pub power_profiles: BTreeMap<String, Profile>,
}
impl ConfigV301 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_managed: self.gfx_managed,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.curr_fan_mode,
bat_charge_limit: self.bat_charge_limit,
kbd_led_brightness: self.kbd_led_brightness,
kbd_backlight_mode: self.kbd_backlight_mode,
kbd_backlight_modes: self.kbd_backlight_modes,
power_profiles: self.power_profiles,
}
}
}

View File

@@ -1,265 +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 SET: u8 = 0xc3;
const APPLY: u8 = 0xc4;
// Used to turn the panel on and off
// The next byte can be 0x03 for "on" and 0x00 for "off"
const ON_OFF: u8 = 0x04;
use log::{error, info, warn};
use rog_types::{
anime_matrix::{
AniMeDataBuffer, AniMeImageBuffer, AniMePacketType, ANIME_PANE1_PREFIX, ANIME_PANE2_PREFIX,
},
error::AuraError,
};
use rusb::{Device, DeviceHandle};
use std::convert::TryInto;
use std::error::Error;
use std::time::Duration;
use zbus::dbus_interface;
use crate::GetSupported;
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct AnimeSupportedFunctions(bool);
impl GetSupported for CtrlAnimeDisplay {
type A = AnimeSupportedFunctions;
fn get_supported() -> Self::A {
AnimeSupportedFunctions(CtrlAnimeDisplay::get_device(0x0b05, 0x193b).is_ok())
}
}
pub struct CtrlAnimeDisplay {
handle: DeviceHandle<rusb::GlobalContext>,
}
//AnimatrixWrite
pub trait Dbus {
/// Write an image 34x56 pixels. Each pixel is 0-255 greyscale.
fn write_image(&self, input: AniMeImageBuffer);
/// Write a direct stream of data
fn write_direct(&self, input: AniMeDataBuffer);
fn set_on_off(&self, status: bool);
fn set_boot_on_off(&self, status: bool);
}
impl crate::ZbusAdd for CtrlAnimeDisplay {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/Anime".try_into().unwrap(), self)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl Dbus for CtrlAnimeDisplay {
/// Writes a 34x56 image
fn write_image(&self, input: AniMeImageBuffer) {
self.write_image_buffer(input)
.map_or_else(|err| warn!("{}", err), |()| info!("Writing image to Anime"));
}
/// Writes a data stream of length
fn write_direct(&self, input: AniMeDataBuffer) {
self.write_data_buffer(input)
.map_or_else(|err| warn!("{}", err), |()| info!("Writing data to Anime"));
}
fn set_on_off(&self, status: bool) {
let mut buffer = [0u8; PACKET_SIZE];
buffer[0] = DEV_PAGE;
buffer[1] = WRITE;
buffer[2] = ON_OFF;
if status {
buffer[3] = 0x03;
} else {
buffer[3] = 0x00;
}
self.write_bytes(&buffer);
}
fn set_boot_on_off(&self, status: bool) {
let status_str = if status { "on" } else { "off" };
self.do_set_boot(status).map_or_else(
|err| warn!("{}", err),
|()| info!("Turning {} the AniMe at boot/shutdown", status_str),
);
self.do_apply().map_or_else(
|err| warn!("{}", err),
|()| info!("Turning {} the AniMe at boot/shutdown", status_str),
);
}
}
impl CtrlAnimeDisplay {
#[inline]
pub fn new() -> Result<CtrlAnimeDisplay, Box<dyn Error>> {
// We don't expect this ID to ever change
let device = CtrlAnimeDisplay::get_device(0x0b05, 0x193b)?;
let mut device = device.open()?;
device.reset()?;
device.set_auto_detach_kernel_driver(true).map_err(|err| {
error!("Auto-detach kernel driver failed: {}", err);
err
})?;
device.claim_interface(0).map_err(|err| {
error!("Could not claim device interface: {}", err);
err
})?;
info!("Device has an AniMe Matrix display");
let ctrl = CtrlAnimeDisplay { handle: device };
ctrl.do_initialization()?;
Ok(ctrl)
}
#[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)
}
/// Should only be used if the bytes you are writing are verified correct
#[inline]
fn write_bytes(&self, message: &[u8]) {
match self.handle.write_control(
0x21, // request_type
0x09, // request
0x35e, // value
0x00, // index
message,
Duration::from_millis(200),
) {
Ok(_) => {}
Err(err) => match err {
rusb::Error::Timeout => {}
_ => error!("Failed to write to led interrupt: {}", err),
},
}
}
#[inline]
fn write_data_buffer(&self, buffer: AniMeDataBuffer) -> Result<(), AuraError> {
let mut image = AniMePacketType::from(buffer);
image[0][..7].copy_from_slice(&ANIME_PANE1_PREFIX);
image[1][..7].copy_from_slice(&ANIME_PANE2_PREFIX);
for row in image.iter() {
self.write_bytes(row);
}
self.do_flush()?;
Ok(())
}
/// Write an Animatrix image
///
/// The expected USB 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_buffer(&self, buffer: AniMeImageBuffer) -> Result<(), AuraError> {
let mut image = AniMePacketType::from(buffer);
image[0][..7].copy_from_slice(&ANIME_PANE1_PREFIX);
image[1][..7].copy_from_slice(&ANIME_PANE2_PREFIX);
for row in image.iter() {
self.write_bytes(row);
}
self.do_flush()?;
Ok(())
}
#[inline]
fn do_initialization(&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);
Ok(())
}
#[inline]
fn do_flush(&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_boot(&self, status: bool) -> Result<(), AuraError> {
let mut flush = [0; PACKET_SIZE];
flush[0] = DEV_PAGE;
flush[1] = SET;
flush[2] = 0x01;
flush[3] = if status { 0x00 } else { 0x80 };
self.write_bytes(&flush);
Ok(())
}
#[inline]
fn do_apply(&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

@@ -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,402 @@
pub mod config;
pub mod zbus;
use ::zbus::Connection;
use log::{error, info, warn};
use logind_zbus::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 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 system thread started");
// Getting copies of these Atomics is done *in* the thread to ensure
// we don't block other threads/main
let thread_exit;
let thread_running;
// First two loops are to ensure we *do* aquire a lock on the mutex
// The reason the loop is required is because the USB writes can block
// for up to 10ms. We can't fail to get the atomics.
loop {
if let Ok(lock) = inner.try_lock() {
thread_exit = lock.thread_exit.clone();
thread_running = lock.thread_running.clone();
// Make any running loop exit first
thread_exit.store(true, Ordering::SeqCst);
break;
}
}
loop {
// wait for other threads to set not running so we know they exited
if !thread_running.load(Ordering::SeqCst) {
thread_exit.store(false, Ordering::SeqCst);
info!("AniMe forced a thread to exit");
break;
}
}
'main: loop {
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
for action in actions.iter() {
match action {
ActionData::Animation(frames) => {
if let Err(err) = rog_anime::run_animation(
frames,
thread_exit.clone(),
&|frame| {
inner
.try_lock()
.map(|lock| lock.write_data_buffer(frame))
.map_err(|err| {
warn!("rog_anime::run_animation: {}", err);
AnimeError::NoFrames
})
},
) {
warn!("rog_anime::run_animation: {}", err);
break 'main;
};
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
}
ActionData::Image(image) => {
once = false;
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(image.as_ref().clone())
}
}
ActionData::Pause(duration) => sleep(*duration),
ActionData::AudioEq => {}
ActionData::SystemInfo => {}
ActionData::TimeDate => {}
ActionData::Matrix => {}
}
}
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_exit.store(false, Ordering::SeqCst);
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<'a> {
inner: Arc<Mutex<CtrlAnime>>,
_c: Connection,
manager: ManagerProxy<'a>,
}
impl<'a> CtrlAnimeTask<'a> {
pub fn new(inner: Arc<Mutex<CtrlAnime>>) -> Self {
let connection =
Connection::new_system().expect("CtrlAnimeTask could not create dbus connection");
let manager =
ManagerProxy::new(&connection).expect("CtrlAnimeTask could not create ManagerProxy");
let c1 = inner.clone();
// Run this action when the system starts shutting down
manager
.connect_prepare_for_shutdown(move |shutdown| {
if shutdown {
'outer: loop {
if let Ok(lock) = c1.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(c1.clone(), lock.cache.shutdown.clone(), false);
break 'outer;
}
}
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
let c1 = inner.clone();
// Run this action when the system wakes up from sleep
manager
.connect_prepare_for_sleep(move |sleep| {
if !sleep {
// wait a fraction for things to wake up properly
std::thread::sleep(Duration::from_millis(100));
'outer: loop {
if let Ok(lock) = c1.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(c1.clone(), lock.cache.wake.clone(), true);
break 'outer;
}
}
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
Self {
inner,
_c: connection,
manager,
}
}
}
impl<'a> crate::CtrlTask for CtrlAnimeTask<'a> {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(mut lock) = self.inner.try_lock() {
// Refresh the config and cache incase the user has edited it
let config = AnimeConfig::load();
lock.cache
.init_from_config(&config)
.map_err(|err| {
warn!("CtrlAnimeTask: do_task {}", err);
err
})
.ok();
}
// Check for signals on each task iteration, this will run the callbacks
// if any signal is recieved
self.manager.next_signal()?;
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 log::warn;
use rog_anime::{
usb::{pkt_for_apply, pkt_for_set_boot, pkt_for_set_on},
AnimeDataBuffer, AnimePowerStates,
};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use std::sync::atomic::Ordering;
use super::CtrlAnime;
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
/// The struct with the main dbus methods requires this trait
impl crate::ZbusAdd for CtrlAnimeZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
// 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
fn set_on_off(&self, status: bool) {
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_on(status));
lock.config.awake_enabled = status;
lock.config.write();
let states = AnimePowerStates {
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
break 'outer;
}
}
}
/// Set whether the AniMe will show boot, suspend, or off animations
fn set_boot_on_off(&self, on: bool) {
'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();
let states = AnimePowerStates {
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
break 'outer;
}
}
}
/// 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
#[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)]
fn notify_power_states(&self, data: &AnimePowerStates) -> zbus::Result<()>;
}

View File

@@ -0,0 +1,276 @@
use crate::laptops::LaptopLedData;
use log::{error, info, warn};
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, LedBrightness};
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/aura.conf";
#[derive(Deserialize, Serialize)]
pub struct AuraConfigV320 {
pub brightness: u32,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
}
impl AuraConfigV320 {
pub(crate) fn into_current(self) -> AuraConfig {
AuraConfig {
brightness: <LedBrightness>::from(self.brightness),
current_mode: self.current_mode,
builtins: self.builtins,
multizone: self.multizone,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraConfigV352 {
pub brightness: LedBrightness,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
}
impl AuraConfigV352 {
pub(crate) fn into_current(self) -> AuraConfig {
AuraConfig {
brightness: self.brightness,
current_mode: self.current_mode,
builtins: self.builtins,
multizone: self.multizone,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraConfig {
pub brightness: LedBrightness,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
pub awake_enabled: bool,
pub sleep_anim_enabled: bool,
}
impl Default for AuraConfig {
fn default() -> Self {
AuraConfig {
brightness: LedBrightness::Med,
current_mode: AuraModeNum::Static,
builtins: BTreeMap::new(),
multizone: None,
awake_enabled: true,
sleep_anim_enabled: 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::<AuraConfigV320>(&buf) {
let config = data.into_current();
config.write();
info!("Updated AuraConfig version");
return config;
} else if let Ok(data) = serde_json::from_str::<AuraConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated AuraConfig version");
return config;
}
warn!(
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
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,419 @@
// 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 log::{info, warn};
use logind_zbus::ManagerProxy;
use rog_aura::{
usb::{
LED_APPLY, LED_AWAKE_OFF_SLEEP_OFF, LED_AWAKE_OFF_SLEEP_ON, LED_AWAKE_ON_SLEEP_OFF,
LED_AWAKE_ON_SLEEP_ON, LED_SET,
},
AuraEffect, LedBrightness, LED_MSG_LEN,
};
use rog_supported::LedSupportedFunctions;
use std::io::{Read, Write};
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use std::{fs::OpenOptions, thread::spawn};
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 multizone_led_mode = false;
let per_key_led_mode = false;
let laptop = LaptopLedData::get_data();
let stock_led_modes = laptop.standard;
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<'a> {
inner: Arc<Mutex<CtrlKbdLed>>,
_c: Connection,
manager: ManagerProxy<'a>,
}
impl<'a> CtrlKbdLedTask<'a> {
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
let connection =
Connection::new_system().expect("CtrlKbdLedTask could not create dbus connection");
let manager =
ManagerProxy::new(&connection).expect("CtrlKbdLedTask could not create ManagerProxy");
let c1 = inner.clone();
// Run this action when the system wakes up from sleep
manager
.connect_prepare_for_sleep(move |sleep| {
if !sleep {
let c1 = c1.clone();
spawn(move || {
// wait a fraction for things to wake up properly
//std::thread::sleep(Duration::from_millis(100));
loop {
if let Ok(ref mut lock) = c1.try_lock() {
lock.set_brightness(lock.config.brightness).ok();
break;
}
}
});
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
Self {
inner,
_c: connection,
manager,
}
}
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)
}
}
impl<'a> CtrlTask for CtrlKbdLedTask<'a> {
fn do_task(&self) -> Result<(), RogError> {
self.manager.next_signal()?;
if let Ok(ref mut lock) = self.inner.try_lock() {
return Self::update_config(lock);
}
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_states_enabled(ctrl.config.awake_enabled, ctrl.config.sleep_anim_enabled)
.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);
break;
}
Err(err) => warn!("led_node: {}", err),
}
}
let bright_node = Self::get_kbd_bright_path();
if led_node.is_none() && bright_node.is_none() {
return Err(RogError::MissingFunction(
"All keyboard features missing, you may require a v5.11 series kernel or newer"
.into(),
));
}
if bright_node.is_none() {
return Err(RogError::MissingFunction(
"No brightness control, you may require a v5.11 series kernel or newer".into(),
));
}
let ctrl = 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 if awake/on LED active, and/or sleep animation active
pub(super) fn set_states_enabled(&self, awake: bool, sleep: bool) -> Result<(), RogError> {
let bytes = if awake && sleep {
LED_AWAKE_ON_SLEEP_ON
} else if awake && !sleep {
LED_AWAKE_ON_SLEEP_OFF
} else if !awake && sleep {
LED_AWAKE_OFF_SLEEP_ON
} else if !awake && !sleep {
LED_AWAKE_OFF_SLEEP_OFF
} else {
LED_AWAKE_ON_SLEEP_ON
};
self.write_bytes(&bytes)?;
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,179 @@
use log::{error, warn};
use rog_aura::{AuraEffect, LedBrightness, LedPowerStates};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use super::controller::CtrlKbdLedZbus;
impl crate::ZbusAdd for CtrlKbdLedZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Led"), self)
.map_err(|err| {
error!("DbusKbdLed: add_to_server {}", err);
})
.ok();
}
}
/// 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)
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
fn set_awake_enabled(&mut self, enabled: bool) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.set_states_enabled(enabled, ctrl.config.sleep_anim_enabled)
.map_err(|err| warn!("{}", err))
.ok();
ctrl.config.awake_enabled = enabled;
ctrl.config.write();
let states = LedPowerStates {
enabled: ctrl.config.awake_enabled,
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
}
}
/// Set the keyboard LED suspend animation to enabled while the device is suspended
fn set_sleep_enabled(&mut self, enabled: bool) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.set_states_enabled(ctrl.config.awake_enabled, enabled)
.map_err(|err| warn!("{}", err))
.ok();
ctrl.config.sleep_anim_enabled = enabled;
ctrl.config.write();
let states = LedPowerStates {
enabled: ctrl.config.awake_enabled,
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
}
}
fn set_led_mode(&mut self, effect: AuraEffect) {
if let Ok(mut ctrl) = self.0.try_lock() {
match ctrl.do_command(effect) {
Ok(_) => {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
Err(err) => {
warn!("{}", err);
}
}
}
}
fn next_led_mode(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.toggle_mode(false)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
fn prev_led_mode(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.toggle_mode(true)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
fn next_led_brightness(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.next_brightness()
.unwrap_or_else(|err| warn!("{}", err));
}
}
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)]
fn awake_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.awake_enabled;
}
true
}
#[dbus_interface(property)]
fn sleep_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.sleep_anim_enabled;
}
true
}
/// Return the current mode data
#[dbus_interface(property)]
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)]
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)]
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)]
fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>;
#[dbus_interface(signal)]
fn notify_power_states(&self, data: &LedPowerStates) -> zbus::Result<()>;
}

View File

@@ -1,21 +1,18 @@
use crate::{config::Config, error::RogError, GetSupported};
//use crate::dbus::DbusEvents;
use log::{info, warn};
use serde_derive::{Deserialize, Serialize};
use std::convert::TryInto;
use rog_supported::ChargeSupportedFunctions;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::dbus_interface;
use zvariant::ObjectPath;
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
#[derive(Serialize, Deserialize)]
pub struct ChargeSupportedFunctions {
pub charge_level_set: bool,
}
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;
@@ -64,7 +61,10 @@ impl CtrlCharge {
impl crate::ZbusAdd for CtrlCharge {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/Charge".try_into().unwrap(), self)
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Charge"),
self,
)
.map_err(|err| {
warn!("CtrlCharge: add_to_server {}", err);
err
@@ -90,8 +90,12 @@ impl CtrlCharge {
}
fn get_battery_path() -> Result<&'static str, RogError> {
if Path::new(BAT_CHARGE_PATH).exists() {
Ok(BAT_CHARGE_PATH)
if Path::new(BAT_CHARGE_PATH0).exists() {
Ok(BAT_CHARGE_PATH0)
} else if Path::new(BAT_CHARGE_PATH1).exists() {
Ok(BAT_CHARGE_PATH1)
} else if Path::new(BAT_CHARGE_PATH2).exists() {
Ok(BAT_CHARGE_PATH2)
} else {
Err(RogError::MissingFunction(
"Charge control not available, you may require a v5.8.10 series kernel or newer"
@@ -108,12 +112,14 @@ impl CtrlCharge {
);
}
let path = Self::get_battery_path()?;
let mut file = OpenOptions::new()
.write(true)
.open(BAT_CHARGE_PATH)
.map_err(|err| RogError::Path(BAT_CHARGE_PATH.into(), err))?;
.open(path)
.map_err(|err| RogError::Path(path.into(), err))?;
file.write_all(limit.to_string().as_bytes())
.map_err(|err| RogError::Write(BAT_CHARGE_PATH.into(), err))?;
.map_err(|err| RogError::Write(path.into(), err))?;
info!("Battery charge limit: {}", limit);
config.read();

View File

@@ -1,347 +0,0 @@
use crate::error::RogError;
use crate::{
config::{Config, Profile},
GetSupported,
};
use log::{info, warn};
use rog_types::profile::{FanLevel, ProfileEvent};
use serde_derive::{Deserialize, Serialize};
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 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>>,
}
#[derive(Serialize, Deserialize)]
pub struct FanCpuSupportedFunctions {
pub stock_fan_modes: bool,
pub min_max_freq: bool,
pub fan_curve_set: bool,
}
impl GetSupported for CtrlFanAndCPU {
type A = FanCpuSupportedFunctions;
fn get_supported() -> Self::A {
FanCpuSupportedFunctions {
stock_fan_modes: CtrlFanAndCPU::get_fan_path().is_ok(),
min_max_freq: intel_pstate::PState::new().is_ok(),
fan_curve_set: rog_fan_curve::Board::from_board_name().is_some(),
}
}
}
pub struct DbusFanAndCpu {
inner: Arc<Mutex<CtrlFanAndCPU>>,
}
impl DbusFanAndCpu {
pub fn new(inner: Arc<Mutex<CtrlFanAndCPU>>) -> Self {
Self { inner }
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl DbusFanAndCpu {
/// Set profile details
fn set_profile(&self, profile: String) {
if let Ok(event) = serde_json::from_str(&profile) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
cfg.read();
ctrl.handle_profile_event(&event, &mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
self.notify_profile(&cfg.active_profile).unwrap_or(());
}
}
}
}
/// Fetch the active profile name
fn next_profile(&mut self) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
ctrl.do_next_profile(&mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
if let Ok(json) = serde_json::to_string(profile) {
self.notify_profile(&json)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
}
/// Fetch the active profile name
fn active_profile_name(&mut self) -> String {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
return cfg.active_profile.clone();
}
}
"Failed".to_string()
}
/// Fetch the active profile details
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)
.map_err(|err| {
warn!("DbusFanAndCpu: add_to_server {}", err);
err
})
.ok();
}
}
impl crate::Reloadable for CtrlFanAndCPU {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(mut config) = self.config.clone().try_lock() {
let profile = config.active_profile.clone();
self.set(&profile, &mut config)?;
// info!(
// "Reloaded fan mode: {:?}",
// FanLevel::from(config.power_profile)
// );
}
Ok(())
}
}
impl CtrlFanAndCPU {
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
let path = CtrlFanAndCPU::get_fan_path()?;
info!("Device has thermal throttle control");
Ok(CtrlFanAndCPU { path, config })
}
fn get_fan_path() -> Result<&'static str, RogError> {
if Path::new(FAN_TYPE_1_PATH).exists() {
Ok(FAN_TYPE_1_PATH)
} else if Path::new(FAN_TYPE_2_PATH).exists() {
Ok(FAN_TYPE_2_PATH)
} else {
Err(RogError::MissingFunction(
"Fan mode not available, you may require a v5.8.10 series kernel or newer".into(),
))
}
}
/// Toggle to next profile in list
pub(super) fn do_next_profile(&mut self, config: &mut Config) -> Result<(), RogError> {
config.read();
let mut i = config
.toggle_profiles
.iter()
.position(|x| x == &config.active_profile)
.map(|i| i + 1)
.unwrap_or(0);
if i >= config.toggle_profiles.len() {
i = 0;
}
let new_profile = config
.toggle_profiles
.get(i)
.unwrap_or(&config.active_profile)
.clone();
self.set(&new_profile, config)?;
info!("Profile was changed: {}", &new_profile);
Ok(())
}
fn set_fan_mode(&mut self, preset: u8, config: &mut Config) -> Result<(), RogError> {
let mode = config.active_profile.clone();
let mut fan_ctrl = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
config.read();
let mut mode_config = config
.power_profiles
.get_mut(&mode)
.ok_or_else(|| RogError::MissingProfile(mode.clone()))?;
config.curr_fan_mode = preset;
mode_config.fan_preset = preset;
config.write();
fan_ctrl
.write_all(format!("{}\n", preset).as_bytes())
.map_err(|err| RogError::Write(self.path.into(), err))?;
info!("Fan mode set to: {:?}", FanLevel::from(preset));
Ok(())
}
fn handle_profile_event(
&mut self,
event: &ProfileEvent,
config: &mut Config,
) -> Result<(), RogError> {
match event {
ProfileEvent::Toggle => self.do_next_profile(config)?,
ProfileEvent::ChangeMode(mode) => {
self.set_fan_mode(*mode, config)?;
let mode = config.active_profile.clone();
self.set_pstate_for_fan_mode(&mode, config)?;
self.set_fan_curve_for_fan_mode(&mode, config)?;
}
ProfileEvent::Cli(command) => {
let profile_key = match command.profile.as_ref() {
Some(k) => k.clone(),
None => config.active_profile.clone(),
};
let mut profile = if command.create {
config
.power_profiles
.entry(profile_key.clone())
.or_insert_with(Profile::default)
} else {
config
.power_profiles
.get_mut(&profile_key)
.ok_or_else(|| RogError::MissingProfile(profile_key.clone()))?
};
if command.turbo.is_some() {
profile.turbo = command.turbo.unwrap();
}
if let Some(min_perc) = command.min_percentage {
profile.min_percentage = min_perc;
}
if let Some(max_perc) = command.max_percentage {
profile.max_percentage = max_perc;
}
if let Some(ref preset) = command.preset {
profile.fan_preset = preset.into();
}
if let Some(ref curve) = command.curve {
profile.fan_curve = Some(curve.clone());
}
self.set(&profile_key, config)?;
}
}
Ok(())
}
fn set(&mut self, profile: &str, config: &mut Config) -> Result<(), RogError> {
let mode_config = config
.power_profiles
.get(profile)
.ok_or_else(|| RogError::MissingProfile(profile.into()))?;
let mut fan_ctrl = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
config.curr_fan_mode = mode_config.fan_preset;
fan_ctrl
.write_all(format!("{}\n", mode_config.fan_preset).as_bytes())
.map_err(|err| RogError::Write(self.path.into(), err))?;
self.set_pstate_for_fan_mode(profile, config)?;
self.set_fan_curve_for_fan_mode(profile, config)?;
config.active_profile = profile.into();
config.write();
Ok(())
}
fn set_pstate_for_fan_mode(&self, mode: &str, config: &mut Config) -> Result<(), RogError> {
info!("Setting pstate");
let mode_config = config
.power_profiles
.get(mode)
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
// Set CPU pstate
if let Ok(pstate) = intel_pstate::PState::new() {
pstate.set_min_perf_pct(mode_config.min_percentage)?;
pstate.set_max_perf_pct(mode_config.max_percentage)?;
pstate.set_no_turbo(!mode_config.turbo)?;
info!(
"Intel CPU Power: min: {}%, max: {}%, turbo: {}",
mode_config.min_percentage, mode_config.max_percentage, mode_config.turbo
);
} else {
info!("Setting pstate for AMD CPU");
// must be AMD CPU
let mut file = OpenOptions::new()
.write(true)
.open(AMD_BOOST_PATH)
.map_err(|err| RogError::Path(self.path.into(), err))?;
let boost = if mode_config.turbo { "1" } else { "0" }; // opposite of Intel
file.write_all(boost.as_bytes())
.map_err(|err| RogError::Write(AMD_BOOST_PATH.into(), err))?;
info!("AMD CPU Turbo: {}", boost);
}
Ok(())
}
fn set_fan_curve_for_fan_mode(&self, mode: &str, config: &Config) -> Result<(), RogError> {
let mode_config = &config
.power_profiles
.get(mode)
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
if let Some(ref curve) = mode_config.fan_curve {
use rog_fan_curve::{Board, Fan};
if let Some(board) = Board::from_board_name() {
curve.apply(board, Fan::Cpu)?;
curve.apply(board, Fan::Gpu)?;
} else {
warn!("Fan curve unsupported on this board.")
}
}
Ok(())
}
}

View File

@@ -1,42 +0,0 @@
use std::error;
use std::fmt;
use crate::error::RogError;
#[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),
Modprobe(String),
DisplayManager(String),
}
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),
GfxError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
GfxError::DisplayManager(detail) => write!(f, "Display manager: {}", detail),
}
}
}
impl error::Error for GfxError {}
impl From<GfxError> for RogError {
fn from(err: GfxError) -> Self {
RogError::GfxSwitching(err)
}
}

View File

@@ -1,378 +0,0 @@
use ctrl_gfx::error::GfxError;
use ctrl_gfx::*;
use log::{error, info, warn};
use rog_types::gfx_vendors::GfxVendors;
use std::io::Write;
use std::iter::FromIterator;
use std::process::Command;
use std::str::FromStr;
use std::{sync::Arc, sync::Mutex};
use sysfs_class::{PciDevice, SysClass};
use system::{GraphicsDevice, PciBus};
use zbus::dbus_interface;
use crate::*;
pub struct CtrlGraphics {
bus: PciBus,
_amd: Vec<GraphicsDevice>,
_intel: Vec<GraphicsDevice>,
nvidia: Vec<GraphicsDevice>,
#[allow(dead_code)]
other: Vec<GraphicsDevice>,
config: Arc<Mutex<Config>>,
}
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<()>;
}
use std::convert::TryInto;
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl Dbus for CtrlGraphics {
fn vendor(&self) -> String {
self.get_gfx_mode()
.map(|gfx| gfx.into())
.unwrap_or_else(|err| format!("Get vendor failed: {}", err))
}
fn power(&self) -> String {
Self::get_runtime_status().unwrap_or_else(|err| format!("Get power status failed: {}", err))
}
fn set_vendor(&mut self, vendor: String) {
if let Ok(tmp) = GfxVendors::from_str(&vendor) {
let msg = self.set_gfx_config(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(&msg)
.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 ZbusAdd for CtrlGraphics {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&"/org/asuslinux/Gfx"
.try_into()
.expect("Couldn't add to zbus"),
self,
)
.map_err(|err| {
warn!("CtrlGraphics: add_to_server {}", err);
err
})
.ok();
}
}
impl Reloadable for CtrlGraphics {
fn reload(&mut self) -> Result<(), RogError> {
self.auto_power()?;
info!("Reloaded gfx mode: {:?}", self.get_gfx_mode()?);
Ok(())
}
}
impl CtrlGraphics {
pub fn new(config: Arc<Mutex<Config>>) -> std::io::Result<CtrlGraphics> {
let bus = PciBus::new()?;
info!("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)));
}
}
}
}
Ok(CtrlGraphics {
bus,
_amd: amd,
_intel: intel,
nvidia,
other,
config,
})
}
fn save_gfx_mode(&self, vendor: GfxVendors) -> Result<(), RogError> {
if let Ok(mut config) = self.config.lock() {
config.gfx_mode = vendor.clone();
config.write();
return Ok(());
}
// TODO: Error here
Ok(())
}
/// Associated method to get which vendor mode is set
pub fn get_gfx_mode(&self) -> Result<GfxVendors, RogError> {
if let Ok(config) = self.config.lock() {
return Ok(config.gfx_mode.clone());
}
// TODO: Error here
Ok(GfxVendors::Hybrid)
}
fn get_runtime_status() -> Result<String, RogError> {
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 toggle_fallback_service(vendor: GfxVendors) -> Result<(), RogError> {
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
);
}
Ok(())
}
fn write_xorg_conf(vendor: GfxVendors) -> Result<(), RogError> {
let text = if vendor == GfxVendors::Nvidia {
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_NVIDIA, PRIMARY_GPU_END].concat()
} else {
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_END].concat()
};
info!("Writing {}", PRIMARY_GPU_XORG_PATH);
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))?;
file.write_all(&text)
.and_then(|_| file.sync_all())
.map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?;
Ok(())
}
fn write_modprobe_conf() -> Result<(), RogError> {
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))?;
file.write_all(MODPROBE_BASE)
.and_then(|_| file.sync_all())
.map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?;
Ok(())
}
fn unbind_remove_nvidia(&self) -> Result<(), RogError> {
// 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 do_driver_action(driver: &str, action: &str) -> Result<(), RogError> {
let mut cmd = Command::new(action);
cmd.arg(driver);
let status = cmd
.status()
.map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?;
if !status.success() {
let msg = format!("{} {} failed: {:?}", action, driver, status);
error!("{}", msg);
return Err(GfxError::Modprobe(msg).into());
}
Ok(())
}
fn do_display_manager_action(action: &str) -> Result<(), RogError> {
let service = "display-manager.service";
let mut cmd = Command::new("systemctl");
cmd.arg(action);
cmd.arg(service);
let status = cmd
.status()
.map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?;
if !status.success() {
let msg = format!("systemctl {} {} failed: {:?}", action, service, status);
error!("{}", msg);
return Err(GfxError::DisplayManager(msg).into());
}
Ok(())
}
fn wait_display_manager_inactive() -> Result<(), RogError> {
let service = "display-manager.service";
let mut cmd = Command::new("systemctl");
cmd.arg("is-active");
cmd.arg(service);
let mut count = 0;
while count <= 4 {
let output = cmd
.output()
.map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?;
if output.stdout.starts_with("inactive".as_bytes()) {
return Ok(());
}
std::thread::sleep(std::time::Duration::from_millis(500));
count += 1;
}
return Err(
GfxError::DisplayManager("display-manager did not completely stop".into()).into(),
);
}
pub fn do_vendor_tasks(&mut self, vendor: GfxVendors) -> Result<(), RogError> {
Self::write_xorg_conf(vendor)?;
Self::write_modprobe_conf()?; // TODO: Not required here, should put in startup?
// Rescan before doing remove or add drivers
self.bus
.rescan()
.map_err(|err| GfxError::Bus("bus rescan error".into(), err))?;
let drivers = vec!["nvidia_drm", "nvidia_modeset", "nvidia"]; // i2c_nvidia_gpu?
match vendor {
GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Compute => {
for driver in drivers {
Self::do_driver_action(driver, "modprobe")?;
}
}
// TODO: compute mode, needs different setup
// GfxVendors::Compute => {}
GfxVendors::Integrated => {
for driver in drivers {
Self::do_driver_action(driver, "rmmod")?;
}
self.unbind_remove_nvidia()?;
}
}
self.save_gfx_mode(vendor)?;
Ok(())
}
/// For manually calling (not on boot/startup)
///
/// Will stop and start display manager without warning
pub fn set_gfx_config(&mut self, vendor: GfxVendors) -> Result<String, RogError> {
Self::do_display_manager_action("stop")?;
Self::wait_display_manager_inactive()?;
self.do_vendor_tasks(vendor)?;
Self::do_display_manager_action("start")?;
// TODO: undo if failed? Save last mode, catch errors...
let v: &str = vendor.into();
Ok(format!("Graphics mode changed to {} successfully", v))
}
// if CtrlRogBios::has_dedicated_gfx_toggle() {
// if let Ok(config) = self.config.clone().try_lock() {
// // Switch to dedicated if config says to do so
// if config.gfx_nv_mode_is_dedicated && vendor == GfxVendors::Nvidia {
// CtrlRogBios::set_gfx_mode(true)
// .unwrap_or_else(|err| warn!("Gfx controller: {}", err));
// } else if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
// // otherwise if switching to non-Nvidia mode turn off dedicated mode
// if ded == 1 && vendor != GfxVendors::Nvidia {
// CtrlRogBios::set_gfx_mode(false)
// .unwrap_or_else(|err| warn!("Gfx controller: {}", err));
// }
// }
// }
// }
fn auto_power(&mut self) -> Result<(), RogError> {
let vendor = self.get_gfx_mode()?;
self.do_vendor_tasks(vendor)?;
Self::toggle_fallback_service(vendor)?;
Ok(())
}
}

View File

@@ -1,32 +0,0 @@
pub mod error;
pub mod gfx;
pub mod system;
const MODPROBE_PATH: &str = "/etc/modprobe.d/asusd.conf";
static MODPROBE_BASE: &[u8] = br#"# Automatically generated by asusd
#blacklist i2c_nvidia_gpu
#alias i2c_nvidia_gpu off
blacklist nouveau
alias nouveau off
options nvidia NVreg_DynamicPowerManagement=0x02
options nvidia-drm modeset=1
"#;
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,154 +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",
))
}
}
/// Will rescan the device tree, which adds all removed devices back
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: 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 rebind(&self) -> Result<(), std::io::Error> {
for func in self.functions.iter() {
if func.path().exists() {
match func.driver() {
Ok(driver) => {
info!("{}: Binding {}", driver.id(), func.id());
unsafe {
driver.bind(&func).map_err(|err| {
error!("gfx bind: {}", err);
err
})?;
}
}
Err(err) => match err.kind() {
io::ErrorKind::NotFound => (),
_ => {
error!("gfx driver: {:?}, {}", func.path(), err);
return Err(err);
}
},
}
}
}
Ok(())
}
pub fn remove(&self) -> Result<(), std::io::Error> {
for func in self.functions.iter() {
if func.path().exists() {
match func.driver() {
Ok(driver) => {
error!("{}: in use by {}", func.id(), driver.id());
}
Err(why) => match why.kind() {
std::io::ErrorKind::NotFound => {
info!("{}: Removing", func.id());
unsafe {
// ignore errors and carry on
if let Err(err) = func.remove() {
error!("gfx remove: {}", err);
}
}
}
_ => {
error!("Remove device failed");
}
},
}
} else {
warn!("{}: Already removed", func.id());
}
}
info!("Removed all gfx devices");
Ok(())
}
}

View File

@@ -1,569 +0,0 @@
// Only these two packets must be 17 bytes
static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
use crate::{
config::Config,
error::RogError,
laptops::{match_laptop, HELP_ADDRESS},
};
use log::{error, info, warn};
use rog_types::{
aura_brightness_bytes,
aura_modes::{AuraModes, PER_KEY},
fancy::KeyColourArray,
LED_MSG_LEN,
};
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::sync::Arc;
use std::sync::Mutex;
use std::{convert::TryInto, path::Path};
use zbus::dbus_interface;
use crate::GetSupported;
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct LedSupportedFunctions {
pub brightness_set: bool,
pub stock_led_modes: Option<Vec<u8>>,
pub per_key_led_mode: bool,
}
impl GetSupported for CtrlKbdBacklight {
type A = LedSupportedFunctions;
fn get_supported() -> Self::A {
// let mode = <&str>::from(&<AuraModes>::from(*mode));
let mut stock_led_modes = None;
let mut per_key_led_mode = false;
if let Some(laptop) = match_laptop() {
let modes = laptop.supported_modes().to_vec();
if modes.contains(&PER_KEY) {
per_key_led_mode = true;
let modes = modes.iter().filter(|x| **x != PER_KEY).copied().collect();
stock_led_modes = Some(modes);
} else {
stock_led_modes = Some(modes);
}
}
LedSupportedFunctions {
brightness_set: CtrlKbdBacklight::get_kbd_bright_path().is_ok(),
stock_led_modes,
per_key_led_mode,
}
}
}
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)
.map_err(|err| {
error!("DbusKbdBacklight: add_to_server {}", err);
})
.ok();
}
}
#[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));
}
_ => {
if let Ok(json) = serde_json::to_string(&data) {
match ctrl.do_command(data, &mut cfg) {
Ok(_) => {
self.notify_led(&json).ok();
}
Err(err) => {
warn!("{}", err);
}
}
}
}
}
}
}
} else {
warn!("SetKeyBacklight could not deserialise");
}
}
fn next_led_mode(&self) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
ctrl.toggle_mode(false, &mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = cfg.get_led_mode_data(cfg.kbd_backlight_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
self.notify_led(&json)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
}
fn prev_led_mode(&self) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
ctrl.toggle_mode(true, &mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = cfg.get_led_mode_data(cfg.kbd_backlight_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
self.notify_led(&json)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
}
/// Return the current mode data
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()
}
/// Return a list of available modes
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()
}
/// Return the current LED brightness
fn led_brightness(&self) -> i8 {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(cfg) = ctrl.config.clone().try_lock() {
return cfg.kbd_led_brightness as i8;
}
}
warn!("SetKeyBacklight could not deserialise");
-1
}
#[dbus_interface(signal)]
fn notify_led(&self, data: &str) -> zbus::Result<()>;
}
impl crate::Reloadable for CtrlKbdBacklight {
fn reload(&mut self) -> Result<(), RogError> {
// set current mode (if any)
if 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| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
}
_ => RogError::Path((&self.bright_node).into(), err),
})?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)
.map_err(|err| RogError::Read("buffer".into(), err))?;
if let Some(num) = char::from(buf[0]).to_digit(10) {
if 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>>,
) -> Result<Self, RogError> {
// TODO: return error if *all* nodes are None
let led_node = Self::get_node_failover(id_product, None, Self::scan_led_node).map_or_else(
|err| {
warn!("led_node: {}", err);
None
},
Some,
);
let kbd_node = Self::get_node_failover(id_product, condev_iface, Self::scan_kbd_node)
.map_or_else(
|err| {
warn!("kbd_node: {}", err);
None
},
Some,
);
let bright_node = Self::get_kbd_bright_path();
if led_node.is_none() && kbd_node.is_none() && Self::get_kbd_bright_path().is_err() {
return Err(RogError::MissingFunction(
"All keyboard features missing, you may require a v5.11 series kernel or newer"
.into(),
));
}
let ctrl = CtrlKbdBacklight {
// Using `ok` here so we can continue without keyboard features but
// still get brightness control at least... maybe...
led_node,
kbd_node,
// TODO: Check for existance
bright_node: bright_node?.to_owned(),
supported_modes,
flip_effect_write: false,
config,
};
Ok(ctrl)
}
fn get_kbd_bright_path() -> Result<&'static str, RogError> {
if Path::new(KBD_BRIGHT_PATH).exists() {
Ok(KBD_BRIGHT_PATH)
} else {
Err(RogError::MissingFunction(
"Keyboard features missing, you may require a v5.11 series kernel or newer".into(),
))
}
}
fn get_node_failover(
id_product: &str,
iface: Option<&String>,
fun: fn(&str, Option<&String>) -> Result<String, RogError>,
) -> Result<String, RogError> {
match fun(id_product, iface) {
Ok(o) => return Ok(o),
Err(e) => {
warn!("Looking for node: {}", e.to_string());
}
}
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")
.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());
}
}
}
}
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) {
// 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: 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 toggle_mode(&mut self, reverse: bool, config: &mut Config) -> Result<(), RogError> {
let current = config.kbd_backlight_mode;
if let Some(idx) = self.supported_modes.iter().position(|v| *v == current) {
let mut idx = idx;
// goes past end of array
if reverse {
if idx == 0 {
idx = self.supported_modes.len() - 1;
} else {
idx -= 1;
}
} else {
idx += 1;
if idx == self.supported_modes.len() {
idx = 0;
}
}
let next = self.supported_modes[idx];
config.read();
if let Some(data) = config.get_led_mode_data(next) {
self.write_mode(&data)?;
config.kbd_backlight_mode = next;
}
config.write();
}
Ok(())
}
#[inline]
fn write_mode(&mut self, mode: &AuraModes) -> Result<(), RogError> {
let mode_num: u8 = u8::from(mode);
if !self.supported_modes.contains(&mode_num) {
return Err(RogError::NotSupported);
}
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)?;
}
}
AuraModes::MultiStatic(_) | AuraModes::MultiBreathe(_) => {
let bytes: [[u8; LED_MSG_LEN]; 4] = mode.into();
for array in bytes.iter() {
self.write_bytes(array)?;
}
self.write_bytes(&LED_SET)?;
// Changes won't persist unless apply is set
self.write_bytes(&LED_APPLY)?;
return Ok(());
}
_ => {
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,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,151 @@
use std::sync::{Arc, Mutex};
use crate::error::RogError;
use crate::{CtrlTask, GetSupported};
use log::{info, warn};
use rog_profiles::error::ProfileError;
use rog_profiles::{FanCurveProfiles, Profile};
use rog_supported::PlatformProfileFunctions;
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 with the following patch applied:
https://lkml.org/lkml/2021/8/18/1022
"#
);
}
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/8/20/232
Please note that as of 24/08/2021 this is not final.
"#
);
}
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() {
curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?;
}
}
Ok(())
}
}
impl CtrlPlatformProfile {
pub fn new(mut config: ProfileConfig) -> Result<Self, RogError> {
if Profile::is_platform_profile_supported() {
info!("Device has profile control available");
if let Ok(ref device) = FanCurveProfiles::get_device() {
let profile = config.active_profile;
if let Some(curve) = config.fan_curves.as_mut() {
curve.read_from_dev_profile(profile, device);
}
}
config.write();
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 }
}
}
impl CtrlTask for CtrlProfileTask {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(ref mut lock) = self.ctrl.try_lock() {
let new_profile = Profile::get_active_profile().unwrap();
if new_profile != lock.config.active_profile {
lock.config.active_profile = new_profile;
lock.save_config();
}
}
Ok(())
}
}

View File

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

View File

@@ -0,0 +1,189 @@
use log::warn;
use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::fan_curve_set::FanCurveSet;
use rog_profiles::Profile;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::{dbus_interface, fdo::Error};
use zvariant::ObjectPath;
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.
fn next_profile(&mut self) {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.set_next_profile()
.unwrap_or_else(|err| warn!("{}", err));
ctrl.save_config();
}
self.do_notification();
}
/// 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
fn set_active_profile(&self, profile: Profile) {
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();
}
self.do_notification();
}
/// 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();
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();
return Ok(());
} else {
return 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)]
fn notify_profile(&self, profile: &Profile) -> zbus::Result<()> {}
}
impl ProfileZbus {
fn do_notification(&self) {
if let Ok(ctrl) = self.inner.try_lock() {
self.notify_profile(&ctrl.config.active_profile)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
impl crate::ZbusAdd for ProfileZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Profile"),
self,
)
.map_err(|err| {
warn!("DbusFanAndCpu: add_to_server {}", err);
err
})
.ok();
}
}

View File

@@ -1,14 +1,15 @@
use crate::{config::Config, error::RogError, GetSupported};
use log::{error, info, warn};
use serde_derive::{Deserialize, Serialize};
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 std::{convert::TryInto, io::BufRead};
use zbus::dbus_interface;
use zvariant::ObjectPath;
const INITRAMFS_PATH: &str = "/usr/sbin/update-initramfs";
const DRACUT_PATH: &str = "/usr/bin/dracut";
@@ -22,19 +23,13 @@ pub struct CtrlRogBios {
_config: Arc<Mutex<Config>>,
}
#[derive(Serialize, Deserialize)]
pub struct RogBiosSupportedFunctions {
pub post_sound_toggle: bool,
pub dedicated_gfx_toggle: bool,
}
impl GetSupported for CtrlRogBios {
type A = RogBiosSupportedFunctions;
fn get_supported() -> Self::A {
RogBiosSupportedFunctions {
post_sound_toggle: CtrlRogBios::check_path_exists(ASUS_POST_LOGO_SOUND).is_ok(),
dedicated_gfx_toggle: CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE).is_ok(),
post_sound_toggle: Path::new(ASUS_POST_LOGO_SOUND).exists(),
dedicated_gfx_toggle: Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists(),
}
}
}
@@ -68,8 +63,6 @@ impl CtrlRogBios {
#[dbus_interface(signal)]
pub fn notify_dedicated_graphic_mode(&self, dedicated: bool) -> zbus::Result<()> {}
// // // // // // // // // //
pub fn set_post_boot_sound(&mut self, on: bool) {
Self::set_boot_sound(on)
.map_err(|err| {
@@ -101,7 +94,10 @@ impl CtrlRogBios {
impl crate::ZbusAdd for CtrlRogBios {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/RogBios".try_into().unwrap(), self)
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/RogBios"),
self,
)
.map_err(|err| {
warn!("CtrlRogBios: add_to_server {}", err);
err
@@ -118,27 +114,20 @@ impl crate::Reloadable for CtrlRogBios {
impl CtrlRogBios {
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
match CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE) {
Ok(_) => {
CtrlRogBios::set_path_mutable(ASUS_SWITCH_GRAPHIC_MODE)?;
}
Err(err) => {
info!("ROG Switchable Graphics (bios) not detected: {}", err);
}
if Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists() {
CtrlRogBios::set_path_mutable(ASUS_SWITCH_GRAPHIC_MODE)?;
} else {
info!("G-Sync Switchable Graphics not detected");
info!("If your laptop is not a G-Sync enabled laptop then you can ignore this. Standard graphics switching will still work.");
}
match CtrlRogBios::check_path_exists(ASUS_POST_LOGO_SOUND) {
Ok(_) => {
CtrlRogBios::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
}
Err(err) => {
info!("ROG boot sound toggle (bios) not detected: {}", err);
}
if Path::new(ASUS_POST_LOGO_SOUND).exists() {
CtrlRogBios::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
} else {
info!("Switch for POST boot sound not detected");
}
Ok(CtrlRogBios {
_config: config,
})
Ok(CtrlRogBios { _config: config })
}
fn set_path_mutable(path: &str) -> Result<(), RogError> {
@@ -151,19 +140,8 @@ impl CtrlRogBios {
Ok(())
}
fn check_path_exists(path: &str) -> Result<(), RogError> {
if Path::new(path).exists() {
Ok(())
} else {
Err(RogError::MissingFunction(path.into()))
}
}
pub fn has_dedicated_gfx_toggle() -> bool {
if CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE).is_ok() {
return true;
}
false
Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists()
}
pub fn get_gfx_mode() -> Result<i8, RogError> {
@@ -190,7 +168,7 @@ impl CtrlRogBios {
.map_err(|err| RogError::Path(path.into(), err))?;
let mut data = Vec::new();
file.read_to_end(&mut data).unwrap();
file.read_to_end(&mut data)?;
let idx = data.len() - 1;
if dedicated {
@@ -204,17 +182,6 @@ impl CtrlRogBios {
.map_err(|err| RogError::Path(path.into(), err))?;
self.update_initramfs(dedicated)?;
// if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
// if let Ok(vendor) = CtrlGraphics::get_vendor() {
// if ded == 1 && vendor != "nvidia" {
// warn!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
// CtrlGraphics::set_gfx_config(&GfxVendors::Nvidia)
// .unwrap_or_else(|err| warn!("Gfx controller: {}", err));
// }
// }
// }
Ok(())
}
@@ -271,6 +238,7 @@ impl CtrlRogBios {
} 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'");
}
@@ -301,7 +269,7 @@ impl CtrlRogBios {
RogError::Write(module_include.to_string_lossy().to_string(), err)
})?;
// add nvidia modules to module_include
file.write_all(modules.concat().as_bytes()).unwrap();
file.write_all(modules.concat().as_bytes())?;
} else {
let file = std::fs::OpenOptions::new()
.read(true)
@@ -312,11 +280,9 @@ impl CtrlRogBios {
let mut buf = Vec::new();
// remove modules
for line in std::io::BufReader::new(file).lines() {
if let Ok(l) = line {
if !modules.contains(&l.as_ref()) {
buf.append(&mut l.as_bytes().to_vec());
}
for line in std::io::BufReader::new(file).lines().flatten() {
if !modules.contains(&line.as_str()) {
buf.append(&mut line.as_bytes().to_vec());
}
}
@@ -326,7 +292,7 @@ impl CtrlRogBios {
.map_err(|err| {
RogError::Write(module_include.to_string_lossy().to_string(), err)
})?;
std::io::BufWriter::new(file).write_all(&buf).unwrap();
std::io::BufWriter::new(file).write_all(&buf)?;
}
}
@@ -335,7 +301,7 @@ impl CtrlRogBios {
.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()).into());
return Err(RogError::Initramfs("Ram disk update failed".into()));
} else {
info!("Successfully updated initramfs");
}

View File

@@ -1,38 +1,42 @@
use std::convert::TryInto;
use log::warn;
use serde_derive::{Deserialize, Serialize};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use zvariant_derive::Type;
use crate::{
ctrl_anime::{AnimeSupportedFunctions, CtrlAnimeDisplay},
ctrl_charge::{ChargeSupportedFunctions, CtrlCharge},
ctrl_fan_cpu::{CtrlFanAndCPU, FanCpuSupportedFunctions},
ctrl_leds::{CtrlKbdBacklight, LedSupportedFunctions},
ctrl_rog_bios::{CtrlRogBios, RogBiosSupportedFunctions},
GetSupported,
ctrl_anime::CtrlAnime, ctrl_aura::controller::CtrlKbdLed, ctrl_charge::CtrlCharge,
ctrl_profiles::controller::CtrlPlatformProfile, ctrl_rog_bios::CtrlRogBios, GetSupported,
};
#[derive(Serialize, Deserialize)]
use rog_supported::{
AnimeSupportedFunctions, ChargeSupportedFunctions, LedSupportedFunctions,
PlatformProfileFunctions, RogBiosSupportedFunctions,
};
#[derive(Serialize, Deserialize, Type)]
pub struct SupportedFunctions {
anime_ctrl: AnimeSupportedFunctions,
charge_ctrl: ChargeSupportedFunctions,
fan_cpu_ctrl: FanCpuSupportedFunctions,
keyboard_led: LedSupportedFunctions,
rog_bios_ctrl: RogBiosSupportedFunctions,
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) -> String {
serde_json::to_string_pretty(self).unwrap()
fn supported_functions(&self) -> &SupportedFunctions {
self
}
}
impl crate::ZbusAdd for SupportedFunctions {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&"/org/asuslinux/Supported".try_into().unwrap(), self)
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Supported"),
self,
)
.map_err(|err| {
warn!("SupportedFunctions: add_to_server {}", err);
err
@@ -46,10 +50,10 @@ impl GetSupported for SupportedFunctions {
fn get_supported() -> Self::A {
SupportedFunctions {
keyboard_led: CtrlKbdBacklight::get_supported(),
anime_ctrl: CtrlAnimeDisplay::get_supported(),
anime_ctrl: CtrlAnime::get_supported(),
keyboard_led: CtrlKbdLed::get_supported(),
charge_ctrl: CtrlCharge::get_supported(),
fan_cpu_ctrl: CtrlFanAndCPU::get_supported(),
platform_profile: CtrlPlatformProfile::get_supported(),
rog_bios_ctrl: CtrlRogBios::get_supported(),
}
}

View File

@@ -1,27 +1,36 @@
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_fan_cpu::{CtrlFanAndCPU, DbusFanAndCpu};
use daemon::ctrl_leds::{CtrlKbdBacklight, DbusKbdBacklight};
use daemon::laptops::match_laptop;
use daemon::ctrl_profiles::config::ProfileConfig;
use daemon::ctrl_profiles::controller::CtrlProfileTask;
use daemon::{
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
};
use daemon::{ctrl_anime::CtrlAnimeDisplay, ctrl_gfx::gfx::CtrlGraphics};
use daemon::{
ctrl_profiles::{controller::CtrlPlatformProfile, zbus::ProfileZbus},
laptops::LaptopLedData,
};
use ::zbus::{fdo, Connection, ObjectServer};
use daemon::{CtrlTask, Reloadable, ZbusAdd};
use log::LevelFilter;
use log::{error, info, warn};
use rog_dbus::DBUS_NAME;
use rog_types::gfx_vendors::GfxVendors;
use rog_profiles::Profile;
use std::env;
use std::error::Error;
use std::io::Write;
use std::sync::Arc;
use std::sync::Mutex;
use daemon::ctrl_rog_bios::CtrlRogBios;
use std::convert::Into;
use std::convert::TryInto;
use zbus::fdo;
use zbus::Connection;
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();
@@ -31,43 +40,49 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
.filter(None, LevelFilter::Info)
.init();
info!("daemon version {}", daemon::VERSION);
info!(" rog-dbus version {}", rog_dbus::VERSION);
info!("rog-types version {}", rog_types::VERSION);
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);
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)
/// The actual main loop for the daemon
fn start_daemon() -> Result<(), Box<dyn Error>> {
let supported = SupportedFunctions::get_supported();
print_board_info();
println!("{}", serde_json::to_string_pretty(&supported).unwrap());
let laptop = match_laptop();
let config = if let Some(laptop) = laptop.as_ref() {
Config::load(laptop.supported_modes())
} else {
Config::load(&[])
};
println!("{}", serde_json::to_string_pretty(&supported)?);
// Collect tasks for task thread
let mut tasks: Vec<Box<dyn CtrlTask + Send>> = Vec::new();
// Start zbus server
let connection = Connection::new_system()?;
fdo::DBusProxy::new(&connection)?
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
let mut object_server = zbus::ObjectServer::new(&connection);
let fdo_connection = fdo::DBusProxy::new(&connection)?;
let mut object_server = ObjectServer::new(&connection);
let config = Config::load();
let config = Arc::new(Mutex::new(config));
supported.add_to_server(&mut object_server);
let enable_gfx_switching = config.gfx_managed;
let config = Arc::new(Mutex::new(config));
match CtrlRogBios::new(config.clone()) {
Ok(mut ctrl) => {
// Do a reload of any settings
@@ -81,7 +96,7 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
}
}
match CtrlCharge::new(config.clone()) {
match CtrlCharge::new(config) {
Ok(mut ctrl) => {
// Do a reload of any settings
ctrl.reload()
@@ -94,103 +109,92 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
}
}
match CtrlAnimeDisplay::new() {
if Profile::is_platform_profile_supported() {
let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into());
match CtrlPlatformProfile::new(profile_config) {
Ok(mut ctrl) => {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
ProfileZbus::new(tmp.clone()).add_to_server(&mut object_server);
tasks.push(Box::new(CtrlProfileTask::new(tmp)));
}
Err(err) => {
error!("Profile control: {}", err);
}
}
} else {
warn!("platform_profile support not found. This requires kernel 5.15.x or the patch applied: https://lkml.org/lkml/2021/8/18/1022");
}
match CtrlAnime::new(AnimeConfig::load()) {
Ok(ctrl) => {
ctrl.add_to_server(&mut object_server);
let inner = Arc::new(Mutex::new(ctrl));
let mut reload = CtrlAnimeReloader(inner.clone());
reload
.reload()
.unwrap_or_else(|err| warn!("AniMe: {}", err));
let zbus = CtrlAnimeZbus(inner.clone());
zbus.add_to_server(&mut object_server);
tasks.push(Box::new(CtrlAnimeTask::new(inner)));
}
Err(err) => {
error!("AniMe control: {}", err);
}
}
if enable_gfx_switching {
match CtrlGraphics::new(config.clone()) {
Ok(mut ctrl) => {
// Need to check if a laptop has the dedicated gfx switch
if CtrlRogBios::has_dedicated_gfx_toggle() {
if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
if let Ok(vendor) = ctrl.get_gfx_mode() {
if ded == 1 && vendor != GfxVendors::Nvidia {
error!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
error!("You must reboot to enable Nvidia driver");
ctrl.do_vendor_tasks(GfxVendors::Nvidia)?;
} else if ded == 0 {
info!("Dedicated GFX toggle is off");
}
}
}
}
ctrl.reload()
.unwrap_or_else(|err| warn!("Gfx controller: {}", err));
ctrl.add_to_server(&mut object_server);
}
Err(err) => {
error!("Gfx 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 object_server);
let task = CtrlKbdLedTask::new(inner);
tasks.push(Box::new(task));
}
}
// Collect tasks for task thread
let mut tasks: Vec<Arc<Mutex<dyn CtrlTask + Send>>> = Vec::new();
if let Ok(mut ctrl) = CtrlFanAndCPU::new(config.clone()).map_err(|err| {
error!("Profile control: {}", err);
}) {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
DbusFanAndCpu::new(tmp).add_to_server(&mut object_server);
};
if let Some(laptop) = laptop {
if let Ok(ctrl) = CtrlKbdBacklight::new(
laptop.usb_product(),
laptop.condev_iface(),
laptop.supported_modes().to_owned(),
config,
)
.map_err(|err| {
Err(err) => {
error!("Keyboard control: {}", err);
err
}) {
let tmp = Arc::new(Mutex::new(ctrl));
DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server);
tasks.push(tmp);
}
}
// 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()
// Run tasks
let handle = std::thread::Builder::new()
.name("asusd watch".to_string())
.spawn(move || loop {
std::thread::sleep(std::time::Duration::from_millis(100));
for ctrl in tasks.iter() {
if let Ok(mut lock) = ctrl.try_lock() {
lock.do_task()
.map_err(|err| {
warn!("do_task error: {}", err);
})
.ok();
}
ctrl.do_task()
.map_err(|err| {
warn!("do_task error: {}", err);
})
.ok();
}
});
object_server
.with(&"/org/asuslinux/Charge".try_into()?, |obj: &CtrlCharge| {
let x = obj.limit();
obj.notify_charge(x as u8)
})
.map_err(|err| {
warn!("object_server notify_charge error: {}", err);
})
.ok();
// Request dbus name after finishing initalizing all functions
fdo_connection.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
// Loop to check errors and iterate zbus server
loop {
if let Err(err) = &handle {
error!("{}", err);
}
if let Err(err) = object_server.try_handle_next() {
eprintln!("{}", err);
error!("{}", err);
}
}
}

View File

@@ -1,16 +1,11 @@
use intel_pstate::PStateError;
use rog_fan_curve::CurveError;
use rog_types::error::GraphicsError;
use rog_profiles::error::ProfileError;
use std::convert::From;
use std::fmt;
use crate::ctrl_gfx::error::GfxError;
#[derive(Debug)]
pub enum RogError {
ParseFanLevel,
ParseVendor,
ParseLED,
ParseLed,
MissingProfile(String),
Udev(String, std::io::Error),
Path(String, std::io::Error),
@@ -18,23 +13,23 @@ pub enum RogError {
Write(String, std::io::Error),
NotSupported,
NotFound(String),
IntelPstate(PStateError),
FanCurve(CurveError),
DoTask(String),
MissingFunction(String),
MissingLedBrightNode(String, std::io::Error),
ReloadFail(String),
GfxSwitching(GfxError),
Profiles(ProfileError),
Initramfs(String),
Modprobe(String),
Io(std::io::Error),
Zbus(zbus::Error),
}
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::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),
@@ -42,36 +37,42 @@ impl fmt::Display for RogError {
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
RogError::NotSupported => write!(f, "Not supported"),
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
RogError::IntelPstate(err) => write!(f, "Intel pstate error: {}", err),
RogError::FanCurve(err) => write!(f, "Custom fan-curve error: {}", err),
RogError::DoTask(deets) => write!(f, "Task error: {}", deets),
RogError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
RogError::MissingLedBrightNode(path, error) => write!(f, "Led node at {} is missing, please check you have the required patch or dkms module installed: {}", path, error),
RogError::ReloadFail(deets) => write!(f, "Task error: {}", deets),
RogError::GfxSwitching(deets) => write!(f, "Graphics switching error: {}", deets),
RogError::Profiles(deets) => write!(f, "Profile error: {}", deets),
RogError::Initramfs(detail) => write!(f, "Initiramfs error: {}", detail),
RogError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
RogError::Io(detail) => write!(f, "std::io error: {}", detail),
RogError::Zbus(detail) => write!(f, "Zbus error: {}", detail),
}
}
}
impl std::error::Error for RogError {}
impl From<PStateError> for RogError {
fn from(err: PStateError) -> Self {
RogError::IntelPstate(err)
impl From<ProfileError> for RogError {
fn from(err: ProfileError) -> Self {
RogError::Profiles(err)
}
}
impl From<CurveError> for RogError {
fn from(err: CurveError) -> Self {
RogError::FanCurve(err)
impl From<zbus::Error> for RogError {
fn from(err: zbus::Error) -> Self {
RogError::Zbus(err)
}
}
impl From<GraphicsError> for RogError {
fn from(err: GraphicsError) -> Self {
match err {
GraphicsError::ParseVendor => RogError::GfxSwitching(GfxError::ParseVendor),
}
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))
}
}

View File

@@ -1,91 +1,17 @@
use log::{info, warn};
use rog_types::aura_modes::{AuraModes, BREATHING, STATIC};
use rog_aura::AuraModeNum;
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";
static LAPTOP_DEVICES: [u16; 4] = [0x1866, 0x1869, 0x1854, 0x19b6];
#[derive(Debug)]
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().expect("Couldn't get device").iter() {
let device_desc = device
.device_descriptor()
.expect("Couldn't get device descriptor");
if device_desc.vendor_id() == 0x0b05 {
if LAPTOP_DEVICES.contains(&device_desc.product_id()) {
let prod_str = format!("{:x?}", device_desc.product_id());
if device_desc.product_id() == 0x1854 {
let mut laptop = laptop(prod_str, None);
if laptop.supported_modes.is_empty() {
laptop.supported_modes = vec![STATIC, BREATHING];
}
return Some(laptop);
}
let laptop = laptop(prod_str, Some("02".to_owned()));
return Some(laptop);
}
}
}
warn!(
"Unsupported laptop, please request support at {}",
HELP_ADDRESS
);
warn!("Continuing with minimal support");
None
}
fn laptop(prod: String, condev_iface: Option<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 mut laptop = LaptopBase {
usb_product: prod,
condev_iface,
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
}
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_name = dmi.product_name().expect("Could not get product_name");
let prod_family = dmi.product_family().expect("Could not get product_family");
info!("Product name: {}", prod_name.trim());
info!("Product family: {}", prod_family.trim());
info!("Board name: {}", board_name.trim());
}
@@ -94,12 +20,12 @@ 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(&<AuraModes>::from(*mode));
let mode = <&str>::from(&<AuraModeNum>::from(*mode));
info!("- {}", mode);
}
info!(
"If these modes are incorrect or missing please request support at {}",
HELP_ADDRESS
"If these modes are incorrect you can edit {}",
ASUS_LED_MODE_CONF
);
} else {
info!("No RGB control available");
@@ -107,19 +33,50 @@ pub fn print_modes(supported_modes: &[u8]) {
}
#[derive(Debug, Deserialize, Serialize)]
struct LEDModeGroup {
led_modes: Vec<LEDModes>,
struct LedSupportFile {
led_data: Vec<LaptopLedData>,
}
impl LEDModeGroup {
#[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<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);
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);
}
}
}
@@ -128,26 +85,19 @@ impl LEDModeGroup {
}
fn load_from_config() -> Option<Self> {
if let Ok(mut file) = OpenOptions::new().read(true).open(&LEDMODE_CONFIG_PATH) {
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", LEDMODE_CONFIG_PATH);
warn!("{} is empty", ASUS_LED_MODE_CONF);
} else {
return Some(toml::from_str(&buf).unwrap_or_else(|_| {
panic!("Could not deserialise {}", LEDMODE_CONFIG_PATH)
panic!("Could not deserialise {}", ASUS_LED_MODE_CONF)
}));
}
}
}
warn!("Does {} exist?", LEDMODE_CONFIG_PATH);
warn!("Does {} exist?", ASUS_LED_MODE_CONF);
None
}
}
#[derive(Debug, Deserialize, Serialize)]
struct LEDModes {
prod_family: String,
board_names: Vec<String>,
led_modes: Vec<u8>,
}

View File

@@ -1,9 +1,10 @@
#![deny(unused_must_use)]
/// Configuration loading, saving
pub mod config;
pub(crate) mod config_old;
/// Control of AniMe matrix display
pub mod ctrl_anime;
/// Keyboard LED brightness control, RGB, and LED display modes
pub mod ctrl_aura;
/// Control of battery charge level
pub mod ctrl_charge;
/// Control CPU min/max freq and turbo, fan mode, fan curves
@@ -17,11 +18,7 @@ pub mod ctrl_charge;
/// - CPU turbo enable/disable
/// - Fan mode (normal, boost, silent)
/// - Fan min/max RPM curve
pub mod ctrl_fan_cpu;
/// GPU switching and power
pub mod ctrl_gfx;
/// Keyboard LED brightness control, RGB, and LED display modes
pub mod ctrl_leds;
pub mod ctrl_profiles;
/// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode
pub mod ctrl_rog_bios;
/// Laptop matching to determine capabilities
@@ -47,7 +44,7 @@ pub trait ZbusAdd {
}
pub trait CtrlTask {
fn do_task(&mut self) -> Result<(), RogError>;
fn do_task(&self) -> Result<(), RogError>;
}
pub trait CtrlTaskComplex {

View File

@@ -1,7 +0,0 @@
# Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
# Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"

View File

@@ -1,4 +0,0 @@
Section "ServerLayout"
Identifier "layout"
Option "AllowNVIDIAGPUScreens"
EndSection

View File

@@ -3,6 +3,7 @@ Description=ASUS Notebook Control
After=basic.target syslog.target
[Service]
Environment=IS_SERVICE=1
ExecStart=/usr/bin/asusd
Restart=on-failure
Type=dbus

View File

@@ -1,59 +1,133 @@
[[led_modes]]
[[led_data]]
prod_family = "Zephyrus S"
board_names = ["GX502", "GX701", "G531", "GL531", "G532"]
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 255]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = false
per_key = true
[[led_modes]]
[[led_data]]
prod_family = "Zephyrus M"
board_names = ["GU502GV"]
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 255]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = false
per_key = true
[[led_modes]]
[[led_data]]
prod_family = "Zephyrus M"
board_names = ["GM501GS"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
multizone = true
per_key = false
[[led_data]]
prod_family = "ROG Zephyrus M15"
board_names = ["GU502LW"]
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 255]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = false
per_key = true
[[led_modes]]
[[led_data]]
prod_family = "ROG Zephyrus M15"
board_names = ["GU502LU"]
led_modes = [0, 1, 2, 10]
standard = ["Static", "Breathe", "Strobe", "Pulse"]
multizone = false
per_key = false
[[led_modes]]
[[led_data]]
prod_family = "Zephyrus"
board_names = ["GM501GM", "GX531"]
led_modes = [0, 1, 2, 3, 10, 13]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
multizone = true
per_key = false
[[led_modes]]
[[led_data]]
prod_family = "ROG Strix"
board_names = ["G531GW"]
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 255]
board_names = ["G531GW", "G533QR", "G533QS", "G733QS", "G733QR", "G713QR"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = false
per_key = true
[[led_modes]]
[[led_data]]
prod_family = "ROG Strix"
board_names = ["GX531", "G512LV", "G712LV"]
led_modes = [0, 1, 2, 3, 10, 13, 14]
board_names = ["GX531", "G512LV", "G712LV", "G712LW", "G513IH", "G513QY"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
multizone = true
per_key = false
[[led_modes]]
[[led_data]]
prod_family = "ROG Strix"
board_names = ["G512LI", "G712LI", "G531GD"]
led_modes = [0, 1, 2, 3, 10]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
multizone = false
per_key = false
[[led_modes]]
[[led_data]]
prod_family = "Strix"
board_names = ["G731GV", "G731GW", "G531GV"]
led_modes = [0, 1, 2, 3, 13, 14]
standard = ["Static", "Breathe", "Strobe", "Rainbow"]
multizone = true
per_key = false
[[led_modes]]
[[led_data]]
prod_family = "Strix"
board_names = ["G731GT", "G731GU", "G531GT", "G531GU"]
led_modes = [0, 1, 2, 3]
standard = ["Static", "Breathe", "Strobe", "Rainbow"]
multizone = false
per_key = false
[[led_modes]]
[[led_data]]
prod_family = "Strix Scar"
board_names = ["G531", "G731"]
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 255]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = true
per_key = true
[[led_modes]]
[[led_data]]
prod_family = "ROG"
board_names = ["GL553VE"]
led_modes = [0, 1, 2, 13, 14]
standard = ["Static", "Breathe", "Strobe"]
multizone = true
per_key = false
[[led_data]]
prod_family = "ROG Zephyrus G14"
board_names = ["GA401Q"]
standard = ["Static", "Breathe", "Pulse"]
multizone = false
per_key = false
# GA503QE at higher priority (first match) than GA503Q
[[led_data]]
prod_family = "ROG Zephyrus G15"
board_names = ["GA503QE"]
standard = ["Static", "Breathe", "Pulse"]
multizone = false
per_key = false
[[led_data]]
prod_family = "ROG Zephyrus G15"
board_names = ["GA503Q"]
standard = ["Static", "Breathe", "Pulse", "Rainbow", "Strobe"]
multizone = false
per_key = false
[[led_data]]
prod_family = "ROG Zephyrus"
board_names = ["GX550L"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = false
per_key = true
[[led_data]]
prod_family = "ROG Zephyrus Duo 15 SE"
board_names = ["GX551Q"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
multizone = false
per_key = true
[[led_data]]
prod_family = "ROG Flow X13"
board_names = ["GV301QH", "GV301QE"]
standard = ["Static", "Breathe", "Pulse"]
multizone = false
per_key = false

14
data/asusd-user.service Normal file
View File

@@ -0,0 +1,14 @@
[Unit]
Description=ASUS User Daemon
StartLimitInterval=200
StartLimitBurst=2
[Service]
ExecStartPre=/usr/bin/sleep 2
ExecStart=/usr/bin/asusd-user
Restart=on-failure
RestartSec=1
Type=simple
[Install]
WantedBy=default.target

View File

@@ -2,12 +2,16 @@
Description=ASUS Notebook Control
StartLimitInterval=200
StartLimitBurst=2
Before=multi-user.target
[Service]
Environment=IS_SERVICE=1
ExecStartPre=/bin/sleep 2
ExecStart=/usr/bin/asusd
Restart=on-failure
Restart=always
RestartSec=1
Type=dbus
BusName=org.asuslinux.Daemon
Before=display-manager.service
SELinuxContext=system_u:system_r:unconfined_t:s0
#SELinuxContext=system_u:object_r:modules_object_t:s0

View File

@@ -0,0 +1,77 @@
# Author: AlenPaulVarghese <alenpaul2001@gmail.com>
set -l progname asusctl
set -l noopt "not __fish_contains_opt -s -s h -s v -s s -s k -s f -s c help version show-supported kbd-bright fan-mode chg-limit; and not __fish_seen_subcommand_from led-mode profile graphics;"
set -l gmod_options '__fish_contains_opt -s m mode;'
set -l fan_options '__fish_contains_opt -s f fan-mode;'
set -l led_options '__fish_seen_subcommand_from led-mode;'
set -l profile_options '__fish_seen_subcommand_from profile;'
set -l keyboard_options '__fish_contains_opt -s k kbd-bright;'
set -l graphics_options '__fish_seen_subcommand_from graphics;'
set -l fan_modes 'silent normal boost'
set -l brightness_modes 'off low med high'
set -l led_modes 'static breathe strobe rainbow comet'
set -l graphics_modes 'nvidia hybird compute integrated'
complete -c $progname -e
complete -c $progname -f
# asusctl completion
complete -c $progname -s h -f -l help -n "$noopt" -d "print help message"
complete -c $progname -s v -f -l version -n "$noopt" -d "show program version number"
complete -c $progname -s s -f -l show-supported -n "$noopt" -d "show supported functions of this laptop"
complete -c $progname -s k -f -l kbd-bright -n "$noopt" -d "set led brightness"
complete -c $progname -s f -f -l fan-mode -n "$noopt" -d "set fan mode independent of profile"
complete -c $progname -s c -f -l chg-limit -n "$noopt" -d "set charge limit <20-100>"
complete -c $progname -f -a "led-mode" -n "$noopt" -d "Set the keyboard lighting from built-in modes"
complete -c $progname -f -a "profile" -n "$noopt" -d "Create and configure profiles"
complete -c $progname -f -a "graphics" -n "$noopt" -d "Set the graphics mode"
# brightness completion
complete -c $progname -n "$keyboard_options" -d "available brightness modes" -a "$brightness_modes"
# fan completion
complete -c $progname -n "$fan_options" -d "available fan modes" -a $fan_modes
# graphics completion
set -l gopt 'not __fish_contains_opt -s h -s g -s m -s p help mode get pow;'
complete -c $progname -n "$graphics_options and $gopt" -a "-h" -d "print help message"
complete -c $progname -n "$graphics_options and $gopt" -a "-g" -d "Get the current mode"
complete -c $progname -s h -f -l help -n "$graphics_options and $gopt" -d "print help message"
complete -c $progname -s m -f -l mode -n "$graphics_options and $gopt" -d "Set graphics mode: <nvidia, hybrid, compute, integrated>"
complete -c $progname -s g -f -l get -n "$graphics_options and $gopt" -d "Get the current mode"
complete -c $progname -s p -f -l pow -n "$graphics_options and $gopt" -d "Get the current power status"
complete -c $progname -n "$graphics_options and $gmod_options" -d "available graphics modes" -a "$graphics_modes"
# led-mode completion
complete -c $progname -n "$led_options" -a "-h" -d "print help message"
complete -c $progname -n "$led_options" -a "-n" -d "switch to next aura mode"
complete -c $progname -s h -f -l help -n "$led_options" -d "print help message"
complete -c $progname -s n -f -l next-mode -n "$led_options" -d "switch to nex aura mode"
complete -c $progname -s p -f -l prev-mode -n "$led_options" -d "switch to previous aura mode"
complete -c $progname -n "$led_options" -d "available led modes" -a "$led_modes"
# profile completion
set -l popt 'not __fish_contains_opt -s h -s n -s c -s t -s m -s M -s f help next create turbo min-percentage max-percentage fan-preset;'
complete -c $progname -n "$profile_options and $popt" -a "-h" -d "print help message"
complete -c $progname -n "$profile_options and $popt" -a "-n" -d "toggle to next profile in list"
complete -c $progname -s h -f -l help -n "$profile_options and $popt" -d "print help message"
complete -c $progname -s n -f -l next -n "$profile_options and $popt" -d "toggle to next profile in list"
complete -c $progname -s c -f -l create -n "$profile_options and $popt" -d "create the profile if it doesn't exist"
complete -c $progname -s t -f -l turbo -n "$profile_options and $popt" -d "enable or disable cpu turbo"
complete -c $progname -s m -f -l min-percentage -n "$profile_options and $popt" -d "set min cpu scaling (intel)"
complete -c $progname -s M -f -l max-percentage -n "$profile_options and $popt" -d "set max cpu scaling (intel)"
complete -c $progname -s f -f -l fan-preset -n "$profile_options and $popt" -d "<silent, normal, boost>"
complete -c $progname -n "$profile_option and __fish_contains_opt fan-preset" -d "available fan modes" -a $fan_modes

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Gravit.io -->
<svg
style="isolation:isolate"
viewBox="0 0 82.733002 82.733002"
width="82.733002pt"
height="82.733002pt"
version="1.1"
id="svg19"
sodipodi:docname="gpu-compute.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview21"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="pt"
showgrid="false"
inkscape:zoom="4.96875"
inkscape:cx="15.597484"
inkscape:cy="54.742138"
inkscape:window-width="2048"
inkscape:window-height="1083"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg19" />
<defs
id="defs5">
<clipPath
id="_clipPath_ZEBBDq6ZtbLgkRXocTHUWTcRbcURZIva">
<rect
width="128"
height="128"
id="rect2"
x="0"
y="0" />
</clipPath>
</defs>
<g
clip-path="url(#_clipPath_ZEBBDq6ZtbLgkRXocTHUWTcRbcURZIva)"
id="g17"
transform="translate(-22.624,-23)">
<rect
width="128"
height="128"
style="fill:#000000"
fill-opacity="0"
id="rect7"
x="0"
y="0" />
<g
id="g15">
<path
id="path9"
style="opacity:1;mix-blend-mode:normal;fill:#ed1c24;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.789062,13.789062 v 6.894532 H 0 v 6.894531 H 13.789062 V 41.367188 H 0 v 6.894531 H 13.789062 V 62.048828 H 0 v 6.894531 H 13.789062 V 82.732422 H 0 v 6.894531 h 13.789062 v 6.894531 h 6.894532 v 13.789066 h 6.894531 V 96.521484 h 13.789063 v 13.789066 h 6.894531 V 96.521484 h 13.789062 v 13.789066 h 6.892578 V 96.521484 h 13.789063 v 13.789066 h 6.896484 V 96.521484 h 6.894532 z"
transform="matrix(0.75,0,0,0.75,22.624,23)" />
<path
id="path9-3"
style="isolation:isolate;mix-blend-mode:normal;fill:#76b900;fill-opacity:1;stroke:#000000;stroke-width:0.375;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 95.016578,95.391113 -0.0051,-5.170896 10.341782,-0.01017 -0.005,-5.170896 -10.341789,0.01017 -0.01017,-10.341791 10.341799,-0.01018 -0.005,-5.170895 -10.341787,0.01018 -0.01017,-10.340328 10.341797,-0.01017 -0.005,-5.170896 -10.341794,0.01017 -0.01018,-10.341792 10.341784,-0.01017 -0.005,-5.170895 -10.341791,0.01017 -0.0051,-5.170897 -5.170898,0.0051 -0.01017,-10.341796 -5.170896,0.0051 0.01017,10.341797 -10.341792,0.01017 -0.01017,-10.341795 -5.170897,0.0051 0.01017,10.341796 -10.341791,0.01017 -0.01017,-10.341796 -5.169431,0.0051 0.01017,10.341795 -10.341792,0.01018 -0.01017,-10.341797 -5.172361,0.0051 0.01017,10.341795 -5.170894,0.0051 z" />
<rect
x="38.146"
y="38.512001"
width="51.708"
height="51.708"
fill="rgb(77,77,77)"
id="rect11"
style="fill:none" />
<path
d="m 67.238,81.543 c 3.416,0 7.169,-0.866 10.056,-2.262 l -1.395,-4.474 c -2.165,0.818 -4.86,1.299 -7.169,1.299 -7.313,0 -11.884,-4.811 -11.884,-12.317 0,-7.073 4.138,-11.114 11.162,-11.114 2.646,0 5.678,0.529 7.843,1.395 l 1.732,-4.811 c -2.839,-1.347 -6.111,-2.069 -9.43,-2.069 -10.537,0 -17.754,6.976 -17.754,17.465 0,10.104 6.832,16.888 16.839,16.888 z"
fill="#dddddd"
id="path13"
style="stroke:#000000;stroke-opacity:1;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;fill:#ffffff" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Gravit.io -->
<svg
style="isolation:isolate"
viewBox="0 0 82.733002 82.733002"
width="82.733002pt"
height="82.733002pt"
version="1.1"
id="svg19"
sodipodi:docname="gpu-hybrid.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview21"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="pt"
showgrid="false"
inkscape:zoom="4.96875"
inkscape:cx="15.798742"
inkscape:cy="54.742138"
inkscape:window-width="2048"
inkscape:window-height="1083"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg19" />
<defs
id="defs5">
<clipPath
id="_clipPath_uVSrFPVusaaYDISWhfCjIlGBeb9FaZAr">
<rect
width="128"
height="128"
id="rect2"
x="0"
y="0" />
</clipPath>
</defs>
<g
clip-path="url(#_clipPath_uVSrFPVusaaYDISWhfCjIlGBeb9FaZAr)"
id="g17"
transform="translate(-22.624,-23)">
<rect
width="128"
height="128"
style="fill:#000000"
fill-opacity="0"
id="rect7"
x="0"
y="0" />
<g
id="g15">
<path
d="m 38.137,23 v 10.342 h -5.171 v 5.17 H 22.624 v 5.171 H 32.966 V 54.025 H 22.624 v 5.171 H 32.966 V 69.537 H 22.624 v 5.171 H 32.966 V 85.05 H 22.624 v 5.17 h 10.342 v 5.171 h 5.171 v 10.342 h 5.171 V 95.391 h 10.341 v 10.342 H 58.82 V 95.391 h 10.342 v 10.342 h 5.17 V 95.391 h 10.342 v 10.342 h 5.171 V 95.391 h 5.171 V 90.22 h 10.341 V 85.05 H 95.016 V 74.708 h 10.341 V 69.537 H 95.016 V 59.196 h 10.341 V 54.025 H 95.016 V 43.683 h 10.341 V 38.512 H 95.016 v -5.17 H 89.845 V 23 H 84.674 V 33.342 H 74.332 V 23 h -5.17 V 33.342 H 58.82 V 23 H 53.649 V 33.342 H 43.308 V 23 Z"
fill="rgb(77,77,77)"
id="path9"
style="fill:#ed1c24;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.75;stroke-miterlimit:4;stroke-dasharray:none" />
<rect
x="38.146"
y="38.512001"
width="51.708"
height="51.708"
fill="rgb(77,77,77)"
id="rect11"
style="fill:#76b900;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
<path
d="m 51.464,79 h 5.663 V 66.118 H 70.916 V 79 h 5.62 V 49 h -5.62 V 61.147 H 57.127 V 49 h -5.663 z"
fill="rgb(221,221,221)"
id="path13"
style="fill:#ececec;stroke:#000000;stroke-opacity:1;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Gravit.io -->
<svg
style="isolation:isolate"
viewBox="0 0 82.410369 82.410377"
width="82.41037pt"
height="82.410378pt"
version="1.1"
id="svg17"
sodipodi:docname="gpu-integrated.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview19"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="pt"
showgrid="false"
inkscape:zoom="4.96875"
inkscape:cx="14.389937"
inkscape:cy="54.742138"
inkscape:window-width="2048"
inkscape:window-height="1083"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg17" />
<defs
id="defs5">
<clipPath
id="_clipPath_GAg6mcrkdqJgsmZOjvo6JT6R1d3r9FoI">
<rect
width="128"
height="128"
id="rect2"
x="0"
y="0" />
</clipPath>
</defs>
<path
style="fill:#ed1c24;fill-opacity:1;stroke:#000000;stroke-width:0.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 15.92217,76.9033 V 71.771223 H 13.28066 10.63915 V 69.129715 66.488208 H 5.507075 0.375 v -2.037735 -2.037735 h 5.132075 5.132075 v -5.73585 -5.73585 H 5.507075 0.375 V 48.903302 46.865566 H 5.507075 10.63915 V 41.205189 35.544811 H 5.507075 0.375 V 33.431604 31.318396 H 5.507075 10.63915 V 25.658019 19.997641 H 5.507075 0.375 V 17.959906 15.92217 h 5.132075 5.132075 v -2.641509 -2.64151 h 2.64151 2.64151 V 5.5070763 0.375 h 2.037736 2.037735 v 5.1320763 5.1320747 h 5.660378 5.660377 V 5.5070763 0.375 h 2.113207 2.113208 v 5.1320763 5.1320747 h 5.660377 5.660378 V 5.5070763 0.375 h 1.947066 1.947063 l 0.09711,1.8490563 c 0.07996,1.52246 0.251242,6.717377 0.253724,7.695141 0.0014,0.5274167 -0.04609,0.5197837 3.905982,0.6286047 1.618867,0.04457 3.945285,0.123799 5.16981,0.176053 l 2.226412,0.09501 V 5.5969303 0.375 h 2.037735 2.037743 v 5.1320763 5.1320747 h 2.641507 2.641508 v 2.64151 2.641509 h 5.132077 5.13207 v 2.037736 2.037735 h -5.13207 -5.132077 v 5.660378 5.660377 h 5.132077 5.13207 v 2.113208 2.113207 h -5.13207 -5.132077 v 5.660378 5.660377 h 5.132077 5.13207 v 2.037736 2.037736 h -5.13207 -5.132077 v 5.73585 5.73585 h 5.132077 5.13207 v 2.037735 2.037735 h -5.13207 -5.132077 v 2.641507 2.641508 H 69.129718 66.488211 V 76.9033 82.035378 H 64.450468 62.412733 V 76.9033 71.771223 h -5.73585 -5.735846 V 76.9033 82.035378 H 48.903301 46.865566 V 76.9033 71.771223 H 41.205188 35.544811 V 76.9033 82.035378 H 33.431603 31.318396 V 76.9033 71.771223 H 25.658019 19.997641 V 76.9033 82.035378 H 17.959906 15.92217 Z"
id="path1355" />
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Gravit.io -->
<svg
style="isolation:isolate"
viewBox="0 0 82.410369 82.410377"
width="82.41037pt"
height="82.410378pt"
version="1.1"
id="svg17"
sodipodi:docname="gpu-nvidia.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview19"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="pt"
showgrid="false"
inkscape:zoom="4.96875"
inkscape:cx="14.389937"
inkscape:cy="54.742138"
inkscape:window-width="2048"
inkscape:window-height="1083"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg17" />
<defs
id="defs5">
<clipPath
id="_clipPath_GAg6mcrkdqJgsmZOjvo6JT6R1d3r9FoI">
<rect
width="128"
height="128"
id="rect2"
x="0"
y="0" />
</clipPath>
</defs>
<path
style="fill:#76b900;fill-opacity:1;stroke:#000000;stroke-width:0.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 15.92217,76.9033 V 71.771223 H 13.28066 10.63915 V 69.129715 66.488208 H 5.507075 0.375 v -2.037735 -2.037735 h 5.132075 5.132075 v -5.73585 -5.73585 H 5.507075 0.375 V 48.903302 46.865566 H 5.507075 10.63915 V 41.205189 35.544811 H 5.507075 0.375 V 33.431604 31.318396 H 5.507075 10.63915 V 25.658019 19.997641 H 5.507075 0.375 V 17.959906 15.92217 h 5.132075 5.132075 v -2.641509 -2.64151 h 2.64151 2.64151 V 5.5070763 0.375 h 2.037736 2.037735 v 5.1320763 5.1320747 h 5.660378 5.660377 V 5.5070763 0.375 h 2.113207 2.113208 v 5.1320763 5.1320747 h 5.660377 5.660378 V 5.5070763 0.375 h 1.947066 1.947063 l 0.09711,1.8490563 c 0.07996,1.52246 0.251242,6.717377 0.253724,7.695141 0.0014,0.5274167 -0.04609,0.5197837 3.905982,0.6286047 1.618867,0.04457 3.945285,0.123799 5.16981,0.176053 l 2.226412,0.09501 V 5.5969303 0.375 h 2.037735 2.037743 v 5.1320763 5.1320747 h 2.641507 2.641508 v 2.64151 2.641509 h 5.132077 5.13207 v 2.037736 2.037735 h -5.13207 -5.132077 v 5.660378 5.660377 h 5.132077 5.13207 v 2.113208 2.113207 h -5.13207 -5.132077 v 5.660378 5.660377 h 5.132077 5.13207 v 2.037736 2.037736 h -5.13207 -5.132077 v 5.73585 5.73585 h 5.132077 5.13207 v 2.037735 2.037735 h -5.13207 -5.132077 v 2.641507 2.641508 H 69.129718 66.488211 V 76.9033 82.035378 H 64.450468 62.412733 V 76.9033 71.771223 h -5.73585 -5.735846 V 76.9033 82.035378 H 48.903301 46.865566 V 76.9033 71.771223 H 41.205188 35.544811 V 76.9033 82.035378 H 33.431603 31.318396 V 76.9033 71.771223 H 25.658019 19.997641 V 76.9033 82.035378 H 17.959906 15.92217 Z"
id="path1355" />
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Gravit.io -->
<svg
style="isolation:isolate"
viewBox="0 0 82.733002 82.733002"
width="82.733002pt"
height="82.733002pt"
version="1.1"
id="svg19"
sodipodi:docname="gpu-vfio.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview21"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="pt"
showgrid="false"
inkscape:zoom="7.8686859"
inkscape:cx="55.091791"
inkscape:cy="55.155334"
inkscape:window-width="2048"
inkscape:window-height="1083"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg19" />
<defs
id="defs5">
<clipPath
id="_clipPath_ZEBBDq6ZtbLgkRXocTHUWTcRbcURZIva">
<rect
width="128"
height="128"
id="rect2"
x="0"
y="0" />
</clipPath>
</defs>
<g
clip-path="url(#_clipPath_ZEBBDq6ZtbLgkRXocTHUWTcRbcURZIva)"
id="g17"
transform="translate(-22.624,-23)">
<rect
width="128"
height="128"
style="fill:#000000"
fill-opacity="0"
id="rect7"
x="0"
y="0" />
<g
id="g15">
<path
id="path9"
style="opacity:1;mix-blend-mode:normal;fill:#ed1c24;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.789062,13.789062 v 6.894532 H 0 v 6.894531 H 13.789062 V 41.367188 H 0 v 6.894531 H 13.789062 V 62.048828 H 0 v 6.894531 H 13.789062 V 82.732422 H 0 v 6.894531 h 13.789062 v 6.894531 h 6.894532 v 13.789066 h 6.894531 V 96.521484 h 13.789063 v 13.789066 h 6.894531 V 96.521484 h 13.789062 v 13.789066 h 6.892578 V 96.521484 h 13.789063 v 13.789066 h 6.896484 V 96.521484 h 6.894532 z"
transform="matrix(0.75,0,0,0.75,22.624,23)" />
<path
id="path9-3"
style="isolation:isolate;mix-blend-mode:normal;fill:#76b900;fill-opacity:1;stroke:#000000;stroke-width:0.375;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 95.016578,95.391113 -0.0051,-5.170896 10.341782,-0.01017 -0.005,-5.170896 -10.341789,0.01017 -0.01017,-10.341791 10.341799,-0.01018 -0.005,-5.170895 -10.341787,0.01018 -0.01017,-10.340328 10.341797,-0.01017 -0.005,-5.170896 -10.341794,0.01017 -0.01018,-10.341792 10.341784,-0.01017 -0.005,-5.170895 -10.341791,0.01017 -0.0051,-5.170897 -5.170898,0.0051 -0.01017,-10.341796 -5.170896,0.0051 0.01017,10.341797 -10.341792,0.01017 -0.01017,-10.341795 -5.170897,0.0051 0.01017,10.341796 -10.341791,0.01017 -0.01017,-10.341796 -5.169431,0.0051 0.01017,10.341795 -10.341792,0.01018 -0.01017,-10.341797 -5.172361,0.0051 0.01017,10.341795 -5.170894,0.0051 z" />
<rect
x="38.146"
y="38.512001"
width="51.708"
height="51.708"
fill="rgb(77,77,77)"
id="rect11"
style="fill:none" />
</g>
</g>
<path
d="m 38.016104,57.595764 h 6.037 l 11.263,-31.267 h -5.947 l -8.064,24.374 -7.75,-24.374 h -6.758 z"
fill="#dddddd"
id="path849"
style="isolation:isolate;stroke:#000000;stroke-opacity:1;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="141 297.96 507.339 471.04" width="507.339pt" height="471.04pt"><g><g><g><g><path d=" M 537.075 443.463 L 537.075 443.463 L 564.723 427.335 C 504.903 324.571 375.966 285.313 269.027 337.304 L 269.027 297.96 L 237.027 297.96 L 237.027 393.96 L 333.027 393.96 L 333.027 361.96 L 292.147 361.96 C 382.665 323.563 487.612 358.486 537.075 443.463 Z " fill="rgb(204,64,64)"/><path d=" M 245.027 692.775 C 170.84 632.524 151.314 527.353 198.931 444.488 L 171.187 428.488 C 116.876 523.063 137.426 642.887 220.147 713.96 L 181.027 713.96 L 181.027 745.96 L 277.027 745.96 L 277.027 649.96 L 245.027 649.96 L 245.027 692.775 L 245.027 692.775 Z " fill="rgb(204,64,64)"/><path d=" M 580.451 499.048 L 512.579 566.936 L 535.203 589.56 L 562.403 562.36 C 551.734 661.49 468.201 736.724 368.499 737 L 368.499 769 C 487.289 768.688 585.833 677.011 594.707 558.552 L 625.715 589.56 L 648.339 566.936 L 580.451 499.048 Z " fill="rgb(204,64,64)"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

89
design-patterns.md Normal file
View File

@@ -0,0 +1,89 @@
# Daemon
## Controller pattern
There are a series of traits in the daemon for use with controller objects. Not all traits are required:
- `Reloadable`, for controllers that need the ability to reload (typically on start)
- `ZbusAdd`, for controllers that have zbus derive. These need to run on the zbus server.
- `CtrlTask`, for controllers that need to run tasks every loop.
- `GetSupported`, see if the hardware/functions this controller requires are supported.
The first 3 trait objects get owned by the daemon methods that required them, which is why an `Arc<Mutex<T>>` is required.
Generally the actual controller object will need to live in its own world as its own struct.
Then for each trait that is required a new struct is required that can have the trait implemented, and that struct would have a reference to the main controller via `Arc<Mutex<T>>`.
### Example
Main controller:
```rust
pub struct CtrlAnime {
<things the controller requires>
}
impl CtrlAnime {
<functions the controller exposes>
}
```
The task trait:
```rust
pub struct CtrlAnimeTask(Arc<Mutex<CtrlAnime>>);
impl crate::CtrlTask for CtrlAnimeTask {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(lock) = self.inner.try_lock() {
<some action>
}
Ok(())
}
}
```
The reloader trait
```rust
pub struct CtrlAnimeReloader(Arc<Mutex<CtrlAnime>>);
impl crate::Reloadable for CtrlAnimeReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(lock) = self.inner.try_lock() {
<some action>
}
Ok(())
}
}
```
The Zbus requirements:
```rust
pub struct CtrlAnimeZbus(Arc<Mutex<CtrlAnime>>);
impl crate::ZbusAdd for CtrlAnimeZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnimeZbus {
fn <zbus method>() {
if let Ok(lock) = self.inner.try_lock() {
<some action>
}
}
}
```
The controller can then be added to the daemon parts as required.

30
rog-anime/Cargo.toml Normal file
View File

@@ -0,0 +1,30 @@
[package]
name = "rog_anime"
version = "1.1.0"
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"
documentation = "https://docs.rs/rog-anime"
description = "Types useful for translating images and other data for display on the ASUS AniMe Matrix display"
keywords = ["ROG", "ASUS", "AniMe"]
edition = "2018"
exclude = ["data"]
[features]
default = ["dbus"]
dbus = ["zvariant", "zvariant_derive"]
[dependencies]
png_pong = "^0.8.0"
pix = "0.13"
gif = "^0.11.2"
serde = "^1.0"
serde_derive = "^1.0"
glam = { version = "0.14.0", features = ["serde"] }
zvariant = { version = "^2.6", optional = true }
zvariant_derive = { version = "^2.6", optional = true }

373
rog-anime/LICENSE Normal file
View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

97
rog-anime/README.md Normal file
View File

@@ -0,0 +1,97 @@
# rog-anime
`rog-anime` is a crate for use with ASUS laptops that have an AniMe matrix display built in to them. The crate can be used with zbus to communicate with the `asusd` daemon from the project this crate is part of, or it can be used standalone to write the data directly to USB by transforming the data to USB HID packets using builtin functions.
Supported so far is:
- Sequences of data,
- Data can be:
+ Image<scale, rotate, position>
+ ASUS style gif
+ Plain image type gif
+ Pause
- Create USB HID packets for writing to the device
This crate is mostly purpose built for use with [`asus-nb-ctrl`](https://gitlab.com/asus-linux/asus-nb-ctrl) which is a complete daemon and toolset for Linux on ASUS ROG/TUF machines, but can be used in general for example building a new controller for Windows OS.
## Feature enablement
`dbus` is enabled by default - this uses `zvariant` to enable sending some types over dbus interfaces.
## Example
```rust
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::AuraDbusClient;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = AuraDbusClient::new().unwrap();
let mut image = AniMeImage::from_png(
Path::new("./doom.png"),
0.9, // scale
0.0, // rotation
Vec2::new(0.0, 0.0), // position
0.3, // brightness
)?;
loop {
image.angle += 0.05;
if image.angle > PI * 2.0 {
image.angle = 0.0
}
image.update();
client
.proxies()
.anime()
.write(<AniMeDataBuffer>::from(&image))
.unwrap();
sleep(Duration::from_micros(500));
}
}
```
## Example, USB HID
```rust
let mut image = AniMeImage::from_png(
Path::new("./doom.png"),
0.9, // scale
0.0, // rotation
Vec2::new(0.0, 0.0), // position
0.3, // brightness
)?;
// convert to intermediate packet format
let buffer = <AniMeDataBuffer>::from(&image)
// then to USB HID
let data = AniMePacketType::from(buffer);
// and then write direct
for packet in data.iter() {
write_usb(packet); // some usb call here
}
```
## data
- `data/controller.gif` is an example ASUS diagonally orientated gif.
- `data/diagonal-template.*` are templates for diagonal images or gifs.
See https://blog.joshwalsh.me/asus-anime-matrix/ for details on how
the diagonal layout works.
`diagonal-template.*` is provided from the website above. It is best to
export the final file to 36px height - no scaling is done in asusd or
rog-anime crate for diagonal displays.
## TODO:
- Diagonal font and text
- General font and text
- System info for Linux and Windows
- Audio EQ visual for Linux and Windows
- Time+Date display

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

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