Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93d472fe74 | ||
|
|
5469c73f11 | ||
|
|
ad95765954 | ||
|
|
e42a5bc3e9 | ||
|
|
28347e87eb | ||
|
|
b34cb672c3 | ||
|
|
559ddc9a22 | ||
|
|
a8c014881f | ||
|
|
f417032ed9 | ||
|
|
616fb3aea6 | ||
|
|
6e6e057995 | ||
|
|
085e63ebab | ||
|
|
fdadffcdde | ||
|
|
5f51527dd7 | ||
|
|
39525980a0 | ||
|
|
83a0b570e0 | ||
|
|
2bfbce36b0 | ||
|
|
2705b08dca | ||
|
|
2fca7a09c4 | ||
|
|
ef0da62c55 | ||
|
|
9dab120bcf | ||
|
|
14bf07ba79 | ||
|
|
e76d01eaed | ||
|
|
072a066f28 | ||
|
|
165c6f8ab3 | ||
|
|
5728a9af62 | ||
|
|
17a880b2c5 | ||
|
|
6ba9b9d75d | ||
|
|
eb1f6c83ce | ||
|
|
af653ea405 | ||
|
|
bc13891cdf | ||
|
|
aad4dc909e | ||
|
|
ad79adcbfa | ||
|
|
73b1a7050a | ||
|
|
762bfea102 | ||
|
|
b41fdf5cfe | ||
|
|
bf13ebebd3 | ||
|
|
3a73e3a526 | ||
|
|
1211400d7b | ||
|
|
ff9edb9876 | ||
|
|
20f8251dd3 | ||
|
|
902dfed67c | ||
|
|
5e08b4416d | ||
|
|
44c3ab7294 | ||
|
|
be40474f79 | ||
|
|
3654da2ff8 | ||
|
|
60bbad4ab6 | ||
|
|
0a59d5c041 | ||
|
|
1506fc44d3 | ||
|
|
aad31610f2 | ||
|
|
a6fe7645e9 | ||
|
|
4f8745ae19 | ||
|
|
5f4e950819 | ||
|
|
efc752cce6 | ||
|
|
37553a5fdd | ||
|
|
cd5a85a843 | ||
|
|
7385844a9b | ||
|
|
0b71104833 | ||
|
|
688e3a7358 | ||
|
|
58ff566d65 | ||
|
|
1332ac803c | ||
|
|
ba1d3f045d | ||
|
|
e0ed52092a | ||
|
|
921637f979 | ||
|
|
f6498337fe | ||
|
|
3a640a3269 | ||
|
|
e938f1f9ec | ||
|
|
600d0ae3d9 | ||
|
|
8569edf684 | ||
|
|
52af4ad2b2 | ||
|
|
cde1b4f252 | ||
|
|
2a4754cfc4 | ||
|
|
51c97fa350 | ||
|
|
c968dce009 | ||
|
|
b2b6707f2e | ||
|
|
7939b00aa3 | ||
|
|
30550aaa91 | ||
|
|
7ea1f41286 | ||
|
|
9608d190b9 | ||
|
|
3b9cf474a7 | ||
|
|
283cb7e589 | ||
|
|
5d87747d96 | ||
|
|
56285916cd | ||
|
|
a44a1bfa89 | ||
|
|
0c97cf710d | ||
|
|
62c7338b2d | ||
|
|
af24623178 | ||
|
|
facb7f7f49 | ||
|
|
e38ab624e9 | ||
|
|
d76cb3b95a | ||
|
|
910f529a9b | ||
|
|
7583d070d3 | ||
|
|
1f85e30e42 | ||
|
|
d1bdf4dc7e | ||
|
|
79b108ceb7 | ||
|
|
7d14e8d900 | ||
|
|
493d61cf19 | ||
|
|
fb08d83999 | ||
|
|
71241b7127 | ||
|
|
09963534d8 | ||
|
|
64322044ac | ||
|
|
1a132d847f | ||
|
|
952a974e83 | ||
|
|
ebbfa58a76 | ||
|
|
414d610bd2 | ||
|
|
bff98ddf7b | ||
|
|
97481cd45e | ||
|
|
4f39c01139 | ||
|
|
40987ecd5d | ||
|
|
f378c54815 | ||
|
|
a8a99ac1d1 | ||
|
|
503aa20257 | ||
|
|
8c67836650 | ||
|
|
3fc839820e | ||
|
|
0ef524a94b | ||
|
|
f8cfacda47 | ||
|
|
f3876100ae | ||
|
|
fa1feaf9d9 | ||
|
|
45641c928d | ||
|
|
eba9dc8a52 | ||
|
|
a32527d1df | ||
|
|
1f697b5ff1 | ||
|
|
92009ef96c | ||
|
|
3fe5896596 | ||
|
|
f8cdde2adf | ||
|
|
033f2141ef | ||
|
|
f86bab6f8c | ||
|
|
4951bce961 | ||
|
|
fb92d65fa0 | ||
|
|
24fa075a44 | ||
|
|
a0f7cf3acd | ||
|
|
d35707f2e4 | ||
|
|
b20bde8116 | ||
|
|
308fba9413 | ||
|
|
45268bfb2b | ||
|
|
004982cea7 | ||
|
|
5ab24a624e | ||
|
|
700633e080 | ||
|
|
773c9902a5 | ||
|
|
e05d5bd143 | ||
|
|
3e244d7d3d | ||
|
|
ba4589f986 | ||
|
|
083134fc73 | ||
|
|
3cc04fba60 | ||
|
|
3a00e4f1a3 | ||
|
|
eb78fb613c | ||
|
|
d0b9aee85a | ||
|
|
3e94ef05fb | ||
|
|
fbb025875b | ||
|
|
ae816bd13c | ||
|
|
14f0693511 | ||
|
|
de7fb4a942 | ||
|
|
4164b4645d | ||
|
|
649b14fd0d | ||
|
|
6d97ef13a1 | ||
|
|
7abad979c8 | ||
|
|
0ec1574219 | ||
|
|
03042dd5c3 | ||
|
|
3330e4973f | ||
|
|
5e06aeabe9 | ||
|
|
e99d8766fc | ||
|
|
8f65b7e334 | ||
|
|
5a54b830bf | ||
|
|
85e08510f7 | ||
|
|
d56eeb7fb2 | ||
|
|
bbc520a7f2 | ||
|
|
10e43c64ca | ||
|
|
38be25174a | ||
|
|
2584d69930 | ||
|
|
523f39cf9c | ||
|
|
669760223e | ||
|
|
71ec13fa9f | ||
|
|
409528b286 | ||
|
|
17df3cf01d | ||
|
|
808a1d2470 | ||
|
|
840c500b5e | ||
|
|
42dc360d16 | ||
|
|
f6183597c9 | ||
|
|
79a45c4f10 | ||
|
|
19370215c0 | ||
|
|
030dd661b8 | ||
|
|
1fc12d9855 | ||
|
|
6c1b2b70ea | ||
|
|
23f9af35bf | ||
|
|
526626b80c | ||
|
|
10eaaac54b | ||
|
|
901a3ddcc9 | ||
|
|
e6ebf72a11 | ||
|
|
cd7e748c88 | ||
|
|
a313359ef6 | ||
|
|
f222eef6b7 | ||
|
|
e6f3aeb851 | ||
|
|
22605e57cc | ||
|
|
02fb7addf4 | ||
|
|
bdbb403a0e | ||
|
|
7a8bede92f | ||
|
|
a71a40b509 | ||
|
|
42fc5a5392 | ||
|
|
5017a0ea9b | ||
|
|
05f7b0060f | ||
|
|
1043da5328 | ||
|
|
959ad35afa | ||
|
|
1e10255d01 | ||
|
|
c72693bc53 | ||
|
|
2297aad5e5 | ||
|
|
f39c0db680 | ||
|
|
23353c77f3 | ||
|
|
fee1486db6 | ||
|
|
39c4253b24 | ||
|
|
84e924f46a |
1
.gitignore
vendored
@@ -5,3 +5,4 @@ cargo-config
|
||||
vendor-*
|
||||
vendor_*
|
||||
.vscode-ctags
|
||||
.vscode
|
||||
@@ -1,27 +1,63 @@
|
||||
image: rustdocker/rust:stable
|
||||
image: rust:latest
|
||||
|
||||
cache:
|
||||
key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
|
||||
paths:
|
||||
# Don't include `incremental` to save space
|
||||
# Debug
|
||||
- target/debug/build/
|
||||
- target/debug/deps/
|
||||
- target/debug/.fingerprint/
|
||||
- target/debug/.cargo-lock
|
||||
- target/debug/df_storyteller
|
||||
# Release
|
||||
- target/release/build/
|
||||
- target/release/deps/
|
||||
- target/release/.fingerprint/
|
||||
- target/release/.cargo-lock
|
||||
|
||||
before_script:
|
||||
- apt-get update -qq && apt-get install -y -qq libdbus-1-dev libclang-dev libudev-dev
|
||||
- apt-get update -qq && apt-get install -y -qq libudev-dev libgtk-3-dev grep
|
||||
|
||||
stages:
|
||||
- format
|
||||
- check
|
||||
- test
|
||||
- build
|
||||
- release
|
||||
|
||||
format:
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- echo "nightly" > rust-toolchain
|
||||
- rustup component add rustfmt
|
||||
- cargo fmt --check
|
||||
|
||||
check:
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- rustup component add clippy
|
||||
- cargo check
|
||||
# deny currently catches too much
|
||||
#- cargo install cargo-deny && cargo deny
|
||||
- cargo install cargo-cranky && cargo cranky
|
||||
|
||||
test:
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- cargo check #+nightly check --features "clippy"
|
||||
- cargo test
|
||||
|
||||
build:
|
||||
release:
|
||||
only:
|
||||
- main
|
||||
- tags
|
||||
script:
|
||||
- make && make vendor
|
||||
artifacts:
|
||||
paths:
|
||||
- vendor_asus-nb-ctrl_*.tar.xz
|
||||
- vendor_asusctl_*.tar.xz
|
||||
- cargo-config
|
||||
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: normal
|
||||
|
||||
|
||||
128
CHANGELOG.md
@@ -4,7 +4,133 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased ]
|
||||
## [Unreleased]
|
||||
## [v4.5.4]
|
||||
### Changed
|
||||
- ROGCC:: Allow ROGCC to run without supergfxd
|
||||
- ROGCC: Tray/notifs now reads dGPU status directly via supergfx crate (supergfxd not required)
|
||||
- Add rust-toolchain to force minimum rust version
|
||||
|
||||
## [v4.5.3]
|
||||
### Changed
|
||||
- Adjust how fan graph in ROGCC works, deny incorrect graphs
|
||||
- Fix to apply the fan curve change in ROGCC to the correct profile
|
||||
- Support for G713RS LED modes (Author: Peter Ivanov)
|
||||
- Support for G713RM LED modes (Author: maxbachmann)
|
||||
- Fix VivoBook detection
|
||||
- Update dependencies to get latest winit crate (fixes various small issues)
|
||||
|
||||
## [v4.5.2]
|
||||
### Changed
|
||||
- Update dependencies and bump version
|
||||
|
||||
## [v4.5.1]
|
||||
### Added
|
||||
- Support for FA506IE LED modes (Author: Herohtar)
|
||||
### Changed
|
||||
- Add a basic system tray with dGPU status and gpu mode switch actions
|
||||
- Fixup some notifications in ROGCC
|
||||
- Add config options for notifications for ROGCC
|
||||
- Share states with tray process in ROGCC
|
||||
- Share tates with tray process in ROGCC
|
||||
|
||||
## [v4.5.0]
|
||||
### Added
|
||||
- intofy watches on:
|
||||
- `charge_control_end_threshold`
|
||||
- `panel_od`
|
||||
- `gpu_mux_mode`
|
||||
- `platform_profile`
|
||||
- keyboard brightness
|
||||
- These allow for updating any associated config and sending dbus notifications.
|
||||
- New dbus methods
|
||||
- `DgpuDisable`
|
||||
- `SetDgpuDisable`
|
||||
- `NotifyDgpuDisable`
|
||||
- `EgpuEnable`
|
||||
- `SetEgpuEnable`
|
||||
- `NotifyEgpuEnable`
|
||||
- `MainsOnline` (This is AC, check if plugged in or not)
|
||||
- `NotifyMainsOnline`
|
||||
- `nvidia-powerd.service` will now enable or disable depending on the AC power state
|
||||
and on resume/boot (hybrid boot). It has been proven that this nvidia daemon can be
|
||||
problematic when on battery, not allowing the dgpu to suspend within decent time and
|
||||
sometimes blocking it completely.
|
||||
- Notification to rog-control-center of dGPU state change
|
||||
### Changed
|
||||
- Use loops to ensure that mutex is gained for LED changes.
|
||||
- asusctl now uses tokio for async runtime. This helps simplify some code.
|
||||
- Properly fix notifs used in rog-control-center
|
||||
### Breaking
|
||||
- DBUS: all charge control methods renamed to:
|
||||
- `ChargeControlEndThreshold`
|
||||
- `SetChargeControlEndThreshold`
|
||||
- `NotifyChargeControlEndThreshold`
|
||||
- DBUS: all panel overdrive methods renamed to:
|
||||
- `PanelOd` (from PanelOverdrive)
|
||||
- `SetPanelOd`
|
||||
- `NotifyPanelOd`
|
||||
- Path `/org/asuslinux/Charge` changed to `/org/asuslinux/Power`
|
||||
|
||||
## [v4.4.0] - 2022-08-29
|
||||
### Added
|
||||
- Support for per-key config has been added to `asusd-user`. At the moment it is
|
||||
basic with only a few effects done. Please see the manual for more information.
|
||||
- Support for unzoned and per-zone effects on some laptops. As above.
|
||||
- Added three effects to use with Zoned or Per-Key:
|
||||
+ Static, Breathe, Flicker. More to come.
|
||||
- Support for G713RS LED modes
|
||||
- Support for TUF laptop RGB (kernel patches required, these are submitted upstream)
|
||||
### Changed
|
||||
- Create new rog-platform crate to manage all i/o in a universal way
|
||||
+ kbd-led handling (requires kernel patches, TUF specific)
|
||||
+ platform handling (asus-nb-wmi)
|
||||
+ power (basic, can be extended in future)
|
||||
+ hidraw
|
||||
+ usbraw
|
||||
- Refactor how ROGCC handles IPC for background open, run-in-bg
|
||||
- Refactor daemon task creation to be simpler (for development)
|
||||
- Rename dpu_only to gpu_mux. Update all related messages and info.
|
||||
### Breaking
|
||||
- DBUS: rename path `/org/asuslinux/RogBios` to `/org/asuslinux/Platform`
|
||||
- DBUS: renamed `dedicated_graphic_mode` to `gpu_mux_mode` (`GpuMuxMode`)
|
||||
- DBUS: renamed `set_dedicated_graphic_mode` to `set_gpu_mux_mode` (`SetGpuMuxMode`)
|
||||
+ The methods above take an enum: 0 = Discrete, 1 = Optimus
|
||||
|
||||
## [4.3.4] - 2022-08-03
|
||||
### Bugfix
|
||||
- ROGCC: Remove power setting from correct array
|
||||
|
||||
## [4.3.3] - 2022-08-02
|
||||
### Added
|
||||
- `rog-control-center` has now been moved in to the main workspace due to
|
||||
the heavy dependencies on most of the rog crates
|
||||
- Preliminary support of TUF RGB keyboards + power states
|
||||
- Support for G713RW LED modes (Author: jarvis2709)
|
||||
- Support for G713IC LED modes
|
||||
### Changed
|
||||
- The udev rules have been changed to make asusd load with all gamer variants when asus-nb-wmi is loaded
|
||||
- TUF, ROG, Zephyrus, Strix
|
||||
|
||||
## [4.3.0] - 2022-07-21
|
||||
### Added
|
||||
- Clear command for anime `asusctl anime --clear` will clear the display
|
||||
- Re-added support for LED power states on `0x1866` type keyboards
|
||||
### Changed
|
||||
- Make rog-anime more error tolerent. Remove various asserts and return errors instead
|
||||
- Return error if a pixel-gif is larger than the anime-display dimensions
|
||||
- Both Anime and Aura dbus interfaces are changed a little
|
||||
- Aura power has changed, all power related settings are now in one method
|
||||
- Anime methods will now return an error (if errored)
|
||||
- /org/asuslinux/Led renamed to /org/asuslinux/Aura
|
||||
|
||||
## [4.2.1] - 2022-07-18
|
||||
### Added
|
||||
- Add panel overdrive support (autodetects if supported)
|
||||
- Add detection of dgpu_disable and egpu_enable for diagnostic
|
||||
### Changed
|
||||
- Fixed save and restore of multizone LED settings
|
||||
- Create defaults for multizone
|
||||
|
||||
## [4.2.0] - 2022-07-16
|
||||
### Added
|
||||
|
||||
2930
Cargo.lock
generated
39
Cargo.toml
@@ -1,5 +1,40 @@
|
||||
[workspace]
|
||||
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-supported", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles"]
|
||||
members = ["asusctl", "daemon", "daemon-user", "rog-platform", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles", "rog-control-center"]
|
||||
|
||||
[workspace.package]
|
||||
version = "4.5.4"
|
||||
|
||||
[workspace.dependencies]
|
||||
async-trait = "^0.1"
|
||||
tokio = { version = "^1.22.0", features = ["macros", "rt-multi-thread", "time", "sync"]}
|
||||
concat-idents = "^1.1"
|
||||
dirs = "^4.0"
|
||||
smol = "^1.3"
|
||||
|
||||
zbus = "^3.5"
|
||||
logind-zbus = { version = "^3.0.3" } #, default-features = false, features = ["non_blocking"] }
|
||||
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
toml = "^0.5.9"
|
||||
|
||||
log = "^0.4"
|
||||
env_logger = "^0.10.0"
|
||||
|
||||
glam = { version = "^0.22", features = ["serde"] }
|
||||
gumdrop = "^0.8"
|
||||
udev = "^0.7"
|
||||
rusb = "^0.9"
|
||||
sysfs-class = "^0.1.3"
|
||||
inotify = "^0.10.0"
|
||||
|
||||
png_pong = "^0.8"
|
||||
pix = "^0.13"
|
||||
tinybmp = "^0.4.0"
|
||||
gif = "^0.12.0"
|
||||
|
||||
notify-rust = { git = "https://github.com/flukejones/notify-rust.git", default-features = false, features = ["z"] }
|
||||
|
||||
[profile.release]
|
||||
# thin = 57s, asusd = 9.0M
|
||||
@@ -10,7 +45,7 @@ opt-level = 3
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev]
|
||||
debug = false
|
||||
debug = true
|
||||
opt-level = 1
|
||||
|
||||
[profile.bench]
|
||||
|
||||
121
Cranky.toml
Normal file
@@ -0,0 +1,121 @@
|
||||
# https://github.com/ericseppanen/cargo-cranky
|
||||
# cargo install cargo-cranky && cargo cranky
|
||||
|
||||
warn = [
|
||||
"clippy::all",
|
||||
"clippy::await_holding_lock",
|
||||
"clippy::bool_to_int_with_if",
|
||||
"clippy::char_lit_as_u8",
|
||||
"clippy::checked_conversions",
|
||||
"clippy::dbg_macro",
|
||||
"clippy::debug_assert_with_mut_call",
|
||||
"clippy::disallowed_methods",
|
||||
"clippy::disallowed_script_idents",
|
||||
"clippy::doc_link_with_quotes",
|
||||
"clippy::doc_markdown",
|
||||
"clippy::empty_enum",
|
||||
"clippy::enum_glob_use",
|
||||
"clippy::equatable_if_let",
|
||||
"clippy::exit",
|
||||
"clippy::expl_impl_clone_on_copy",
|
||||
"clippy::explicit_deref_methods",
|
||||
"clippy::explicit_into_iter_loop",
|
||||
"clippy::explicit_iter_loop",
|
||||
"clippy::fallible_impl_from",
|
||||
"clippy::filter_map_next",
|
||||
"clippy::flat_map_option",
|
||||
"clippy::float_cmp_const",
|
||||
"clippy::fn_params_excessive_bools",
|
||||
"clippy::fn_to_numeric_cast_any",
|
||||
"clippy::from_iter_instead_of_collect",
|
||||
"clippy::if_let_mutex",
|
||||
"clippy::implicit_clone",
|
||||
"clippy::imprecise_flops",
|
||||
"clippy::index_refutable_slice",
|
||||
"clippy::inefficient_to_string",
|
||||
"clippy::invalid_upcast_comparisons",
|
||||
"clippy::iter_not_returning_iterator",
|
||||
"clippy::iter_on_empty_collections",
|
||||
"clippy::iter_on_single_items",
|
||||
"clippy::large_digit_groups",
|
||||
"clippy::large_stack_arrays",
|
||||
"clippy::large_types_passed_by_value",
|
||||
"clippy::let_unit_value",
|
||||
"clippy::linkedlist",
|
||||
"clippy::lossy_float_literal",
|
||||
"clippy::macro_use_imports",
|
||||
"clippy::manual_assert",
|
||||
"clippy::manual_instant_elapsed",
|
||||
"clippy::manual_ok_or",
|
||||
"clippy::manual_string_new",
|
||||
"clippy::map_err_ignore",
|
||||
"clippy::map_flatten",
|
||||
"clippy::map_unwrap_or",
|
||||
"clippy::match_on_vec_items",
|
||||
"clippy::match_same_arms",
|
||||
"clippy::match_wild_err_arm",
|
||||
"clippy::match_wildcard_for_single_variants",
|
||||
"clippy::mem_forget",
|
||||
"clippy::mismatched_target_os",
|
||||
"clippy::mismatching_type_param_order",
|
||||
"clippy::missing_enforced_import_renames",
|
||||
# "clippy::missing_errors_doc",
|
||||
"clippy::missing_safety_doc",
|
||||
"clippy::mut_mut",
|
||||
"clippy::mutex_integer",
|
||||
"clippy::needless_borrow",
|
||||
"clippy::needless_continue",
|
||||
"clippy::needless_for_each",
|
||||
"clippy::needless_pass_by_value",
|
||||
"clippy::negative_feature_names",
|
||||
"clippy::nonstandard_macro_braces",
|
||||
"clippy::option_option",
|
||||
"clippy::path_buf_push_overwrite",
|
||||
"clippy::ptr_as_ptr",
|
||||
"clippy::rc_mutex",
|
||||
"clippy::ref_option_ref",
|
||||
"clippy::rest_pat_in_fully_bound_structs",
|
||||
"clippy::same_functions_in_if_condition",
|
||||
"clippy::semicolon_if_nothing_returned",
|
||||
"clippy::single_match_else",
|
||||
"clippy::str_to_string",
|
||||
"clippy::string_add_assign",
|
||||
"clippy::string_add",
|
||||
"clippy::string_lit_as_bytes",
|
||||
"clippy::string_to_string",
|
||||
"clippy::todo",
|
||||
"clippy::trailing_empty_array",
|
||||
"clippy::trait_duplication_in_bounds",
|
||||
"clippy::unimplemented",
|
||||
"clippy::unnecessary_wraps",
|
||||
"clippy::unnested_or_patterns",
|
||||
"clippy::unused_peekable",
|
||||
"clippy::unused_rounding",
|
||||
# "clippy::unused_self",
|
||||
"clippy::useless_transmute",
|
||||
"clippy::verbose_file_reads",
|
||||
"clippy::zero_sized_map_values",
|
||||
"elided_lifetimes_in_paths",
|
||||
"future_incompatible",
|
||||
"nonstandard_style",
|
||||
"rust_2018_idioms",
|
||||
"rust_2021_prelude_collisions",
|
||||
"rustdoc::missing_crate_level_docs",
|
||||
"semicolon_in_expressions_from_macros",
|
||||
"trivial_numeric_casts",
|
||||
"unused_extern_crates",
|
||||
"unused_import_braces",
|
||||
"unused_lifetimes",
|
||||
]
|
||||
|
||||
allow = [
|
||||
# TODO(emilk): enable more lints
|
||||
"clippy::cloned_instead_of_copied",
|
||||
"clippy::derive_partial_eq_without_eq",
|
||||
"clippy::type_complexity",
|
||||
"clippy::undocumented_unsafe_blocks",
|
||||
"trivial_casts",
|
||||
"unsafe_op_in_unsafe_fn", # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668
|
||||
"unused_qualifications",
|
||||
]
|
||||
|
||||
101
MANUAL.md
@@ -90,8 +90,8 @@ where the number is a percentage.
|
||||
|
||||
Some options that you find in Armory Crate are available under this controller, so far there is:
|
||||
|
||||
- POST sound: this is the sound you here on bios boot post
|
||||
- G-Sync: this controls if the dGPU (Nvidia) is the *only* GPU, making it the main GPU and disabling the iGPU
|
||||
- POST sound: this is the sound you hear on bios boot post
|
||||
- GPU MUX: this controls if the dGPU 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.
|
||||
|
||||
@@ -131,6 +131,101 @@ As of now only AniMe is active in this with configuration in `~/.config/rog/`. O
|
||||
|
||||
The main config is `~/.config/rog/rog-user.cfg`
|
||||
|
||||
#### Config options: Aura, per-key and zoned
|
||||
|
||||
I'm unsure of how many laptops this works on, so please try it.
|
||||
|
||||
`led_type: Key` works only on actual per-key RGB keyboards.
|
||||
|
||||
`led_type: Zone` works on zoned laptops.
|
||||
|
||||
`led_type: Zone` set to `None` works on zoned ROG laptops, unzoned ROG laptops, and TUF laptops (and yes this does mean an audio EQ can be done now).
|
||||
|
||||
`~/.config/rog/rog-user.cfg` contains a setting `"active_aura": "<FILENAME>"` where `<FILENAME>` is the name of the Aura config to use, located in the same directory and without the file postfix, e.g, `"active_anime": "aura-default"`
|
||||
|
||||
An Aura config itself is a file with contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "aura-default",
|
||||
"aura": [
|
||||
{
|
||||
"Breathe": {
|
||||
"led_type": {
|
||||
"Key": "W"
|
||||
},
|
||||
"start_colour1": [
|
||||
255,
|
||||
0,
|
||||
20
|
||||
],
|
||||
"start_colour2": [
|
||||
20,
|
||||
255,
|
||||
0
|
||||
],
|
||||
"speed": "Low"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Static": {
|
||||
"led_type": {
|
||||
"Key": "Esc"
|
||||
},
|
||||
"colour": [
|
||||
0,
|
||||
0,
|
||||
255
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Flicker": {
|
||||
"led_type": {
|
||||
"Key": "N9"
|
||||
},
|
||||
"start_colour": [
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"max_percentage": 80,
|
||||
"min_percentage": 40
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If your laptop supports multizone, `"led_type"` can also be `"Zone": <one of the following>`
|
||||
- `"None"`
|
||||
- `"KeyboardLeft"`
|
||||
- `"KeyboardCenterLeft"`
|
||||
- `"KeyboardCenterRight"`
|
||||
- `"KeyboardRight"`
|
||||
- `"LightbarRight"`
|
||||
- `"LightbarRightCorner"`
|
||||
- `"LightbarRightBottom"`
|
||||
- `"LightbarLeftBottom"`
|
||||
- `"LightbarLeftCorner"`
|
||||
- `"LightbarLeft"`
|
||||
|
||||
At the moment there are only three effects available as shown in the example. More will come in the future
|
||||
but this may take me some time.
|
||||
|
||||
**Aura layouts**: `asusd-user` does its best to find a suitable layout to use based on `/sys/class/dmi/id/board_name`.
|
||||
It looks at each of the files in `/usr/share/rog-gui/layouts/` and matches against the toml block looking like:
|
||||
```toml
|
||||
matches = [
|
||||
'GX502',
|
||||
'GU502',
|
||||
]
|
||||
```
|
||||
|
||||
My laptop is a `GX502GW`, so `GX502` is a match. Note that these layouts are the physical representation of
|
||||
the keyboard and are used in the GUI also. The config that tells if per-key is supported is located in
|
||||
`/etc/asusd/asusd-ledmodes.toml`
|
||||
|
||||
#### 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"`
|
||||
@@ -293,7 +388,7 @@ A plain non-float integer.
|
||||
|
||||
## asusctl
|
||||
|
||||
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. I can be used in any place a terminal app can be used.
|
||||
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. It can be used in any place a terminal app can be used.
|
||||
|
||||
This program will query `asusd` for the `Support` level of the laptop and show or hide options according to this support level.
|
||||
|
||||
|
||||
31
Makefile
@@ -1,4 +1,4 @@
|
||||
VERSION := $(shell grep -Pm1 'version = "(\d.\d.\d)"' daemon/Cargo.toml | cut -d'"' -f2)
|
||||
VERSION := $(shell /usr/bin/grep -Pm1 'version = "(\d.\d.\d)"' Cargo.toml | cut -d'"' -f2)
|
||||
|
||||
INSTALL = install
|
||||
INSTALL_PROGRAM = ${INSTALL} -D -m 0755
|
||||
@@ -11,10 +11,10 @@ datarootdir = $(prefix)/share
|
||||
libdir = $(exec_prefix)/lib
|
||||
zshcpl = $(datarootdir)/zsh/site-functions
|
||||
|
||||
BIN_ROG := rog-control-center
|
||||
BIN_C := asusctl
|
||||
BIN_D := asusd
|
||||
BIN_U := asusd-user
|
||||
BIN_N := asus-notify
|
||||
LEDCFG := asusd-ledmodes.toml
|
||||
|
||||
SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs')
|
||||
@@ -39,22 +39,28 @@ distclean:
|
||||
rm -rf .cargo vendor vendor.tar.xz
|
||||
|
||||
install:
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_ROG)" "$(DESTDIR)$(bindir)/$(BIN_ROG)"
|
||||
$(INSTALL_DATA) "./rog-control-center/data/$(BIN_ROG).desktop" "$(DESTDIR)$(datarootdir)/applications/$(BIN_ROG).desktop"
|
||||
$(INSTALL_DATA) "./rog-control-center/data/$(BIN_ROG).png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/$(BIN_ROG).png"
|
||||
cd rog-aura/data/layouts && find . -type f -name "*.toml" -exec $(INSTALL_DATA) "{}" "$(DESTDIR)$(datarootdir)/rog-gui/layouts/{}" \;
|
||||
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_C)" "$(DESTDIR)$(bindir)/$(BIN_C)"
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_D)" "$(DESTDIR)$(bindir)/$(BIN_D)"
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_U)" "$(DESTDIR)$(bindir)/$(BIN_U)"
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_N)" "$(DESTDIR)$(bindir)/$(BIN_N)"
|
||||
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).rules" "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
|
||||
$(INSTALL_DATA) "./data/$(LEDCFG)" "$(DESTDIR)/etc/asusd/$(LEDCFG)"
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).conf" "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
|
||||
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).service" "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
|
||||
$(INSTALL_DATA) "./data/$(BIN_N).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
|
||||
$(INSTALL_DATA) "./data/$(BIN_U).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_U).service"
|
||||
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_yellow.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_green.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_blue.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_blue.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_red.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_orange.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_orange.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_white.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_white.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"
|
||||
@@ -63,19 +69,19 @@ install:
|
||||
$(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/{}" \;
|
||||
cd rog-anime/data && find "./anime" -type f -exec $(INSTALL_DATA) "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
|
||||
|
||||
uninstall:
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_ROG)"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/applications/$(BIN_ROG).desktop"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/$(BIN_ROG).png"
|
||||
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_C)"
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_D)"
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_N)"
|
||||
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
|
||||
rm -f "$(DESTDIR)/etc/asusd/$(LEDCFG)"
|
||||
rm -f "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
|
||||
rm -f "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
|
||||
rm -r "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
|
||||
@@ -85,9 +91,8 @@ uninstall:
|
||||
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"
|
||||
rm -rf "$(DESTDIR)$(datarootdir)/rog-gui"
|
||||
|
||||
update:
|
||||
cargo update
|
||||
@@ -107,5 +112,9 @@ ifeq ($(VENDORED),1)
|
||||
tar pxf vendor_asusctl_$(VERSION).tar.xz
|
||||
endif
|
||||
cargo build $(ARGS)
|
||||
strip -s ./target/release/$(BIN_C)
|
||||
strip -s ./target/release/$(BIN_D)
|
||||
strip -s ./target/release/$(BIN_U)
|
||||
strip -s ./target/release/$(BIN_ROG)
|
||||
|
||||
.PHONY: all clean distclean install uninstall update build
|
||||
|
||||
41
README.md
@@ -2,14 +2,16 @@
|
||||
|
||||
[](https://www.paypal.com/donate/?hosted_button_id=4V2DEPS7K6APC) - [Asus Linux Website](https://asus-linux.org/)
|
||||
|
||||
**WARNING:** Many features are developed in tandem with kernel patches. If you see a feature is missing you either need a patched kernel, or v6.1 which has all my work merged upstream.
|
||||
|
||||
`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.
|
||||
|
||||
Now includes a GUI, `rog-control-center`.
|
||||
|
||||
## Kernel support
|
||||
|
||||
**The minimum supported kernel version is 5.15**
|
||||
|
||||
Fan curve control on laptops with this feature require [this patch](https://lkml.org/lkml/2021/10/23/250) which has been merged for 5.17 upstream.
|
||||
**The minimum supported kernel version is 5.17**
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -19,8 +21,8 @@ Fan curve control on laptops with this feature require [this patch](https://lkml
|
||||
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.
|
||||
supported. All other distros are *not* supported (while asusd might still run fine on them).
|
||||
For best support use fedora 36+ 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.
|
||||
@@ -39,12 +41,14 @@ Bus 001 Device 002: ID 0b05:1866 ASUSTek Computer, Inc. N-KEY Device
|
||||
```
|
||||
|
||||
then it may work without tweaks. Technically all other functions except the LED
|
||||
and AniMe parts should work regardless of your latop make. Eventually this project
|
||||
will probably suffer another rename once it becomes generic enough to do so.
|
||||
and AniMe parts should work regardless of your latop make.
|
||||
|
||||
**TUF Laptops**: now supported provided the kernel is patched. These patches are submitted upstream and will be in version 6.1.x of the kernel (or thereabouts). See the blog on asus-linux.org for more info.
|
||||
|
||||
## Implemented
|
||||
|
||||
- [X] System daemon
|
||||
- [X] GUI app
|
||||
- [X] User notifications daemon
|
||||
- [X] Setting/modifying built-in LED modes
|
||||
- [X] Per-key LED setting
|
||||
@@ -52,9 +56,17 @@ will probably suffer another rename once it becomes generic enough to do so.
|
||||
- [X] Saving settings for reload
|
||||
- [X] AniMatrix display on G14 models that include it
|
||||
- [X] Set battery charge limit (with kernel supporting this)
|
||||
- [X] Fan curve control on G14 + G15. Requires kernel patch (should reach 5.15 kernel)
|
||||
- [X] Fan curve control on supported laptops (G14/G15, some TUF like FA507)
|
||||
- [X] Toggle bios setting for boot/POST sound
|
||||
- [X] Toggle bios setting for "dedicated gfx" mode on supported laptops (g-sync)
|
||||
- [X] Toggle GPU MUX (g-sync, or called MUX on 2022+ laptops)
|
||||
|
||||
# GUI
|
||||
|
||||
A gui is now in the repo - ROG Control Center. At this time it is still a WIP, but it has almost all features in place already.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
# BUILDING
|
||||
|
||||
@@ -69,7 +81,16 @@ Requirements are rust >= 1.57 installed from rustup.io if the distro provided ve
|
||||
|
||||
**fedora:**
|
||||
|
||||
dnf install clang-devel systemd-devel cargo
|
||||
dnf install cmake clang-devel systemd-devel gtk3-devel cargo
|
||||
make
|
||||
sudo make install
|
||||
|
||||
**openSUSE:**
|
||||
|
||||
Works with KDE Plasma (without GTK packages)
|
||||
|
||||
zypper in -t pattern devel_basis
|
||||
zypper in rustup cmake clang-devel systemd-devel glib2-devel cairo-devel atkmm-devel pangomm-devel gdk-pixbuf-devel gtk3-devel
|
||||
make
|
||||
sudo make install
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "asus-notify"
|
||||
version = "3.1.0"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
zbus = "^2.2"
|
||||
# serialisation
|
||||
serde_json = "^1.0"
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_supported = { path = "../rog-supported" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
smol = "^1.2"
|
||||
|
||||
[dependencies.notify-rust]
|
||||
version = "^4.3"
|
||||
default-features = false
|
||||
features = ["z"]
|
||||
@@ -1,166 +0,0 @@
|
||||
use notify_rust::{Hint, Notification, NotificationHandle};
|
||||
use rog_aura::AuraEffect;
|
||||
use rog_dbus::{
|
||||
zbus_charge::ChargeProxy, zbus_led::LedProxy, zbus_profile::ProfileProxy,
|
||||
zbus_rogbios::RogBiosProxy,
|
||||
};
|
||||
use rog_profiles::Profile;
|
||||
use smol::{future, Executor};
|
||||
use std::{
|
||||
error::Error,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
|
||||
const NOTIF_HEADER: &str = "ROG Control";
|
||||
|
||||
macro_rules! notify {
|
||||
($notifier:ident, $last_notif:ident, $data:expr) => {
|
||||
if let Some(notif) = $last_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
if let Ok(x) = $notifier($data) {
|
||||
$last_notif.replace(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! base_notification {
|
||||
($body:expr) => {
|
||||
Notification::new()
|
||||
.summary(NOTIF_HEADER)
|
||||
.body($body)
|
||||
.timeout(2000)
|
||||
.show()
|
||||
};
|
||||
}
|
||||
|
||||
type SharedHandle = Arc<Mutex<Option<NotificationHandle>>>;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("asus-notify version {}", env!("CARGO_PKG_VERSION"));
|
||||
println!(" rog-dbus version {}", rog_dbus::VERSION);
|
||||
|
||||
let last_notification: SharedHandle = Arc::new(Mutex::new(None));
|
||||
|
||||
let executor = Executor::new();
|
||||
// BIOS notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = RogBiosProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_post_boot_sound().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_post_sound_notif, lock, &out.sound());
|
||||
}
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
// Charge notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = ChargeProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_charge().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_charge_notif, lock, &out.limit);
|
||||
}
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
// Profile notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = ProfileProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_profile().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_thermal_notif, lock, &out.profile);
|
||||
}
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
// LED notif
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = LedProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_led().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = last_notification.try_lock() {
|
||||
notify!(do_led_notif, lock, &out.data);
|
||||
}
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
loop {
|
||||
smol::block_on(executor.tick());
|
||||
}
|
||||
}
|
||||
|
||||
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
|
||||
let icon = match profile {
|
||||
Profile::Balanced => "asus_notif_yellow",
|
||||
Profile::Performance => "asus_notif_red",
|
||||
Profile::Quiet => "asus_notif_green",
|
||||
};
|
||||
let profile: &str = (*profile).into();
|
||||
let x = Notification::new()
|
||||
.summary("ASUS ROG")
|
||||
.body(&format!(
|
||||
"Thermal profile changed to {}",
|
||||
profile.to_uppercase(),
|
||||
))
|
||||
.hint(Hint::Resident(true))
|
||||
.timeout(2000)
|
||||
.hint(Hint::Category("device".into()))
|
||||
//.hint(Hint::Transient(true))
|
||||
.icon(icon)
|
||||
.show()?;
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
fn do_led_notif(ledmode: &AuraEffect) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!(
|
||||
"Keyboard LED mode changed to {}",
|
||||
ledmode.mode_name()
|
||||
))
|
||||
}
|
||||
|
||||
fn do_charge_notif(limit: &u8) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!("Battery charge limit changed to {}", limit))
|
||||
}
|
||||
|
||||
fn do_post_sound_notif(on: &bool) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!("BIOS Post sound {}", on))
|
||||
}
|
||||
@@ -1,26 +1,24 @@
|
||||
[package]
|
||||
name = "asusctl"
|
||||
version = "4.0.7"
|
||||
license = "MPL-2.0"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
zbus = "^2.2"
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_supported = { path = "../rog-supported" }
|
||||
rog_platform = { path = "../rog-platform" }
|
||||
daemon = { path = "../daemon" }
|
||||
gumdrop = "^0.8"
|
||||
toml = "^0.5.8"
|
||||
|
||||
sysfs-class = "^0.1.2"
|
||||
gumdrop.workspace = true
|
||||
toml.workspace = true
|
||||
sysfs-class.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tinybmp = "^0.3.3"
|
||||
glam = "0.20.5"
|
||||
gif.workspace = true
|
||||
tinybmp.workspace = true
|
||||
glam.workspace = true
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
gif = "^0.11.2"
|
||||
|
||||
@@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(matrix.into_data_buffer(anime_type))
|
||||
.write(matrix.into_data_buffer(anime_type)?)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -20,7 +20,7 @@ fn main() {
|
||||
}
|
||||
|
||||
for c in (0..35).into_iter().step_by(step) {
|
||||
for i in matrix.get_mut()[c].iter_mut() {
|
||||
for i in &mut matrix.get_mut()[c] {
|
||||
*i = 50;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ fn main() {
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(matrix.into_data_buffer(anime_type))
|
||||
.write(matrix.into_data_buffer(anime_type).unwrap())
|
||||
.unwrap();
|
||||
sleep(Duration::from_millis(300));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use rog_anime::{usb::get_anime_type, AnimeDataBuffer, AnimeGrid};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
@@ -16,7 +17,6 @@ fn main() {
|
||||
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 {
|
||||
@@ -39,7 +39,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let matrix = <AnimeDataBuffer>::from(matrix);
|
||||
let matrix = <AnimeDataBuffer>::try_from(matrix).unwrap();
|
||||
|
||||
client.proxies().anime().write(matrix).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::{env, error::Error, path::Path, process::exit};
|
||||
|
||||
use rog_anime::{
|
||||
@@ -32,7 +33,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))
|
||||
.write(<AnimeDataBuffer>::try_from(&matrix)?)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::{
|
||||
env, error::Error, f32::consts::PI, path::Path, process::exit, thread::sleep, time::Duration,
|
||||
};
|
||||
@@ -34,14 +35,14 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
loop {
|
||||
matrix.angle += 0.05;
|
||||
if matrix.angle > PI * 2.0 {
|
||||
matrix.angle = 0.0
|
||||
matrix.angle = 0.0;
|
||||
}
|
||||
matrix.update();
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))
|
||||
.write(<AnimeDataBuffer>::try_from(&matrix)?)
|
||||
.unwrap();
|
||||
sleep(Duration::from_micros(500));
|
||||
}
|
||||
|
||||
113
asusctl/examples/aura-rgb-ball.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
//! Very bad rushed example. The better way to do this would be to have
|
||||
//! the balles move on their own square grid, then translate that to the
|
||||
//! key layout via shape by pitch etc.
|
||||
use rog_aura::{
|
||||
layouts::{KeyLayout, KeyRow},
|
||||
KeyColourArray,
|
||||
};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Ball {
|
||||
position: (f32, f32),
|
||||
direction: (f32, f32),
|
||||
trail: VecDeque<(f32, f32)>,
|
||||
}
|
||||
impl Ball {
|
||||
fn new(x: f32, y: f32, trail_len: u32) -> Self {
|
||||
let mut trail = VecDeque::new();
|
||||
for _ in 1..=trail_len {
|
||||
trail.push_back((x, y));
|
||||
}
|
||||
|
||||
Ball {
|
||||
position: (x, y),
|
||||
direction: (1.0, 1.0),
|
||||
trail,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn update(&mut self, key_map: &[KeyRow]) {
|
||||
self.position.0 += self.direction.0;
|
||||
self.position.1 += self.direction.1;
|
||||
|
||||
if self.position.1.abs() as usize >= key_map.len() {
|
||||
self.direction.1 *= -1.0;
|
||||
self.position.1 += self.direction.1;
|
||||
self.direction.0 *= -1.0;
|
||||
self.position.0 += self.direction.0;
|
||||
}
|
||||
if self.position.0.abs() as usize >= key_map[self.position.1.abs() as usize].row_ref().len()
|
||||
{
|
||||
self.direction.1 *= -1.0;
|
||||
self.position.1 += self.direction.1;
|
||||
}
|
||||
if self.position.0 as usize >= key_map[self.position.1.abs() as usize].row_ref().len() {
|
||||
self.direction.0 *= -1.0;
|
||||
self.position.0 += self.direction.0;
|
||||
}
|
||||
|
||||
let pos = self.position;
|
||||
|
||||
if pos.1 == key_map[pos.1.abs() as usize].row_ref().len() as f32 - 1.0 || pos.1 <= 0.0 {
|
||||
self.direction.0 *= -1.0;
|
||||
} else if key_map[(pos.1) as usize].row_ref()[(pos.0) as usize].is_placeholder() {
|
||||
self.direction.0 *= -1.0;
|
||||
}
|
||||
|
||||
if pos.0 == key_map.len() as f32 - 1.0 || pos.0 <= 0.0 {
|
||||
self.direction.1 *= -1.0;
|
||||
} else if key_map[(pos.1) as usize].row_ref()[(pos.0) as usize].is_placeholder() {
|
||||
self.direction.1 *= -1.0;
|
||||
}
|
||||
|
||||
self.trail.pop_front();
|
||||
self.trail.push_back(self.position);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (dbus, _) = RogDbusClientBlocking::new()?;
|
||||
|
||||
let mut colours = KeyColourArray::new();
|
||||
let layout = KeyLayout::gx502_layout();
|
||||
|
||||
let mut balls = [Ball::new(2.0, 1.0, 12), Ball::new(5.0, 2.0, 12)];
|
||||
// let mut balls = [Ball::new(2, 1, 12)];
|
||||
|
||||
loop {
|
||||
for (n, ball) in balls.iter_mut().enumerate() {
|
||||
ball.update(layout.rows_ref());
|
||||
for (i, pos) in ball.trail.iter().enumerate() {
|
||||
if let Some(c) = colours
|
||||
.rgb_for_key(layout.rows_ref()[pos.1.abs() as usize].row_ref()[pos.0 as usize])
|
||||
{
|
||||
c[0] = 0;
|
||||
c[1] = 0;
|
||||
c[2] = 0;
|
||||
if n == 0 {
|
||||
c[0] = i as u8 * (255 / ball.trail.len() as u8);
|
||||
} else if n == 1 {
|
||||
c[1] = i as u8 * (255 / ball.trail.len() as u8);
|
||||
} else if n == 2 {
|
||||
c[2] = i as u8 * (255 / ball.trail.len() as u8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(c) = colours.rgb_for_key(
|
||||
layout.rows_ref()[ball.position.1.abs() as usize].row_ref()
|
||||
[ball.position.0 as usize],
|
||||
) {
|
||||
c[0] = 255;
|
||||
c[1] = 255;
|
||||
c[2] = 255;
|
||||
};
|
||||
}
|
||||
dbus.proxies().led().per_key_raw(colours.get())?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(150));
|
||||
}
|
||||
}
|
||||
59
asusctl/examples/aura-rgb-breathe.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
//! Using a combination of key-colour array plus a key layout to generate outputs.
|
||||
|
||||
use rog_aura::{keys::Key, layouts::KeyLayout, Breathe, Colour, Effect, LedType, Sequences, Speed};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let layout = KeyLayout::gx502_layout();
|
||||
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
let mut seq = Sequences::new();
|
||||
let mut key = Effect::Breathe(Breathe::new(
|
||||
LedType::Key(Key::W),
|
||||
Colour(255, 127, 0),
|
||||
Colour(127, 0, 255),
|
||||
Speed::Med,
|
||||
));
|
||||
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::A));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::S));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::D));
|
||||
seq.push(key.clone());
|
||||
|
||||
let mut key = Effect::Breathe(Breathe::new(
|
||||
LedType::Key(Key::Q),
|
||||
Colour(127, 127, 127),
|
||||
Colour(127, 255, 255),
|
||||
Speed::Low,
|
||||
));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::E));
|
||||
seq.push(key.clone());
|
||||
|
||||
let mut key = Effect::Breathe(Breathe::new(
|
||||
LedType::Key(Key::N1),
|
||||
Colour(166, 127, 166),
|
||||
Colour(127, 155, 20),
|
||||
Speed::High,
|
||||
));
|
||||
key.set_led_type(LedType::Key(Key::Tilde));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::N2));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::N3));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::N4));
|
||||
seq.push(key.clone());
|
||||
|
||||
loop {
|
||||
seq.next_state(&layout);
|
||||
let packets = seq.create_packets();
|
||||
|
||||
client.proxies().led().per_key_raw(packets)?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(60));
|
||||
}
|
||||
}
|
||||
34
asusctl/examples/aura-rgb-iterate-keys.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
//! Using a combination of key-colour array plus a key layout to generate outputs.
|
||||
|
||||
use rog_aura::{layouts::KeyLayout, KeyColourArray};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let layout = KeyLayout::gx502_layout();
|
||||
loop {
|
||||
let mut key_colours = KeyColourArray::new();
|
||||
for row in layout.rows() {
|
||||
for (k, key) in row.row().enumerate() {
|
||||
if k != 0 {
|
||||
if let Some(prev) = row.row().nth(k - 1) {
|
||||
if let Some(c) = key_colours.rgb_for_key(*prev) {
|
||||
c[0] = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if key.is_placeholder() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(c) = key_colours.rgb_for_key(*key) {
|
||||
c[0] = 255;
|
||||
};
|
||||
|
||||
client.proxies().led().per_key_raw(key_colours.get())?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
asusctl/examples/aura-zoned-breathe.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! Using a combination of key-colour array plus a key layout to generate outputs.
|
||||
|
||||
use rog_aura::{layouts::KeyLayout, Breathe, Colour, Effect, LedType, PerZone, Sequences, Speed};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let layout = KeyLayout::gx502_layout();
|
||||
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
let mut seq = Sequences::new();
|
||||
|
||||
let zone = Effect::Breathe(Breathe::new(
|
||||
LedType::Zone(PerZone::KeyboardLeft),
|
||||
Colour(166, 127, 166),
|
||||
Colour(127, 155, 20),
|
||||
Speed::High,
|
||||
));
|
||||
seq.push(zone);
|
||||
|
||||
let zone = Effect::Breathe(Breathe::new(
|
||||
LedType::Zone(PerZone::KeyboardCenterLeft),
|
||||
Colour(16, 127, 255),
|
||||
Colour(127, 15, 20),
|
||||
Speed::Low,
|
||||
));
|
||||
seq.push(zone);
|
||||
|
||||
let zone = Effect::Breathe(Breathe::new(
|
||||
LedType::Zone(PerZone::LightbarRightCorner),
|
||||
Colour(0, 255, 255),
|
||||
Colour(255, 0, 255),
|
||||
Speed::Med,
|
||||
));
|
||||
seq.push(zone);
|
||||
|
||||
loop {
|
||||
seq.next_state(&layout);
|
||||
let packets = seq.create_packets();
|
||||
|
||||
client.proxies().led().per_key_raw(packets)?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(60));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ pub struct AnimeCommand {
|
||||
pub boot_enable: Option<bool>,
|
||||
#[options(meta = "", help = "set global AniMe brightness value")]
|
||||
pub brightness: Option<f32>,
|
||||
#[options(help = "clear the display")]
|
||||
pub clear: bool,
|
||||
#[options(command)]
|
||||
pub command: Option<AnimeActions>,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,23 @@ use rog_aura::{error::Error, AuraEffect, AuraModeNum, AuraZone, Colour, Directio
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct LedPowerCommand {
|
||||
pub struct LedPowerCommand1 {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "Control if LEDs enabled while awake <true/false>")]
|
||||
pub awake: Option<bool>,
|
||||
#[options(meta = "", help = "Use with awake option <true/false>")]
|
||||
pub keyboard: Option<bool>,
|
||||
#[options(meta = "", help = "Use with awake option <true/false>")]
|
||||
pub lightbar: Option<bool>,
|
||||
#[options(meta = "", help = "Control boot animations <true/false>")]
|
||||
pub boot: Option<bool>,
|
||||
#[options(meta = "", help = "Control suspend animations <true/false>")]
|
||||
pub sleep: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct LedPowerCommand2 {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(command)]
|
||||
@@ -12,12 +28,13 @@ pub struct LedPowerCommand {
|
||||
|
||||
#[derive(Options)]
|
||||
pub enum SetAuraEnabled {
|
||||
/// Applies to both old and new models
|
||||
#[options(help = "set <keyboard, logo, lightbar> to enabled while device is awake")]
|
||||
Awake(AuraEnabled),
|
||||
#[options(help = "set <keyboard, logo, lightbar> to enabled while the device is booting")]
|
||||
Boot(AuraEnabled),
|
||||
#[options(help = "set <keyboard, logo, lightbar> to animate while the device is suspended")]
|
||||
Sleep(AuraEnabled),
|
||||
#[options(help = "set <keyboard, logo, lightbar> to enabled while device is awake")]
|
||||
Awake(AuraEnabled),
|
||||
#[options(help = "set <keyboard, logo, lightbar> to animate while the device is shutdown")]
|
||||
Shutdown(AuraEnabled),
|
||||
}
|
||||
@@ -32,6 +49,8 @@ pub struct AuraEnabled {
|
||||
pub logo: Option<bool>,
|
||||
#[options(meta = "", help = "<true/false>")]
|
||||
pub lightbar: Option<bool>,
|
||||
#[options(meta = "", help = "<true/false>")]
|
||||
pub lid: Option<bool>,
|
||||
}
|
||||
|
||||
// impl FromStr for AuraEnabled {
|
||||
@@ -39,7 +58,6 @@ pub struct AuraEnabled {
|
||||
|
||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// let s = s.to_lowercase();
|
||||
// dbg!(s);
|
||||
// Ok(Self {
|
||||
// help: false,
|
||||
// keyboard: None,
|
||||
@@ -87,7 +105,7 @@ impl ToString for LedBrightness {
|
||||
Some(0x02) => "high",
|
||||
_ => "unknown",
|
||||
};
|
||||
s.to_string()
|
||||
s.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
anime_cli::AnimeCommand,
|
||||
aura_cli::{LedBrightness, LedPowerCommand, SetAuraBuiltin},
|
||||
aura_cli::{LedBrightness, LedPowerCommand1, LedPowerCommand2, SetAuraBuiltin},
|
||||
profiles_cli::{FanCurveCommand, ProfileCommand},
|
||||
};
|
||||
use gumdrop::Options;
|
||||
@@ -29,8 +29,10 @@ pub struct CliStart {
|
||||
pub enum CliCommand {
|
||||
#[options(help = "Set the keyboard lighting from built-in modes")]
|
||||
LedMode(LedModeCommand),
|
||||
#[options(help = "Set the keyboard lighting from built-in modes")]
|
||||
LedPower(LedPowerCommand),
|
||||
#[options(help = "Set the LED power states")]
|
||||
LedPow1(LedPowerCommand1),
|
||||
#[options(help = "Set the LED power states")]
|
||||
LedPow2(LedPowerCommand2),
|
||||
#[options(help = "Set or select platform_profile")]
|
||||
Profile(ProfileCommand),
|
||||
#[options(help = "Set, select, or modify fan curves if supported")]
|
||||
@@ -67,18 +69,29 @@ pub struct BiosCommand {
|
||||
pub help: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "S",
|
||||
no_long,
|
||||
help = "set bios POST sound: asusctl -p <true/false>"
|
||||
)]
|
||||
pub post_sound_set: Option<bool>,
|
||||
#[options(no_long, help = "read bios POST sound")]
|
||||
#[options(no_long, short = "s", help = "read bios POST sound")]
|
||||
pub post_sound_get: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "D",
|
||||
no_long,
|
||||
help = "activate dGPU dedicated/G-Sync: asusctl -d <true/false>, reboot required"
|
||||
help = "Switch GPU MUX mode: 0 = Discrete, 1 = Optimus, reboot required"
|
||||
)]
|
||||
pub dedicated_gfx_set: Option<bool>,
|
||||
#[options(no_long, help = "get GPU mode")]
|
||||
pub dedicated_gfx_get: bool,
|
||||
pub gpu_mux_mode_set: Option<u8>,
|
||||
#[options(no_long, short = "d", help = "get GPU mode")]
|
||||
pub gpu_mux_mode_get: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "O",
|
||||
no_long,
|
||||
help = "Set device panel overdrive <true/false>"
|
||||
)]
|
||||
pub panel_overdrive_set: Option<bool>,
|
||||
#[options(no_long, short = "o", help = "get panel overdrive")]
|
||||
pub panel_overdrive_get: bool,
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::process::Command;
|
||||
use std::thread::sleep;
|
||||
use std::{env::args, path::Path};
|
||||
|
||||
use aura_cli::LedPowerCommand;
|
||||
use aura_cli::{LedPowerCommand1, LedPowerCommand2};
|
||||
use gumdrop::{Opt, Options};
|
||||
|
||||
use anime_cli::{AnimeActions, AnimeCommand};
|
||||
use profiles_cli::{FanCurveCommand, ProfileCommand};
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, Vec2};
|
||||
use rog_aura::usb::AuraControl;
|
||||
|
||||
use rog_aura::usb::{AuraDev1866, AuraDev19b6, AuraDevTuf, AuraDevice, AuraPowerDev};
|
||||
use rog_aura::{self, AuraEffect};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use rog_platform::platform::GpuMode;
|
||||
use rog_platform::supported::*;
|
||||
use rog_profiles::error::ProfileError;
|
||||
use rog_supported::SupportedFunctions;
|
||||
use rog_supported::{
|
||||
AnimeSupportedFunctions, LedSupportedFunctions, PlatformProfileFunctions,
|
||||
RogBiosSupportedFunctions,
|
||||
};
|
||||
|
||||
use crate::aura_cli::LedBrightness;
|
||||
use crate::cli_opts::*;
|
||||
@@ -27,32 +26,25 @@ mod aura_cli;
|
||||
mod cli_opts;
|
||||
mod profiles_cli;
|
||||
|
||||
const CONFIG_ADVICE: &str = "A config file need to be removed so a new one can be generated";
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn main() {
|
||||
let args: Vec<String> = args().skip(1).collect();
|
||||
|
||||
let parsed: CliStart;
|
||||
let missing_argument_k = gumdrop::Error::missing_argument(Opt::Short('k'));
|
||||
match CliStart::parse_args_default(&args) {
|
||||
Ok(p) => {
|
||||
parsed = p;
|
||||
}
|
||||
Err(err) if err.to_string() == missing_argument_k.to_string() => {
|
||||
parsed = CliStart {
|
||||
kbd_bright: Some(LedBrightness::new(None)),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
let parsed = match CliStart::parse_args_default(&args) {
|
||||
Ok(p) => p,
|
||||
Err(err) if err.to_string() == missing_argument_k.to_string() => CliStart {
|
||||
kbd_bright: Some(LedBrightness::new(None)),
|
||||
..Default::default()
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("source {}", err);
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (dbus, _) = RogDbusClientBlocking::new()
|
||||
.map_err(|e| {
|
||||
print_error_help(Box::new(e), None);
|
||||
print_error_help(&e, None);
|
||||
std::process::exit(3);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -62,7 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.supported()
|
||||
.supported_functions()
|
||||
.map_err(|e| {
|
||||
print_error_help(Box::new(e), None);
|
||||
print_error_help(&e, None);
|
||||
std::process::exit(4);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -71,26 +63,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
print_versions();
|
||||
println!();
|
||||
print_laptop_info();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Err(err) = do_parsed(&parsed, &supported, &dbus) {
|
||||
print_error_help(err, Some(&supported));
|
||||
print_error_help(&*err, Some(&supported));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_error_help(err: Box<dyn std::error::Error>, supported: Option<&SupportedFunctions>) {
|
||||
if do_diagnose("asusd") {
|
||||
println!("\nError: {}\n", err);
|
||||
print_versions();
|
||||
fn print_error_help(err: &dyn std::error::Error, supported: Option<&SupportedFunctions>) {
|
||||
check_service("asusd");
|
||||
println!("\nError: {}\n", err);
|
||||
print_versions();
|
||||
println!();
|
||||
print_laptop_info();
|
||||
if let Some(supported) = supported {
|
||||
println!();
|
||||
print_laptop_info();
|
||||
if let Some(supported) = supported {
|
||||
println!();
|
||||
println!("Supported laptop functions:\n\n{}", supported);
|
||||
}
|
||||
println!("Supported laptop functions:\n\n{}", supported);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +91,7 @@ fn print_versions() {
|
||||
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!("rog-platform v{}", rog_platform::VERSION);
|
||||
}
|
||||
|
||||
fn print_laptop_info() {
|
||||
@@ -115,7 +103,7 @@ fn print_laptop_info() {
|
||||
println!("Board name: {}", board_name.trim());
|
||||
}
|
||||
|
||||
fn do_diagnose(name: &str) -> bool {
|
||||
fn check_service(name: &str) -> bool {
|
||||
if name != "asusd" && !check_systemd_unit_enabled(name) {
|
||||
println!(
|
||||
"\n\x1b[0;31m{} is not enabled, enable it with `systemctl enable {}\x1b[0m",
|
||||
@@ -128,13 +116,6 @@ fn do_diagnose(name: &str) -> bool {
|
||||
name, name
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
println!("\nSome error happened (sorry)");
|
||||
println!(
|
||||
"Please use `systemctl status {}` and `journalctl -b -u {}` for more information",
|
||||
name, name
|
||||
);
|
||||
println!("{}", CONFIG_ADVICE);
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -142,16 +123,17 @@ fn do_diagnose(name: &str) -> bool {
|
||||
fn do_parsed(
|
||||
parsed: &CliStart,
|
||||
supported: &SupportedFunctions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match &parsed.command {
|
||||
Some(CliCommand::LedMode(mode)) => handle_led_mode(dbus, &supported.keyboard_led, mode)?,
|
||||
Some(CliCommand::LedPower(pow)) => handle_led_power(dbus, &supported.keyboard_led, pow)?,
|
||||
Some(CliCommand::LedPow1(pow)) => handle_led_power1(dbus, &supported.keyboard_led, pow)?,
|
||||
Some(CliCommand::LedPow2(pow)) => handle_led_power2(dbus, &supported.keyboard_led, pow)?,
|
||||
Some(CliCommand::Profile(cmd)) => handle_profile(dbus, &supported.platform_profile, cmd)?,
|
||||
Some(CliCommand::FanCurve(cmd)) => {
|
||||
handle_fan_curve(dbus, &supported.platform_profile, cmd)?
|
||||
handle_fan_curve(dbus, &supported.platform_profile, cmd)?;
|
||||
}
|
||||
Some(CliCommand::Graphics(_)) => do_gfx()?,
|
||||
Some(CliCommand::Graphics(_)) => do_gfx(),
|
||||
Some(CliCommand::Anime(cmd)) => handle_anime(dbus, &supported.anime_ctrl, cmd)?,
|
||||
Some(CliCommand::Bios(cmd)) => handle_bios_option(dbus, &supported.rog_bios_ctrl, cmd)?,
|
||||
None => {
|
||||
@@ -165,8 +147,32 @@ fn do_parsed(
|
||||
println!("{}", CliStart::usage());
|
||||
println!();
|
||||
if let Some(cmdlist) = CliStart::command_list() {
|
||||
println!("{}", cmdlist);
|
||||
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
|
||||
for command in commands.iter().filter(|command| {
|
||||
if !matches!(
|
||||
supported.keyboard_led.prod_id,
|
||||
AuraDevice::X1854
|
||||
| AuraDevice::X1869
|
||||
| AuraDevice::X1866
|
||||
| AuraDevice::Tuf
|
||||
) && command.trim().starts_with("led-pow-1")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if supported.keyboard_led.prod_id != AuraDevice::X19B6
|
||||
&& command.trim().starts_with("led-pow-2")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}) {
|
||||
println!("{}", command);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nExtra help can be requested on any command or subcommand:");
|
||||
println!(" asusctl led-mode --help");
|
||||
println!(" asusctl led-mode static --help");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,20 +203,21 @@ fn do_parsed(
|
||||
}
|
||||
|
||||
if let Some(chg_limit) = parsed.chg_limit {
|
||||
dbus.proxies().charge().set_limit(chg_limit)?;
|
||||
dbus.proxies()
|
||||
.charge()
|
||||
.set_charge_control_end_threshold(chg_limit)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_gfx() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn do_gfx() {
|
||||
println!("Please use supergfxctl for graphics switching. supergfxctl is the result of making asusctl graphics switching generic so all laptops can use it");
|
||||
println!("This command will be removed in future");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_anime(
|
||||
dbus: &RogDbusClientBlocking,
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
_supported: &AnimeSupportedFunctions,
|
||||
cmd: &AnimeCommand,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -226,15 +233,22 @@ fn handle_anime(
|
||||
}
|
||||
}
|
||||
if let Some(anime_turn) = cmd.enable {
|
||||
dbus.proxies().anime().set_on_off(anime_turn)?
|
||||
dbus.proxies().anime().set_on_off(anime_turn)?;
|
||||
}
|
||||
if let Some(anime_boot) = cmd.boot_enable {
|
||||
dbus.proxies().anime().set_boot_on_off(anime_boot)?
|
||||
dbus.proxies().anime().set_boot_on_off(anime_boot)?;
|
||||
}
|
||||
if let Some(bright) = cmd.brightness {
|
||||
verify_brightness(bright);
|
||||
dbus.proxies().anime().set_brightness(bright)?
|
||||
dbus.proxies().anime().set_brightness(bright)?;
|
||||
}
|
||||
if cmd.clear {
|
||||
let anime_type = get_anime_type()?;
|
||||
let data = vec![0u8; anime_type.data_length()];
|
||||
let tmp = AnimeDataBuffer::from_vec(anime_type, data)?;
|
||||
dbus.proxies().anime().write(tmp)?;
|
||||
}
|
||||
|
||||
if let Some(action) = cmd.command.as_ref() {
|
||||
let anime_type = get_anime_type()?;
|
||||
match action {
|
||||
@@ -259,7 +273,7 @@ fn handle_anime(
|
||||
|
||||
dbus.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))?;
|
||||
.write(<AnimeDataBuffer>::try_from(&matrix)?)?;
|
||||
}
|
||||
AnimeActions::PixelImage(image) => {
|
||||
if image.help_requested() || image.path.is_empty() {
|
||||
@@ -280,7 +294,7 @@ fn handle_anime(
|
||||
|
||||
dbus.proxies()
|
||||
.anime()
|
||||
.write(matrix.into_data_buffer(anime_type))?;
|
||||
.write(matrix.into_data_buffer(anime_type)?)?;
|
||||
}
|
||||
AnimeActions::Gif(gif) => {
|
||||
if gif.help_requested() || gif.path.is_empty() {
|
||||
@@ -353,7 +367,7 @@ fn handle_anime(
|
||||
}
|
||||
|
||||
fn verify_brightness(brightness: f32) {
|
||||
if brightness < 0.0 || brightness > 1.0 {
|
||||
if !(0.0..=1.0).contains(&brightness) {
|
||||
println!(
|
||||
"Image and global brightness must be between 0.0 and 1.0 (inclusive), was {}",
|
||||
brightness
|
||||
@@ -363,7 +377,7 @@ fn verify_brightness(brightness: f32) {
|
||||
}
|
||||
|
||||
fn handle_led_mode(
|
||||
dbus: &RogDbusClientBlocking,
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
supported: &LedSupportedFunctions,
|
||||
mode: &LedModeCommand,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -419,10 +433,124 @@ fn handle_led_mode(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_led_power(
|
||||
dbus: &RogDbusClientBlocking,
|
||||
_supported: &LedSupportedFunctions,
|
||||
power: &LedPowerCommand,
|
||||
fn handle_led_power1(
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
supported: &LedSupportedFunctions,
|
||||
power: &LedPowerCommand1,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if power.awake.is_none()
|
||||
&& power.sleep.is_none()
|
||||
&& power.boot.is_none()
|
||||
&& power.keyboard.is_none()
|
||||
&& power.lightbar.is_none()
|
||||
{
|
||||
if !power.help {
|
||||
println!("Missing arg or command\n");
|
||||
}
|
||||
println!("{}\n", power.self_usage());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if matches!(
|
||||
supported.prod_id,
|
||||
AuraDevice::X1854 | AuraDevice::X1869 | AuraDevice::X1866
|
||||
) {
|
||||
handle_led_power_1_do_1866(dbus, power)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if matches!(supported.prod_id, AuraDevice::Tuf) {
|
||||
handle_led_power_1_do_tuf(dbus, power)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("These options are for keyboards of product ID 0x1866 or TUF only");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_led_power_1_do_1866(
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
power: &LedPowerCommand1,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut enabled: Vec<AuraDev1866> = Vec::new();
|
||||
let mut disabled: Vec<AuraDev1866> = Vec::new();
|
||||
|
||||
let mut check = |e: Option<bool>, a: AuraDev1866| {
|
||||
if let Some(arg) = e {
|
||||
if arg {
|
||||
enabled.push(a);
|
||||
} else {
|
||||
disabled.push(a);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check(power.awake, AuraDev1866::Awake);
|
||||
check(power.boot, AuraDev1866::Boot);
|
||||
check(power.sleep, AuraDev1866::Sleep);
|
||||
check(power.keyboard, AuraDev1866::Keyboard);
|
||||
check(power.lightbar, AuraDev1866::Lightbar);
|
||||
|
||||
let data = AuraPowerDev {
|
||||
x1866: enabled,
|
||||
x19b6: vec![],
|
||||
tuf: vec![],
|
||||
};
|
||||
dbus.proxies().led().set_leds_power(data, true)?;
|
||||
|
||||
let data = AuraPowerDev {
|
||||
x1866: disabled,
|
||||
x19b6: vec![],
|
||||
tuf: vec![],
|
||||
};
|
||||
dbus.proxies().led().set_leds_power(data, false)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_led_power_1_do_tuf(
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
power: &LedPowerCommand1,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut enabled: Vec<AuraDevTuf> = Vec::new();
|
||||
let mut disabled: Vec<AuraDevTuf> = Vec::new();
|
||||
|
||||
let mut check = |e: Option<bool>, a: AuraDevTuf| {
|
||||
if let Some(arg) = e {
|
||||
if arg {
|
||||
enabled.push(a);
|
||||
} else {
|
||||
disabled.push(a);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check(power.awake, AuraDevTuf::Awake);
|
||||
check(power.boot, AuraDevTuf::Boot);
|
||||
check(power.sleep, AuraDevTuf::Sleep);
|
||||
check(power.keyboard, AuraDevTuf::Keyboard);
|
||||
|
||||
let data = AuraPowerDev {
|
||||
x1866: vec![],
|
||||
x19b6: vec![],
|
||||
tuf: enabled,
|
||||
};
|
||||
dbus.proxies().led().set_leds_power(data, true)?;
|
||||
|
||||
let data = AuraPowerDev {
|
||||
x1866: vec![],
|
||||
x19b6: vec![],
|
||||
tuf: disabled,
|
||||
};
|
||||
dbus.proxies().led().set_leds_power(data, false)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_led_power2(
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
supported: &LedSupportedFunctions,
|
||||
power: &LedPowerCommand2,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if power.command().is_none() {
|
||||
if !power.help {
|
||||
@@ -431,9 +559,9 @@ fn handle_led_power(
|
||||
println!("{}\n", power.self_usage());
|
||||
println!("Commands available");
|
||||
|
||||
if let Some(cmdlist) = LedPowerCommand::command_list() {
|
||||
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_string()).collect();
|
||||
for command in commands.iter() {
|
||||
if let Some(cmdlist) = LedPowerCommand2::command_list() {
|
||||
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
|
||||
for command in &commands {
|
||||
println!("{}", command);
|
||||
}
|
||||
}
|
||||
@@ -448,140 +576,73 @@ fn handle_led_power(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if supported.prod_id != AuraDevice::X19B6 {
|
||||
println!("This option applies only to keyboards with product ID 0x19b6");
|
||||
}
|
||||
|
||||
let mut enabled: Vec<AuraDev19b6> = Vec::new();
|
||||
let mut disabled: Vec<AuraDev19b6> = Vec::new();
|
||||
let mut check = |e: Option<bool>, a: AuraDev19b6| {
|
||||
if let Some(arg) = e {
|
||||
if arg {
|
||||
enabled.push(a);
|
||||
} else {
|
||||
disabled.push(a);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match pow {
|
||||
// TODO: make this a macro or something
|
||||
aura_cli::SetAuraEnabled::Boot(arg) => {
|
||||
let mut enabled: Vec<AuraControl> = Vec::new();
|
||||
let mut disabled: Vec<AuraControl> = Vec::new();
|
||||
arg.keyboard.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::BootKeyb)
|
||||
} else {
|
||||
disabled.push(AuraControl::BootKeyb)
|
||||
}
|
||||
});
|
||||
arg.logo.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::BootLogo)
|
||||
} else {
|
||||
disabled.push(AuraControl::BootLogo)
|
||||
}
|
||||
});
|
||||
arg.lightbar.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::BootBar)
|
||||
} else {
|
||||
disabled.push(AuraControl::BootBar)
|
||||
}
|
||||
});
|
||||
if !enabled.is_empty() {
|
||||
dbus.proxies().led().set_leds_enabled(enabled)?;
|
||||
}
|
||||
if !disabled.is_empty() {
|
||||
dbus.proxies().led().set_leds_disabled(disabled)?;
|
||||
}
|
||||
check(arg.keyboard, AuraDev19b6::BootKeyb);
|
||||
check(arg.logo, AuraDev19b6::BootLogo);
|
||||
check(arg.lightbar, AuraDev19b6::BootBar);
|
||||
check(arg.lid, AuraDev19b6::AwakeLid);
|
||||
}
|
||||
aura_cli::SetAuraEnabled::Sleep(arg) => {
|
||||
let mut enabled: Vec<AuraControl> = Vec::new();
|
||||
let mut disabled: Vec<AuraControl> = Vec::new();
|
||||
arg.keyboard.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::SleepKeyb)
|
||||
} else {
|
||||
disabled.push(AuraControl::SleepKeyb)
|
||||
}
|
||||
});
|
||||
arg.logo.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::SleepLogo)
|
||||
} else {
|
||||
disabled.push(AuraControl::SleepLogo)
|
||||
}
|
||||
});
|
||||
arg.lightbar.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::SleepBar)
|
||||
} else {
|
||||
disabled.push(AuraControl::SleepBar)
|
||||
}
|
||||
});
|
||||
if !enabled.is_empty() {
|
||||
dbus.proxies().led().set_leds_enabled(enabled)?;
|
||||
}
|
||||
if !disabled.is_empty() {
|
||||
dbus.proxies().led().set_leds_disabled(disabled)?;
|
||||
}
|
||||
check(arg.keyboard, AuraDev19b6::SleepKeyb);
|
||||
check(arg.logo, AuraDev19b6::SleepLogo);
|
||||
check(arg.lightbar, AuraDev19b6::SleepBar);
|
||||
check(arg.lid, AuraDev19b6::SleepLid);
|
||||
}
|
||||
aura_cli::SetAuraEnabled::Awake(arg) => {
|
||||
let mut enabled: Vec<AuraControl> = Vec::new();
|
||||
let mut disabled: Vec<AuraControl> = Vec::new();
|
||||
arg.keyboard.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::AwakeKeyb)
|
||||
} else {
|
||||
disabled.push(AuraControl::AwakeKeyb)
|
||||
}
|
||||
});
|
||||
arg.logo.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::AwakeLogo)
|
||||
} else {
|
||||
disabled.push(AuraControl::AwakeLogo)
|
||||
}
|
||||
});
|
||||
arg.lightbar.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::AwakeBar)
|
||||
} else {
|
||||
disabled.push(AuraControl::AwakeBar)
|
||||
}
|
||||
});
|
||||
if !enabled.is_empty() {
|
||||
dbus.proxies().led().set_leds_enabled(enabled)?;
|
||||
}
|
||||
if !disabled.is_empty() {
|
||||
dbus.proxies().led().set_leds_disabled(disabled)?;
|
||||
}
|
||||
check(arg.keyboard, AuraDev19b6::AwakeKeyb);
|
||||
check(arg.logo, AuraDev19b6::AwakeLogo);
|
||||
check(arg.lightbar, AuraDev19b6::AwakeBar);
|
||||
check(arg.lid, AuraDev19b6::AwakeLid);
|
||||
}
|
||||
aura_cli::SetAuraEnabled::Shutdown(arg) => {
|
||||
let mut enabled: Vec<AuraControl> = Vec::new();
|
||||
let mut disabled: Vec<AuraControl> = Vec::new();
|
||||
arg.keyboard.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::ShutdownKeyb)
|
||||
} else {
|
||||
disabled.push(AuraControl::ShutdownKeyb)
|
||||
}
|
||||
});
|
||||
arg.logo.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::ShutdownLogo)
|
||||
} else {
|
||||
disabled.push(AuraControl::ShutdownLogo)
|
||||
}
|
||||
});
|
||||
arg.lightbar.map(|v| {
|
||||
if v {
|
||||
enabled.push(AuraControl::ShutdownBar)
|
||||
} else {
|
||||
disabled.push(AuraControl::ShutdownBar)
|
||||
}
|
||||
});
|
||||
if !enabled.is_empty() {
|
||||
dbus.proxies().led().set_leds_enabled(enabled)?;
|
||||
}
|
||||
if !disabled.is_empty() {
|
||||
dbus.proxies().led().set_leds_disabled(disabled)?;
|
||||
}
|
||||
check(arg.keyboard, AuraDev19b6::ShutdownKeyb);
|
||||
check(arg.logo, AuraDev19b6::ShutdownLogo);
|
||||
check(arg.lightbar, AuraDev19b6::ShutdownBar);
|
||||
check(arg.lid, AuraDev19b6::ShutdownBar);
|
||||
}
|
||||
}
|
||||
|
||||
if !enabled.is_empty() {
|
||||
let data = AuraPowerDev {
|
||||
tuf: vec![],
|
||||
x1866: vec![],
|
||||
x19b6: enabled,
|
||||
};
|
||||
dbus.proxies().led().set_leds_power(data, true)?;
|
||||
}
|
||||
|
||||
if !disabled.is_empty() {
|
||||
let data = AuraPowerDev {
|
||||
tuf: vec![],
|
||||
x1866: vec![],
|
||||
x19b6: disabled,
|
||||
};
|
||||
dbus.proxies().led().set_leds_power(data, false)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_profile(
|
||||
dbus: &RogDbusClientBlocking,
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
supported: &PlatformProfileFunctions,
|
||||
cmd: &ProfileCommand,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -610,7 +671,9 @@ fn handle_profile(
|
||||
|
||||
if cmd.list {
|
||||
let res = dbus.proxies().profile().profiles()?;
|
||||
res.iter().for_each(|p| println!("{:?}", p));
|
||||
for p in &res {
|
||||
println!("{:?}", p);
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.profile_get {
|
||||
@@ -622,7 +685,7 @@ fn handle_profile(
|
||||
}
|
||||
|
||||
fn handle_fan_curve(
|
||||
dbus: &RogDbusClientBlocking,
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
supported: &PlatformProfileFunctions,
|
||||
cmd: &FanCurveCommand,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -684,27 +747,27 @@ fn handle_fan_curve(
|
||||
}
|
||||
|
||||
fn handle_bios_option(
|
||||
dbus: &RogDbusClientBlocking,
|
||||
dbus: &RogDbusClientBlocking<'_>,
|
||||
supported: &RogBiosSupportedFunctions,
|
||||
cmd: &BiosCommand,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
{
|
||||
if (cmd.dedicated_gfx_set.is_none()
|
||||
&& !cmd.dedicated_gfx_get
|
||||
if (cmd.gpu_mux_mode_set.is_none()
|
||||
&& !cmd.gpu_mux_mode_get
|
||||
&& cmd.post_sound_set.is_none()
|
||||
&& !cmd.post_sound_get)
|
||||
&& !cmd.post_sound_get
|
||||
&& cmd.panel_overdrive_set.is_none()
|
||||
&& !cmd.panel_overdrive_get)
|
||||
|| cmd.help
|
||||
{
|
||||
println!("Missing arg or command\n");
|
||||
|
||||
let usage: Vec<String> = BiosCommand::usage()
|
||||
.lines()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
let usage: Vec<String> = BiosCommand::usage().lines().map(|s| s.to_owned()).collect();
|
||||
|
||||
for line in usage.iter().filter(|line| {
|
||||
line.contains("sound") && supported.post_sound_toggle
|
||||
|| line.contains("GPU") && supported.dedicated_gfx_toggle
|
||||
line.contains("sound") && supported.post_sound
|
||||
|| line.contains("GPU") && supported.gpu_mux
|
||||
|| line.contains("panel") && supported.panel_overdrive
|
||||
}) {
|
||||
println!("{}", line);
|
||||
}
|
||||
@@ -717,21 +780,25 @@ fn handle_bios_option(
|
||||
let res = dbus.proxies().rog_bios().post_boot_sound()? == 1;
|
||||
println!("Bios POST sound on: {}", res);
|
||||
}
|
||||
if let Some(opt) = cmd.dedicated_gfx_set {
|
||||
|
||||
if let Some(opt) = cmd.gpu_mux_mode_set {
|
||||
println!("Rebuilding initrd to include drivers");
|
||||
dbus.proxies().rog_bios().set_dedicated_graphic_mode(opt)?;
|
||||
dbus.proxies()
|
||||
.rog_bios()
|
||||
.set_gpu_mux_mode(GpuMode::from_mux(opt))?;
|
||||
println!("The mode change is not active until you reboot, on boot the bios will make the required change");
|
||||
if opt {
|
||||
println!(
|
||||
"NOTE: on reboot your display manager will be forced to use Nvidia drivers"
|
||||
);
|
||||
} else {
|
||||
println!("NOTE: after reboot you can then select regular graphics modes");
|
||||
}
|
||||
}
|
||||
if cmd.dedicated_gfx_get {
|
||||
let res = dbus.proxies().rog_bios().dedicated_graphic_mode()? == 1;
|
||||
println!("Bios dedicated GPU on: {}", res);
|
||||
if cmd.gpu_mux_mode_get {
|
||||
let res = dbus.proxies().rog_bios().gpu_mux_mode()?;
|
||||
println!("Bios GPU MUX: {:?}", res);
|
||||
}
|
||||
|
||||
if let Some(opt) = cmd.panel_overdrive_set {
|
||||
dbus.proxies().rog_bios().set_panel_od(opt)?;
|
||||
}
|
||||
if cmd.panel_overdrive_get {
|
||||
let res = dbus.proxies().rog_bios().panel_od()?;
|
||||
println!("Panel overdrive on: {}", res);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[package]
|
||||
name = "daemon-user"
|
||||
version = "1.3.0"
|
||||
license = "MPL-2.0"
|
||||
version.workspace = true
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
description = "Usermode daemon for user settings, anime, per-key lighting"
|
||||
|
||||
[lib]
|
||||
@@ -14,19 +15,17 @@ name = "asusd-user"
|
||||
path = "src/daemon.rs"
|
||||
|
||||
[dependencies]
|
||||
dirs.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
# serialisation
|
||||
serde = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_supported = { path = "../rog-supported" }
|
||||
rog_platform = { path = "../rog-platform" }
|
||||
|
||||
dirs = "^4.0"
|
||||
|
||||
zbus = "^2.2"
|
||||
zvariant = "^3.0"
|
||||
zvariant_derive = "^3.0"
|
||||
|
||||
smol = "^1.2"
|
||||
zbus.workspace = true
|
||||
|
||||
@@ -11,10 +11,12 @@ use std::{
|
||||
},
|
||||
};
|
||||
use std::{sync::Arc, thread::sleep, time::Instant};
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
use zvariant_derive::Type;
|
||||
use zbus::{
|
||||
dbus_interface,
|
||||
zvariant::{ObjectPath, Type},
|
||||
};
|
||||
|
||||
use crate::user_config::ConfigLoadSave;
|
||||
use crate::{error::Error, user_config::UserAnimeConfig};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
|
||||
@@ -101,7 +103,7 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
.write(output)
|
||||
.map_err(|e| AnimeError::Dbus(format!("{}", e)))
|
||||
.map(|_| false)
|
||||
})?;
|
||||
});
|
||||
}
|
||||
ActionData::Image(image) => {
|
||||
self.client
|
||||
@@ -122,10 +124,10 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
ActionData::AudioEq => {}
|
||||
ActionData::SystemInfo => {}
|
||||
ActionData::TimeDate => {}
|
||||
ActionData::Matrix => {}
|
||||
ActionData::AudioEq
|
||||
| ActionData::SystemInfo
|
||||
| ActionData::TimeDate
|
||||
| ActionData::Matrix => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +143,7 @@ pub struct CtrlAnime<'a> {
|
||||
inner_early_return: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl<'a> CtrlAnime<'static> {
|
||||
impl CtrlAnime<'static> {
|
||||
pub fn new(
|
||||
config: Arc<Mutex<UserAnimeConfig>>,
|
||||
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
|
||||
@@ -183,7 +185,7 @@ impl CtrlAnime<'static> {
|
||||
pub fn insert_asus_gif(
|
||||
&mut self,
|
||||
index: u32,
|
||||
file: String,
|
||||
file: &str,
|
||||
time: Timer,
|
||||
brightness: f32,
|
||||
) -> zbus::fdo::Result<String> {
|
||||
@@ -221,7 +223,7 @@ impl CtrlAnime<'static> {
|
||||
pub fn insert_image_gif(
|
||||
&mut self,
|
||||
index: u32,
|
||||
file: String,
|
||||
file: &str,
|
||||
scale: f32,
|
||||
angle: f32,
|
||||
xy: (f32, f32),
|
||||
@@ -267,7 +269,7 @@ impl CtrlAnime<'static> {
|
||||
pub fn insert_image(
|
||||
&mut self,
|
||||
index: u32,
|
||||
file: String,
|
||||
file: &str,
|
||||
scale: f32,
|
||||
angle: f32,
|
||||
xy: (f32, f32),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_aura::layouts::KeyLayout;
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use rog_user::{
|
||||
ctrl_anime::{CtrlAnime, CtrlAnimeInner},
|
||||
@@ -6,64 +7,104 @@ use rog_user::{
|
||||
DBUS_NAME,
|
||||
};
|
||||
use smol::Executor;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::{fs::OpenOptions, io::Read, path::PathBuf, sync::Arc};
|
||||
use zbus::Connection;
|
||||
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
#[cfg(not(feature = "local_data"))]
|
||||
const DATA_DIR: &str = "/usr/share/rog-gui/";
|
||||
#[cfg(feature = "local_data")]
|
||||
const DATA_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
||||
const BOARD_NAME: &str = "/sys/class/dmi/id/board_name";
|
||||
|
||||
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);
|
||||
println!("rog-platform v{}", rog_platform::VERSION);
|
||||
|
||||
let (client, _) = RogDbusClientBlocking::new()?;
|
||||
let supported = client.proxies().supported().supported_functions()?;
|
||||
|
||||
let mut config = UserConfig::new();
|
||||
config.load_config()?;
|
||||
config.load()?;
|
||||
|
||||
let executor = Executor::new();
|
||||
|
||||
let early_return = Arc::new(AtomicBool::new(false));
|
||||
// Set up the anime data and run loop/thread
|
||||
if supported.anime_ctrl.0 {
|
||||
let anime_type = get_anime_type()?;
|
||||
let anime_config = UserAnimeConfig::load_config(config.active_anime)?;
|
||||
let anime = anime_config.create_anime(anime_type)?;
|
||||
let anime_config = Arc::new(Mutex::new(anime_config));
|
||||
if let Some(cfg) = config.active_anime {
|
||||
let anime_type = get_anime_type()?;
|
||||
let anime_config = UserAnimeConfig::load(cfg)?;
|
||||
let anime = anime_config.create(anime_type)?;
|
||||
let anime_config = Arc::new(Mutex::new(anime_config));
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
// Create server
|
||||
let mut connection = Connection::session().await.unwrap();
|
||||
connection.request_name(DBUS_NAME).await.unwrap();
|
||||
|
||||
// Inner behind mutex required for thread safety
|
||||
let inner = Arc::new(Mutex::new(
|
||||
CtrlAnimeInner::new(anime, client, early_return.clone()).unwrap(),
|
||||
));
|
||||
// Need new client object for dbus control part
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let anime_control =
|
||||
CtrlAnime::new(anime_config, inner.clone(), client, early_return).unwrap();
|
||||
anime_control.add_to_server(&mut connection).await;
|
||||
loop {
|
||||
if let Ok(inner) = inner.clone().try_lock() {
|
||||
inner.run().ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
// if supported.keyboard_led.per_key_led_mode {
|
||||
if let Some(cfg) = config.active_aura {
|
||||
let mut aura_config = UserAuraConfig::load(cfg)?;
|
||||
|
||||
// Find and load a matching layout for laptop
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(PathBuf::from(BOARD_NAME))
|
||||
.map_err(|e| {
|
||||
println!("{BOARD_NAME}, {e}");
|
||||
e
|
||||
})?;
|
||||
let mut board_name = String::new();
|
||||
file.read_to_string(&mut board_name)?;
|
||||
|
||||
let layout = KeyLayout::find_layout(board_name.as_str(), PathBuf::from(DATA_DIR))
|
||||
.map_err(|e| {
|
||||
println!("{BOARD_NAME}, {e}");
|
||||
})
|
||||
.unwrap_or_else(|_| KeyLayout::ga401_layout());
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
// Create server
|
||||
let mut connection = Connection::session().await.unwrap();
|
||||
connection.request_name(DBUS_NAME).await.unwrap();
|
||||
|
||||
// Inner behind mutex required for thread safety
|
||||
let inner = Arc::new(Mutex::new(
|
||||
CtrlAnimeInner::new(anime, client, early_return.clone()).unwrap(),
|
||||
));
|
||||
// Need new client object for dbus control part
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let anime_control =
|
||||
CtrlAnime::new(anime_config, inner.clone(), client, early_return).unwrap();
|
||||
anime_control.add_to_server(&mut connection).await;
|
||||
// let connection = Connection::session().await.unwrap();
|
||||
// connection.request_name(DBUS_NAME).await.unwrap();
|
||||
|
||||
loop {
|
||||
if let Ok(inner) = inner.clone().try_lock() {
|
||||
inner.run().ok();
|
||||
}
|
||||
aura_config.aura.next_state(&layout);
|
||||
let packets = aura_config.aura.create_packets();
|
||||
|
||||
client.proxies().led().per_key_raw(packets).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(33));
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
// if supported.keyboard_led.per_key_led_mode {
|
||||
// executor
|
||||
// .spawn(async move {
|
||||
// //
|
||||
// })
|
||||
// .detach();
|
||||
// }
|
||||
|
||||
loop {
|
||||
|
||||
@@ -13,7 +13,7 @@ pub enum Error {
|
||||
|
||||
impl fmt::Display for Error {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
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"),
|
||||
|
||||
@@ -5,28 +5,21 @@ use std::{
|
||||
};
|
||||
|
||||
use rog_anime::{ActionLoader, AnimTime, AnimeType, Fade, Sequences, Vec2};
|
||||
use rog_aura::{keys::Key, Breathe, Colour, Effect, Flicker, LedType, Speed, Static};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct UserAnimeConfig {
|
||||
pub name: String,
|
||||
pub anime: Vec<ActionLoader>,
|
||||
}
|
||||
pub trait ConfigLoadSave<T: DeserializeOwned + serde::Serialize> {
|
||||
fn name(&self) -> String;
|
||||
|
||||
impl UserAnimeConfig {
|
||||
pub fn create_anime(&self, anime_type: AnimeType) -> Result<Sequences, Error> {
|
||||
let mut seq = Sequences::new(anime_type);
|
||||
fn default_with_name(name: String) -> T;
|
||||
|
||||
for (idx, action) in self.anime.iter().enumerate() {
|
||||
seq.insert(idx, action)?;
|
||||
}
|
||||
|
||||
Ok(seq)
|
||||
}
|
||||
|
||||
pub fn write(&self) -> Result<(), Error> {
|
||||
fn write(&self) -> Result<(), Error>
|
||||
where
|
||||
Self: serde::Serialize,
|
||||
{
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
@@ -37,7 +30,7 @@ impl UserAnimeConfig {
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
let name = self.name.clone();
|
||||
let name = self.name();
|
||||
path.push(name + ".cfg");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
@@ -51,7 +44,7 @@ impl UserAnimeConfig {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_config(name: String) -> Result<UserAnimeConfig, Error> {
|
||||
fn load(name: String) -> Result<T, Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
@@ -75,14 +68,11 @@ impl UserAnimeConfig {
|
||||
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
let default = UserAnimeConfig {
|
||||
name,
|
||||
..Default::default()
|
||||
};
|
||||
let default = Self::default_with_name(name);
|
||||
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) {
|
||||
} else if let Ok(data) = serde_json::from_str::<T>(&buf) {
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
@@ -90,10 +80,41 @@ impl UserAnimeConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct UserAnimeConfig {
|
||||
pub name: String,
|
||||
pub anime: Vec<ActionLoader>,
|
||||
}
|
||||
|
||||
impl UserAnimeConfig {
|
||||
pub fn create(&self, anime_type: AnimeType) -> Result<Sequences, Error> {
|
||||
let mut seq = Sequences::new(anime_type);
|
||||
|
||||
for (idx, action) in self.anime.iter().enumerate() {
|
||||
seq.insert(idx, action)?;
|
||||
}
|
||||
|
||||
Ok(seq)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigLoadSave<UserAnimeConfig> for UserAnimeConfig {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn default_with_name(name: String) -> Self {
|
||||
UserAnimeConfig {
|
||||
name,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserAnimeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "default".to_string(),
|
||||
name: "default".to_owned(),
|
||||
anime: vec![
|
||||
ActionLoader::AsusImage {
|
||||
file: "/usr/share/asusd/anime/custom/diagonal-template.png".into(),
|
||||
@@ -151,20 +172,91 @@ impl Default for UserAnimeConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct UserAuraConfig {
|
||||
pub name: String,
|
||||
pub aura: rog_aura::Sequences,
|
||||
}
|
||||
|
||||
impl ConfigLoadSave<UserAuraConfig> for UserAuraConfig {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn default_with_name(name: String) -> Self {
|
||||
UserAuraConfig {
|
||||
name,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserAuraConfig {
|
||||
fn default() -> Self {
|
||||
let mut seq = rog_aura::Sequences::new();
|
||||
let mut key = Effect::Breathe(Breathe::new(
|
||||
LedType::Key(Key::W),
|
||||
Colour(255, 0, 20),
|
||||
Colour(20, 255, 0),
|
||||
Speed::Low,
|
||||
));
|
||||
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::A));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::S));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::D));
|
||||
seq.push(key);
|
||||
|
||||
let key = Effect::Breathe(Breathe::new(
|
||||
LedType::Key(Key::F),
|
||||
Colour(255, 0, 0),
|
||||
Colour(255, 0, 0),
|
||||
Speed::High,
|
||||
));
|
||||
seq.push(key);
|
||||
|
||||
let mut key = Effect::Static(Static::new(LedType::Key(Key::RCtrl), Colour(0, 0, 255)));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::LCtrl));
|
||||
seq.push(key.clone());
|
||||
key.set_led_type(LedType::Key(Key::Esc));
|
||||
seq.push(key);
|
||||
|
||||
let key = Effect::Flicker(Flicker::new(
|
||||
LedType::Key(Key::N9),
|
||||
Colour(0, 0, 255),
|
||||
80,
|
||||
40,
|
||||
));
|
||||
seq.push(key);
|
||||
|
||||
Self {
|
||||
name: "default".to_owned(),
|
||||
aura: seq,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct UserConfig {
|
||||
/// Name of active anime config file in the user config directory
|
||||
pub active_anime: String,
|
||||
pub active_anime: Option<String>,
|
||||
/// Name of active aura config file in the user config directory
|
||||
pub active_aura: Option<String>,
|
||||
}
|
||||
|
||||
impl UserConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
active_anime: "anime-default".to_string(),
|
||||
active_anime: Some("anime-default".to_owned()),
|
||||
active_aura: Some("aura-default".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(&mut self) -> Result<(), Error> {
|
||||
pub fn load(&mut self) -> Result<(), Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
@@ -192,6 +284,7 @@ impl UserConfig {
|
||||
file.write_all(json.as_bytes())?;
|
||||
} else if let Ok(data) = serde_json::from_str::<UserConfig>(&buf) {
|
||||
self.active_anime = data.active_anime;
|
||||
self.active_aura = data.active_aura;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "daemon"
|
||||
version = "4.2.0"
|
||||
license = "MPL-2.0"
|
||||
version.workspace = true
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
repository = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
||||
homepage = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
||||
description = "A daemon app for ASUS GX502 and similar laptops to control missing features"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "daemon"
|
||||
@@ -20,29 +20,27 @@ path = "src/daemon.rs"
|
||||
[dependencies]
|
||||
rog_anime = { path = "../rog-anime", features = ["dbus"] }
|
||||
rog_aura = { path = "../rog-aura", features = ["dbus"] }
|
||||
rog_supported = { path = "../rog-supported" }
|
||||
rog_platform = { path = "../rog-platform" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
|
||||
async-trait = "^0.1"
|
||||
smol = "^1.2"
|
||||
|
||||
rusb = "^0.9"
|
||||
udev = "^0.6"
|
||||
async-trait.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
# cli and logging
|
||||
log = "^0.4"
|
||||
env_logger = "^0.9"
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
zbus = "^2.2"
|
||||
zvariant = "^3.2"
|
||||
logind-zbus = { version = "^3.0" } #, default-features = false, features = ["non_blocking"] }
|
||||
zbus.workspace = true
|
||||
logind-zbus.workspace = true
|
||||
|
||||
# serialisation
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
toml = "^0.5.8"
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
# Device control
|
||||
sysfs-class = "^0.1.2" # used for backlight control and baord ID
|
||||
sysfs-class.workspace = true # used for backlight control and baord ID
|
||||
|
||||
concat-idents.workspace = true
|
||||
|
||||
@@ -6,16 +6,19 @@ use std::path::PathBuf;
|
||||
|
||||
pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
/// Save charge limit for restoring on boot
|
||||
pub bat_charge_limit: u8,
|
||||
pub panel_od: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new() -> Self {
|
||||
Config {
|
||||
bat_charge_limit: 100,
|
||||
panel_od: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +42,7 @@ impl Config {
|
||||
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
|
||||
CONFIG_PATH, CONFIG_PATH
|
||||
);
|
||||
let cfg_old = CONFIG_PATH.to_string() + "-old";
|
||||
let cfg_old = CONFIG_PATH.to_owned() + "-old";
|
||||
std::fs::rename(CONFIG_PATH, cfg_old).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
@@ -49,7 +52,7 @@ impl Config {
|
||||
config = Self::new();
|
||||
}
|
||||
} else {
|
||||
config = Self::new()
|
||||
config = Self::new();
|
||||
}
|
||||
config.write();
|
||||
config
|
||||
@@ -58,7 +61,7 @@ impl Config {
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.open(CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
|
||||
@@ -86,25 +86,25 @@ impl AnimeConfigCached {
|
||||
anime_type: AnimeType,
|
||||
) -> Result<(), AnimeError> {
|
||||
let mut sys = Vec::with_capacity(config.system.len());
|
||||
for ani in config.system.iter() {
|
||||
for ani in &config.system {
|
||||
sys.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.system = sys;
|
||||
|
||||
let mut boot = Vec::with_capacity(config.boot.len());
|
||||
for ani in config.boot.iter() {
|
||||
for ani in &config.boot {
|
||||
boot.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.boot = boot;
|
||||
|
||||
let mut wake = Vec::with_capacity(config.wake.len());
|
||||
for ani in config.wake.iter() {
|
||||
for ani in &config.wake {
|
||||
wake.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.wake = wake;
|
||||
|
||||
let mut shutdown = Vec::with_capacity(config.shutdown.len());
|
||||
for ani in config.shutdown.iter() {
|
||||
for ani in &config.shutdown {
|
||||
shutdown.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.shutdown = shutdown;
|
||||
@@ -145,7 +145,7 @@ impl AnimeConfig {
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&ANIME_CONFIG_PATH)
|
||||
.open(ANIME_CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
@@ -249,7 +249,7 @@ impl AnimeConfig {
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&ANIME_CONFIG_PATH)
|
||||
.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) {
|
||||
|
||||
@@ -1,48 +1,31 @@
|
||||
pub mod config;
|
||||
pub mod zbus;
|
||||
|
||||
use ::zbus::Connection;
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use rog_anime::{
|
||||
error::AnimeError,
|
||||
usb::{
|
||||
find_node, get_anime_type, pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on,
|
||||
pkts_for_init, PROD_ID, VENDOR_ID,
|
||||
},
|
||||
ActionData, AnimeDataBuffer, AnimePacketType, AnimeType,
|
||||
};
|
||||
use rog_supported::AnimeSupportedFunctions;
|
||||
use rusb::{Device, DeviceHandle};
|
||||
use smol::{stream::StreamExt, Executor};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
error::Error,
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
thread::sleep,
|
||||
};
|
||||
use std::{
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{error::RogError, GetSupported};
|
||||
/// Implements `CtrlTask`, Reloadable, `ZbusRun`
|
||||
pub mod trait_impls;
|
||||
|
||||
use self::config::{AnimeConfig, AnimeConfigCached};
|
||||
use crate::{error::RogError, GetSupported};
|
||||
use ::zbus::export::futures_util::lock::Mutex;
|
||||
use log::{error, info, warn};
|
||||
use rog_anime::{
|
||||
error::AnimeError,
|
||||
usb::{get_anime_type, pkt_for_flush, pkts_for_init},
|
||||
ActionData, AnimeDataBuffer, AnimePacketType, AnimeType,
|
||||
};
|
||||
use rog_platform::{hid_raw::HidRaw, supported::AnimeSupportedFunctions, usb_raw::USBRaw};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{convert::TryFrom, error::Error, sync::Arc, thread::sleep};
|
||||
|
||||
impl GetSupported for CtrlAnime {
|
||||
type A = AnimeSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
AnimeSupportedFunctions(CtrlAnime::get_device(VENDOR_ID, PROD_ID).is_ok())
|
||||
AnimeSupportedFunctions(HidRaw::new("193b").is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnime {
|
||||
_node: String,
|
||||
node: USBRaw,
|
||||
anime_type: AnimeType,
|
||||
handle: RefCell<DeviceHandle<rusb::GlobalContext>>,
|
||||
cache: AnimeConfigCached,
|
||||
config: AnimeConfig,
|
||||
// set to force thread to exit
|
||||
@@ -54,63 +37,32 @@ pub struct CtrlAnime {
|
||||
impl CtrlAnime {
|
||||
#[inline]
|
||||
pub fn new(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
|
||||
let node = find_node("193b")?;
|
||||
let node = USBRaw::new(0x193b)?;
|
||||
let anime_type = get_anime_type()?;
|
||||
let device = Self::get_dev_handle()?;
|
||||
|
||||
info!("Device has an AniMe Matrix display");
|
||||
let mut cache = AnimeConfigCached::default();
|
||||
cache.init_from_config(&config, anime_type)?;
|
||||
|
||||
let ctrl = CtrlAnime {
|
||||
_node: node,
|
||||
node,
|
||||
anime_type,
|
||||
handle: RefCell::new(device),
|
||||
cache,
|
||||
config,
|
||||
thread_exit: Arc::new(AtomicBool::new(false)),
|
||||
thread_running: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
ctrl.do_initialization();
|
||||
ctrl.do_initialization()?;
|
||||
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn get_dev_handle() -> Result<DeviceHandle<rusb::GlobalContext>, Box<dyn Error>> {
|
||||
// We don't expect this ID to ever change
|
||||
let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
|
||||
|
||||
let mut device = device.open()?;
|
||||
device.reset()?;
|
||||
|
||||
device.set_auto_detach_kernel_driver(true).map_err(|err| {
|
||||
error!("Auto-detach kernel driver failed: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
device.claim_interface(0).map_err(|err| {
|
||||
error!("Could not claim device interface: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
fn get_device(vendor: u16, product: u16) -> Result<Device<rusb::GlobalContext>, rusb::Error> {
|
||||
for device in rusb::devices()?.iter() {
|
||||
let device_desc = device.device_descriptor()?;
|
||||
if device_desc.vendor_id() == vendor && device_desc.product_id() == product {
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
Err(rusb::Error::NoDevice)
|
||||
}
|
||||
// let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
|
||||
|
||||
/// 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.
|
||||
/// 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");
|
||||
@@ -123,6 +75,7 @@ impl CtrlAnime {
|
||||
|
||||
// The only reason for this outer thread is to prevent blocking while waiting for the
|
||||
// next spawned thread to exit
|
||||
// TODO: turn this in to async task (maybe? COuld still risk blocking main thread)
|
||||
std::thread::Builder::new()
|
||||
.name("AniMe system thread start".into())
|
||||
.spawn(move || {
|
||||
@@ -133,7 +86,7 @@ impl CtrlAnime {
|
||||
let thread_running;
|
||||
let anime_type;
|
||||
loop {
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
if let Some(lock) = inner.try_lock() {
|
||||
thread_exit = lock.thread_exit.clone();
|
||||
thread_running = lock.thread_running.clone();
|
||||
anime_type = lock.anime_type;
|
||||
@@ -153,13 +106,13 @@ impl CtrlAnime {
|
||||
|
||||
'main: loop {
|
||||
thread_running.store(true, Ordering::SeqCst);
|
||||
for action in actions.iter() {
|
||||
for action in &actions {
|
||||
if thread_exit.load(Ordering::SeqCst) {
|
||||
break 'main;
|
||||
}
|
||||
match action {
|
||||
ActionData::Animation(frames) => {
|
||||
if let Err(err) = rog_anime::run_animation(frames, &|frame| {
|
||||
rog_anime::run_animation(frames, &|frame| {
|
||||
if thread_exit.load(Ordering::Acquire) {
|
||||
info!("rog-anime: frame-loop was asked to exit");
|
||||
return Ok(true); // Do safe exit
|
||||
@@ -167,29 +120,38 @@ impl CtrlAnime {
|
||||
inner
|
||||
.try_lock()
|
||||
.map(|lock| {
|
||||
lock.write_data_buffer(frame);
|
||||
lock.write_data_buffer(frame)
|
||||
.map_err(|err| {
|
||||
warn!(
|
||||
"rog_anime::run_animation:callback {}",
|
||||
err
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
false // Don't exit yet
|
||||
})
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
AnimeError::NoFrames
|
||||
})
|
||||
}) {
|
||||
warn!("rog_anime::run_animation:Animation {}", err);
|
||||
break 'main;
|
||||
};
|
||||
.map_or_else(
|
||||
|| {
|
||||
warn!("rog_anime::run_animation:callback failed");
|
||||
Err(AnimeError::NoFrames)
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
});
|
||||
}
|
||||
ActionData::Image(image) => {
|
||||
once = false;
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
if let Some(lock) = inner.try_lock() {
|
||||
lock.write_data_buffer(image.as_ref().clone())
|
||||
.map_err(|e| error!("{}", e))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
ActionData::Pause(duration) => sleep(*duration),
|
||||
ActionData::AudioEq => {}
|
||||
ActionData::SystemInfo => {}
|
||||
ActionData::TimeDate => {}
|
||||
ActionData::Matrix => {}
|
||||
ActionData::AudioEq
|
||||
| ActionData::SystemInfo
|
||||
| ActionData::TimeDate
|
||||
| ActionData::Matrix => {}
|
||||
}
|
||||
}
|
||||
if thread_exit.load(Ordering::SeqCst) {
|
||||
@@ -200,10 +162,17 @@ impl CtrlAnime {
|
||||
}
|
||||
}
|
||||
// Clear the display on exit
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
let data =
|
||||
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()]);
|
||||
lock.write_data_buffer(data);
|
||||
if let Some(lock) = inner.try_lock() {
|
||||
if let Ok(data) =
|
||||
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()])
|
||||
.map_err(|e| error!("{}", e))
|
||||
{
|
||||
lock.write_data_buffer(data)
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
// Loop ended, set the atmonics
|
||||
thread_running.store(false, Ordering::SeqCst);
|
||||
@@ -213,163 +182,28 @@ impl CtrlAnime {
|
||||
.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.data_mut()[7..].iter_mut() {
|
||||
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> {
|
||||
for led in buffer.data_mut().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);
|
||||
let data = AnimePacketType::try_from(buffer)?;
|
||||
for row in &data {
|
||||
self.node.write_bytes(row)?;
|
||||
}
|
||||
self.write_bytes(&pkt_for_flush());
|
||||
self.node.write_bytes(&pkt_for_flush())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_initialization(&self) {
|
||||
fn do_initialization(&self) -> Result<(), RogError> {
|
||||
let pkts = pkts_for_init();
|
||||
self.write_bytes(&pkts[0]);
|
||||
self.write_bytes(&pkts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeTask {
|
||||
inner: Arc<Mutex<CtrlAnime>>,
|
||||
}
|
||||
|
||||
impl CtrlAnimeTask {
|
||||
pub async fn new(inner: Arc<Mutex<CtrlAnime>>) -> CtrlAnimeTask {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::CtrlTask for CtrlAnimeTask {
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("CtrlAnimeTask could not create dbus connection");
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlAnimeTask could not create ManagerProxy");
|
||||
|
||||
let run_action =
|
||||
|start: bool, lock: MutexGuard<CtrlAnime>, inner: Arc<Mutex<CtrlAnime>>| {
|
||||
if start {
|
||||
info!("CtrlAnimeTask running sleep animation");
|
||||
CtrlAnime::run_thread(inner.clone(), lock.cache.shutdown.clone(), true);
|
||||
} else {
|
||||
info!("CtrlAnimeTask running wake animation");
|
||||
CtrlAnime::run_thread(inner.clone(), lock.cache.wake.clone(), true);
|
||||
}
|
||||
};
|
||||
|
||||
let inner = self.inner.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_sleep().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
// Loop is required to try an attempt to get the mutex *without* blocking
|
||||
// other threads - it is possible to end up with deadlocks otherwise.
|
||||
loop {
|
||||
if let Ok(lock) = inner.clone().try_lock() {
|
||||
run_action(args.start, lock, inner.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlAnimeTask could not create ManagerProxy");
|
||||
|
||||
let inner = self.inner.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_shutdown().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
loop {
|
||||
if let Ok(lock) = inner.clone().try_lock() {
|
||||
run_action(args.start, lock, inner.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeReloader(pub Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl crate::Reloadable for CtrlAnimeReloader {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_on(lock.config.awake_enabled));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
lock.write_bytes(&pkt_for_set_boot(lock.config.boot_anim_enabled));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
|
||||
let action = lock.cache.boot.clone();
|
||||
CtrlAnime::run_thread(self.0.clone(), action, true);
|
||||
}
|
||||
self.node.write_bytes(&pkts[0])?;
|
||||
self.node.write_bytes(&pkts[1])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
216
daemon/src/ctrl_anime/trait_impls.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
use super::CtrlAnime;
|
||||
use crate::error::RogError;
|
||||
use async_trait::async_trait;
|
||||
use log::{info, warn};
|
||||
use rog_anime::{
|
||||
usb::{pkt_for_apply, pkt_for_set_boot, pkt_for_set_on},
|
||||
AnimeDataBuffer, AnimePowerStates,
|
||||
};
|
||||
use std::sync::{atomic::Ordering, Arc};
|
||||
use zbus::{
|
||||
dbus_interface,
|
||||
export::futures_util::lock::{Mutex, MutexGuard},
|
||||
Connection, SignalContext,
|
||||
};
|
||||
|
||||
pub(super) const ZBUS_PATH: &str = "/org/asuslinux/Anime";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
/// The struct with the main dbus methods requires this trait
|
||||
#[async_trait]
|
||||
impl crate::ZbusRun for CtrlAnimeZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
// None of these calls can be guarnateed to succeed unless we loop until okay
|
||||
// If the try_lock *does* succeed then any other thread trying to lock will not grab it
|
||||
// until we finish.
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnimeZbus {
|
||||
/// Writes a data stream of length. Will force system thread to exit until it is restarted
|
||||
async fn write(&self, input: AnimeDataBuffer) -> zbus::fdo::Result<()> {
|
||||
let lock = self.0.lock().await;
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
lock.write_data_buffer(input).map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
err
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the global AniMe brightness
|
||||
async fn set_brightness(&self, bright: f32) {
|
||||
let mut lock = self.0.lock().await;
|
||||
let mut bright = bright;
|
||||
if bright < 0.0 {
|
||||
bright = 0.0;
|
||||
} else if bright > 1.0 {
|
||||
bright = 1.0;
|
||||
}
|
||||
lock.config.brightness = bright;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
/// Set whether the AniMe is displaying images/data
|
||||
async fn set_on_off(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>, status: bool) {
|
||||
let mut lock = self.0.lock().await;
|
||||
lock.node
|
||||
.write_bytes(&pkt_for_set_on(status))
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
lock.config.awake_enabled = status;
|
||||
lock.config.write();
|
||||
|
||||
Self::notify_power_states(
|
||||
&ctxt,
|
||||
AnimePowerStates {
|
||||
brightness: lock.config.brightness.floor() as u8,
|
||||
enabled: lock.config.awake_enabled,
|
||||
boot_anim_enabled: lock.config.boot_anim_enabled,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Set whether the AniMe will show boot, suspend, or off animations
|
||||
async fn set_boot_on_off(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>, on: bool) {
|
||||
let mut lock = self.0.lock().await;
|
||||
lock.node
|
||||
.write_bytes(&pkt_for_set_boot(on))
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
lock.node
|
||||
.write_bytes(&pkt_for_apply())
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
lock.config.boot_anim_enabled = on;
|
||||
lock.config.write();
|
||||
|
||||
Self::notify_power_states(
|
||||
&ctxt,
|
||||
AnimePowerStates {
|
||||
brightness: lock.config.brightness.floor() as u8,
|
||||
enabled: lock.config.awake_enabled,
|
||||
boot_anim_enabled: lock.config.boot_anim_enabled,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// The main loop is the base system set action if the user isn't running
|
||||
/// the user daemon
|
||||
async fn run_main_loop(&self, start: bool) {
|
||||
if start {
|
||||
let lock = self.0.lock().await;
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get status of if the AniMe LEDs are on/displaying while system is awake
|
||||
#[dbus_interface(property)]
|
||||
async fn awake_enabled(&self) -> bool {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.awake_enabled
|
||||
}
|
||||
|
||||
/// Get the status of if factory system-status animations are enabled
|
||||
#[dbus_interface(property)]
|
||||
async fn boot_enabled(&self) -> bool {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.boot_anim_enabled
|
||||
}
|
||||
|
||||
/// Notify listeners of the status of AniMe LED power and factory system-status animations
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_power_states(
|
||||
ctxt: &SignalContext<'_>,
|
||||
data: AnimePowerStates,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::CtrlTask for CtrlAnimeZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let run_action =
|
||||
|start: bool, lock: MutexGuard<'_, CtrlAnime>, inner: Arc<Mutex<CtrlAnime>>| {
|
||||
if start {
|
||||
info!("CtrlAnimeTask running sleep animation");
|
||||
CtrlAnime::run_thread(inner, lock.cache.shutdown.clone(), true);
|
||||
} else {
|
||||
info!("CtrlAnimeTask running wake animation");
|
||||
CtrlAnime::run_thread(inner, lock.cache.wake.clone(), true);
|
||||
}
|
||||
};
|
||||
|
||||
let inner1 = self.0.clone();
|
||||
let inner2 = self.0.clone();
|
||||
let inner3 = self.0.clone();
|
||||
let inner4 = self.0.clone();
|
||||
self.create_sys_event_tasks(
|
||||
// Loop is required to try an attempt to get the mutex *without* blocking
|
||||
// other threads - it is possible to end up with deadlocks otherwise.
|
||||
move || loop {
|
||||
if let Some(lock) = inner1.try_lock() {
|
||||
run_action(true, lock, inner1.clone());
|
||||
break;
|
||||
}
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner2.try_lock() {
|
||||
run_action(false, lock, inner2.clone());
|
||||
break;
|
||||
}
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner3.try_lock() {
|
||||
run_action(true, lock, inner3.clone());
|
||||
break;
|
||||
}
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner4.try_lock() {
|
||||
run_action(false, lock, inner4.clone());
|
||||
break;
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Reloadable for CtrlAnimeZbus {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Some(lock) = self.0.try_lock() {
|
||||
lock.node
|
||||
.write_bytes(&pkt_for_set_on(lock.config.awake_enabled))?;
|
||||
lock.node.write_bytes(&pkt_for_apply())?;
|
||||
lock.node
|
||||
.write_bytes(&pkt_for_set_boot(lock.config.boot_anim_enabled))?;
|
||||
lock.node.write_bytes(&pkt_for_apply())?;
|
||||
|
||||
let action = lock.cache.boot.clone();
|
||||
CtrlAnime::run_thread(self.0.clone(), action, true);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use rog_anime::{
|
||||
usb::{pkt_for_apply, pkt_for_set_boot, pkt_for_set_on},
|
||||
AnimeDataBuffer, AnimePowerStates,
|
||||
};
|
||||
use zbus::{dbus_interface, Connection, SignalContext};
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use super::CtrlAnime;
|
||||
|
||||
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
/// The struct with the main dbus methods requires this trait
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlAnimeZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Anime", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
// None of these calls can be guarnateed to succeed unless we loop until okay
|
||||
// If the try_lock *does* succeed then any other thread trying to lock will not grab it
|
||||
// until we finish.
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnimeZbus {
|
||||
/// Writes a data stream of length. Will force system thread to exit until it is restarted
|
||||
fn write(&self, input: AnimeDataBuffer) {
|
||||
'outer: loop {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
lock.write_data_buffer(input);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the global AniMe brightness
|
||||
fn set_brightness(&self, bright: f32) {
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
let mut bright = bright;
|
||||
if bright < 0.0 {
|
||||
bright = 0.0
|
||||
} else if bright > 1.0 {
|
||||
bright = 1.0;
|
||||
}
|
||||
lock.config.brightness = bright;
|
||||
lock.config.write();
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the AniMe is displaying images/data
|
||||
async fn set_on_off(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>, status: bool) {
|
||||
let states;
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_on(status));
|
||||
lock.config.awake_enabled = status;
|
||||
lock.config.write();
|
||||
|
||||
states = Some(AnimePowerStates {
|
||||
brightness: lock.config.brightness.floor() as u8,
|
||||
enabled: lock.config.awake_enabled,
|
||||
boot_anim_enabled: lock.config.boot_anim_enabled,
|
||||
});
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
if let Some(state) = states {
|
||||
Self::notify_power_states(&ctxt, state).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the AniMe will show boot, suspend, or off animations
|
||||
async fn set_boot_on_off(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>, on: bool) {
|
||||
let states;
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_boot(on));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
lock.config.boot_anim_enabled = on;
|
||||
lock.config.write();
|
||||
|
||||
states = Some(AnimePowerStates {
|
||||
brightness: lock.config.brightness.floor() as u8,
|
||||
enabled: lock.config.awake_enabled,
|
||||
boot_anim_enabled: lock.config.boot_anim_enabled,
|
||||
});
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
if let Some(state) = states {
|
||||
Self::notify_power_states(&ctxt, state).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// The main loop is the base system set action if the user isn't running
|
||||
/// the user daemon
|
||||
fn run_main_loop(&self, start: bool) {
|
||||
if start {
|
||||
'outer: loop {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get status of if the AniMe LEDs are on/displaying while system is awake
|
||||
#[dbus_interface(property)]
|
||||
fn awake_enabled(&self) -> bool {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.config.awake_enabled;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Get the status of if factory system-status animations are enabled
|
||||
#[dbus_interface(property)]
|
||||
fn boot_enabled(&self) -> bool {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.config.boot_anim_enabled;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Notify listeners of the status of AniMe LED power and factory system-status animations
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_power_states(
|
||||
ctxt: &SignalContext<'_>,
|
||||
data: AnimePowerStates,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::laptops::LaptopLedData;
|
||||
use crate::laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES};
|
||||
use log::{error, warn};
|
||||
use rog_aura::usb::AuraControl;
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, LedBrightness};
|
||||
use rog_aura::usb::{AuraDev1866, AuraDev19b6, AuraDevTuf, AuraDevice, AuraPowerDev};
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Direction, LedBrightness, Speed, GRADIENT};
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use rog_platform::keyboard_led::KeyboardLed;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::fs::{File, OpenOptions};
|
||||
@@ -9,37 +11,176 @@ use std::io::{Read, Write};
|
||||
|
||||
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/aura.conf";
|
||||
|
||||
/// Enable/disable LED control in various states such as
|
||||
/// when the device is awake, suspended, shutting down or
|
||||
/// booting.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AuraPowerConfig {
|
||||
AuraDevTuf(HashSet<AuraDevTuf>),
|
||||
AuraDev1866(HashSet<AuraDev1866>),
|
||||
AuraDev19b6(HashSet<AuraDev19b6>),
|
||||
}
|
||||
|
||||
impl AuraPowerConfig {
|
||||
/// Invalid for TUF laptops
|
||||
pub fn to_bytes(control: &Self) -> [u8; 3] {
|
||||
match control {
|
||||
AuraPowerConfig::AuraDevTuf(_) => [0, 0, 0],
|
||||
AuraPowerConfig::AuraDev1866(c) => {
|
||||
let c: Vec<AuraDev1866> = c.iter().copied().collect();
|
||||
AuraDev1866::to_bytes(&c)
|
||||
}
|
||||
AuraPowerConfig::AuraDev19b6(c) => {
|
||||
let c: Vec<AuraDev19b6> = c.iter().copied().collect();
|
||||
AuraDev19b6::to_bytes(&c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_tuf_bool_array(control: &Self) -> Option<[bool; 5]> {
|
||||
if let Self::AuraDevTuf(c) = control {
|
||||
return Some([
|
||||
true,
|
||||
c.contains(&AuraDevTuf::Boot),
|
||||
c.contains(&AuraDevTuf::Awake),
|
||||
c.contains(&AuraDevTuf::Sleep),
|
||||
c.contains(&AuraDevTuf::Keyboard),
|
||||
]);
|
||||
}
|
||||
|
||||
if let Self::AuraDev1866(c) = control {
|
||||
return Some([
|
||||
true,
|
||||
c.contains(&AuraDev1866::Boot),
|
||||
c.contains(&AuraDev1866::Awake),
|
||||
c.contains(&AuraDev1866::Sleep),
|
||||
c.contains(&AuraDev1866::Keyboard),
|
||||
]);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_tuf(&mut self, power: AuraDevTuf, on: bool) {
|
||||
if let Self::AuraDevTuf(p) = self {
|
||||
if on {
|
||||
p.insert(power);
|
||||
} else {
|
||||
p.remove(&power);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn set_0x1866(&mut self, power: AuraDev1866, on: bool) {
|
||||
if let Self::AuraDev1866(p) = self {
|
||||
if on {
|
||||
p.insert(power);
|
||||
} else {
|
||||
p.remove(&power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_0x19b6(&mut self, power: AuraDev19b6, on: bool) {
|
||||
if let Self::AuraDev19b6(p) = self {
|
||||
if on {
|
||||
p.insert(power);
|
||||
} else {
|
||||
p.remove(&power);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuraPowerConfig> for AuraPowerDev {
|
||||
fn from(config: &AuraPowerConfig) -> Self {
|
||||
match config {
|
||||
AuraPowerConfig::AuraDevTuf(d) => AuraPowerDev {
|
||||
tuf: d.iter().copied().collect(),
|
||||
x1866: vec![],
|
||||
x19b6: vec![],
|
||||
},
|
||||
AuraPowerConfig::AuraDev1866(d) => AuraPowerDev {
|
||||
tuf: vec![],
|
||||
x1866: d.iter().copied().collect(),
|
||||
x19b6: vec![],
|
||||
},
|
||||
AuraPowerConfig::AuraDev19b6(d) => AuraPowerDev {
|
||||
tuf: vec![],
|
||||
x1866: vec![],
|
||||
x19b6: d.iter().copied().collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
// #[serde(default)]
|
||||
pub struct AuraConfig {
|
||||
pub brightness: LedBrightness,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub multizone: Option<BTreeMap<AuraModeNum, Vec<AuraEffect>>>,
|
||||
pub enabled: HashSet<AuraControl>,
|
||||
pub multizone_on: bool,
|
||||
pub enabled: AuraPowerConfig,
|
||||
}
|
||||
|
||||
impl Default for AuraConfig {
|
||||
fn default() -> Self {
|
||||
let mut prod_id = AuraDevice::Unknown;
|
||||
for prod in &ASUS_KEYBOARD_DEVICES {
|
||||
if HidRaw::new(prod).is_ok() {
|
||||
prod_id = AuraDevice::from(*prod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if prod_id == AuraDevice::Unknown {
|
||||
if let Ok(p) = KeyboardLed::new() {
|
||||
if p.has_kbd_rgb_mode() {
|
||||
prod_id = AuraDevice::Tuf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let enabled = if prod_id == AuraDevice::X19B6 {
|
||||
AuraPowerConfig::AuraDev19b6(HashSet::from([
|
||||
AuraDev19b6::BootLogo,
|
||||
AuraDev19b6::BootKeyb,
|
||||
AuraDev19b6::SleepLogo,
|
||||
AuraDev19b6::SleepKeyb,
|
||||
AuraDev19b6::AwakeLogo,
|
||||
AuraDev19b6::AwakeKeyb,
|
||||
AuraDev19b6::ShutdownLogo,
|
||||
AuraDev19b6::ShutdownKeyb,
|
||||
AuraDev19b6::BootBar,
|
||||
AuraDev19b6::AwakeBar,
|
||||
AuraDev19b6::SleepBar,
|
||||
AuraDev19b6::ShutdownBar,
|
||||
]))
|
||||
} else if prod_id == AuraDevice::Tuf {
|
||||
AuraPowerConfig::AuraDevTuf(HashSet::from([
|
||||
AuraDevTuf::Awake,
|
||||
AuraDevTuf::Boot,
|
||||
AuraDevTuf::Sleep,
|
||||
AuraDevTuf::Keyboard,
|
||||
]))
|
||||
} else {
|
||||
AuraPowerConfig::AuraDev1866(HashSet::from([
|
||||
AuraDev1866::Awake,
|
||||
AuraDev1866::Boot,
|
||||
AuraDev1866::Sleep,
|
||||
AuraDev1866::Keyboard,
|
||||
AuraDev1866::Lightbar,
|
||||
]))
|
||||
};
|
||||
|
||||
AuraConfig {
|
||||
brightness: LedBrightness::Med,
|
||||
current_mode: AuraModeNum::Static,
|
||||
builtins: BTreeMap::new(),
|
||||
multizone: None,
|
||||
enabled: HashSet::from([
|
||||
AuraControl::BootLogo,
|
||||
AuraControl::BootKeyb,
|
||||
AuraControl::SleepLogo,
|
||||
AuraControl::SleepKeyb,
|
||||
AuraControl::AwakeLogo,
|
||||
AuraControl::AwakeKeyb,
|
||||
AuraControl::ShutdownLogo,
|
||||
AuraControl::ShutdownKeyb,
|
||||
AuraControl::AwakeBar,
|
||||
AuraControl::BootBar,
|
||||
AuraControl::SleepBar,
|
||||
AuraControl::ShutdownBar,
|
||||
]),
|
||||
multizone_on: false,
|
||||
enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +192,7 @@ impl AuraConfig {
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&AURA_CONFIG_PATH)
|
||||
.open(AURA_CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
@@ -90,6 +231,27 @@ impl AuraConfig {
|
||||
config
|
||||
.builtins
|
||||
.insert(*n, AuraEffect::default_with_mode(*n));
|
||||
|
||||
if !support_data.multizone.is_empty() {
|
||||
let mut default = vec![];
|
||||
for (i, tmp) in support_data.multizone.iter().enumerate() {
|
||||
default.push(AuraEffect {
|
||||
mode: *n,
|
||||
zone: *tmp,
|
||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Left,
|
||||
});
|
||||
}
|
||||
if let Some(m) = config.multizone.as_mut() {
|
||||
m.insert(*n, default);
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*n, default);
|
||||
config.multizone = Some(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
@@ -102,14 +264,14 @@ impl AuraConfig {
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&AURA_CONFIG_PATH)
|
||||
.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)
|
||||
let x = serde_json::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", AURA_CONFIG_PATH));
|
||||
*self = x;
|
||||
}
|
||||
@@ -123,28 +285,33 @@ impl AuraConfig {
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
|
||||
/// Multipurpose, will accept AuraEffect with zones and put in the correct store
|
||||
/// Set the mode data, current mode, and if multizone enabled.
|
||||
///
|
||||
/// Multipurpose, will accept `AuraEffect` with zones and put in the correct store.
|
||||
pub fn set_builtin(&mut self, effect: AuraEffect) {
|
||||
match effect.zone() {
|
||||
AuraZone::None => {
|
||||
self.builtins.insert(*effect.mode(), effect);
|
||||
}
|
||||
_ => {
|
||||
if let Some(multi) = self.multizone.as_mut() {
|
||||
if let Some(fx) = multi.get_mut(effect.mode()) {
|
||||
for fx in fx.iter_mut() {
|
||||
if fx.mode == effect.mode {
|
||||
*fx = effect;
|
||||
break;
|
||||
}
|
||||
self.current_mode = effect.mode;
|
||||
if effect.zone() == AuraZone::None {
|
||||
self.builtins.insert(*effect.mode(), effect);
|
||||
self.multizone_on = false;
|
||||
} else {
|
||||
if let Some(multi) = self.multizone.as_mut() {
|
||||
if let Some(fx) = multi.get_mut(effect.mode()) {
|
||||
for fx in fx.iter_mut() {
|
||||
if fx.zone == effect.zone {
|
||||
*fx = effect;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*effect.mode(), vec![effect]);
|
||||
self.multizone = Some(tmp);
|
||||
}
|
||||
fx.push(effect);
|
||||
} else {
|
||||
multi.insert(*effect.mode(), vec![effect]);
|
||||
}
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*effect.mode(), vec![effect]);
|
||||
self.multizone = Some(tmp);
|
||||
}
|
||||
self.multizone_on = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,3 +322,102 @@ impl AuraConfig {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AuraConfig;
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
|
||||
|
||||
#[test]
|
||||
fn set_multizone_4key_config() {
|
||||
let mut config = AuraConfig::default();
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour(0xff, 0x00, 0xff),
|
||||
zone: AuraZone::Key1,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
assert!(config.multizone.is_some());
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour(0x00, 0xff, 0xff),
|
||||
zone: AuraZone::Key2,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour(0xff, 0xff, 0x00),
|
||||
zone: AuraZone::Key3,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour(0x00, 0xff, 0x00),
|
||||
zone: AuraZone::Key4,
|
||||
..Default::default()
|
||||
};
|
||||
let effect_clone = effect.clone();
|
||||
config.set_builtin(effect);
|
||||
// This should replace existing
|
||||
config.set_builtin(effect_clone);
|
||||
|
||||
let res = config.multizone.unwrap();
|
||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(sta.len(), 4);
|
||||
assert_eq!(sta[0].colour1, Colour(0xff, 0x00, 0xff));
|
||||
assert_eq!(sta[1].colour1, Colour(0x00, 0xff, 0xff));
|
||||
assert_eq!(sta[2].colour1, Colour(0xff, 0xff, 0x00));
|
||||
assert_eq!(sta[3].colour1, Colour(0x00, 0xff, 0x00));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_multizone_multimode_config() {
|
||||
let mut config = AuraConfig::default();
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key1,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
assert!(config.multizone.is_some());
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key2,
|
||||
mode: AuraModeNum::Breathe,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key3,
|
||||
mode: AuraModeNum::Comet,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key4,
|
||||
mode: AuraModeNum::Pulse,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let res = config.multizone.unwrap();
|
||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Breathe).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Comet).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Pulse).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
// Only these two packets must be 17 bytes
|
||||
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
|
||||
|
||||
use crate::{
|
||||
error::RogError,
|
||||
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
|
||||
CtrlTask,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use rog_aura::usb::AuraControl;
|
||||
use log::{info, warn};
|
||||
use rog_aura::{
|
||||
usb::{LED_APPLY, LED_SET},
|
||||
AuraEffect, LedBrightness, LED_MSG_LEN,
|
||||
usb::{AuraDevice, LED_APPLY, LED_SET},
|
||||
AuraEffect, KeyColourArray, LedBrightness, PerKeyRaw, LED_MSG_LEN,
|
||||
};
|
||||
use rog_supported::LedSupportedFunctions;
|
||||
use smol::{stream::StreamExt, Executor};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::{fs::OpenOptions, sync::MutexGuard};
|
||||
use zbus::Connection;
|
||||
use rog_aura::{AuraZone, Direction, Speed, GRADIENT};
|
||||
use rog_platform::{hid_raw::HidRaw, keyboard_led::KeyboardLed, supported::LedSupportedFunctions};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::GetSupported;
|
||||
|
||||
use super::config::AuraConfig;
|
||||
use super::config::{AuraConfig, AuraPowerConfig};
|
||||
|
||||
impl GetSupported for CtrlKbdLed {
|
||||
type A = LedSupportedFunctions;
|
||||
@@ -37,8 +25,24 @@ impl GetSupported for CtrlKbdLed {
|
||||
let multizone_led_mode = laptop.multizone;
|
||||
let per_key_led_mode = laptop.per_key;
|
||||
|
||||
let mut prod_id = AuraDevice::Unknown;
|
||||
for prod in &ASUS_KEYBOARD_DEVICES {
|
||||
if HidRaw::new(prod).is_ok() {
|
||||
prod_id = AuraDevice::from(*prod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let rgb = KeyboardLed::new();
|
||||
if let Ok(p) = rgb.as_ref() {
|
||||
if p.has_kbd_rgb_mode() {
|
||||
prod_id = AuraDevice::Tuf;
|
||||
}
|
||||
}
|
||||
|
||||
LedSupportedFunctions {
|
||||
brightness_set: CtrlKbdLed::get_kbd_bright_path().is_some(),
|
||||
prod_id,
|
||||
brightness_set: rgb.is_ok(),
|
||||
stock_led_modes,
|
||||
multizone_led_mode,
|
||||
per_key_led_mode,
|
||||
@@ -46,161 +50,32 @@ impl GetSupported for CtrlKbdLed {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd)]
|
||||
pub enum LEDNode {
|
||||
KbdLed(KeyboardLed),
|
||||
Rog(HidRaw),
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLed {
|
||||
pub led_node: Option<String>,
|
||||
pub bright_node: String,
|
||||
// TODO: config stores the keyboard type as an AuraPower, use or update this
|
||||
pub led_prod: Option<String>,
|
||||
pub led_node: LEDNode,
|
||||
pub kd_brightness: KeyboardLed,
|
||||
pub supported_modes: LaptopLedData,
|
||||
pub flip_effect_write: bool,
|
||||
pub per_key_mode_active: bool,
|
||||
pub config: AuraConfig,
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedTask {
|
||||
inner: Arc<Mutex<CtrlKbdLed>>,
|
||||
}
|
||||
|
||||
impl CtrlKbdLedTask {
|
||||
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&lock.bright_node)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&lock.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&lock.bright_node).into(), err),
|
||||
})?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if lock.config.brightness != num.into() {
|
||||
lock.config.read();
|
||||
lock.config.brightness = num.into();
|
||||
lock.config.write();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Err(RogError::ParseLed)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlKbdLedTask {
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("CtrlKbdLedTask could not create dbus connection");
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlKbdLedTask could not create ManagerProxy");
|
||||
|
||||
let load_save = |start: bool, mut lock: MutexGuard<CtrlKbdLed>| {
|
||||
// If waking up
|
||||
if !start {
|
||||
info!("CtrlKbdLedTask reloading brightness and modes");
|
||||
lock.set_brightness(lock.config.brightness)
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
if let Some(mode) = lock.config.builtins.get(&lock.config.current_mode) {
|
||||
lock.write_mode(mode)
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
}
|
||||
} else if start {
|
||||
info!("CtrlKbdLedTask saving last brightness");
|
||||
Self::update_config(&mut lock)
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
|
||||
let inner = self.inner.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_sleep().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
loop {
|
||||
// Loop so that we do aquire the lock but also don't block other
|
||||
// threads (prevents potential deadlocks)
|
||||
if let Ok(lock) = inner.clone().try_lock() {
|
||||
load_save(args.start, lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
if let Ok(notif) = manager.receive_prepare_for_shutdown().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
loop {
|
||||
if let Ok(lock) = inner.clone().try_lock() {
|
||||
load_save(args.start, lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// let inner = self.inner.clone();
|
||||
// self.repeating_task(500, executor, move || loop {
|
||||
// if let Ok(ref mut lock) = inner.try_lock() {
|
||||
// Self::update_config(lock).unwrap();
|
||||
// break;
|
||||
// }
|
||||
// })
|
||||
// .await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedReloader(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl crate::Reloadable for CtrlKbdLedReloader {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
let current = ctrl.config.current_mode;
|
||||
if let Some(mode) = ctrl.config.builtins.get(¤t).cloned() {
|
||||
ctrl.do_command(mode).ok();
|
||||
}
|
||||
|
||||
ctrl.set_power_states(&ctrl.config)
|
||||
.map_err(|err| warn!("{err}"))
|
||||
.ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedZbus(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl CtrlKbdLedZbus {
|
||||
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlKbdLed {
|
||||
#[inline]
|
||||
pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> {
|
||||
// TODO: return error if *all* nodes are None
|
||||
let mut led_prod = None;
|
||||
let mut led_node = None;
|
||||
for prod in ASUS_KEYBOARD_DEVICES.iter() {
|
||||
match Self::find_led_node(prod) {
|
||||
for prod in &ASUS_KEYBOARD_DEVICES {
|
||||
match HidRaw::new(prod) {
|
||||
Ok(node) => {
|
||||
led_prod = Some((*prod).to_owned());
|
||||
led_node = Some(node);
|
||||
info!("Looked for keyboard controller 0x{prod}: Found");
|
||||
break;
|
||||
@@ -209,69 +84,50 @@ impl CtrlKbdLed {
|
||||
}
|
||||
}
|
||||
|
||||
let bright_node = Self::get_kbd_bright_path();
|
||||
let rgb_led = KeyboardLed::new()?;
|
||||
|
||||
if led_node.is_none() && bright_node.is_none() {
|
||||
return Err(RogError::MissingFunction(
|
||||
"All keyboard features missing, you may require a v5.11 series kernel or newer"
|
||||
.into(),
|
||||
));
|
||||
if led_node.is_none() && !rgb_led.has_kbd_rgb_mode() {
|
||||
let dmi = sysfs_class::DmiId::default();
|
||||
if let Ok(prod_family) = dmi.product_family() {
|
||||
if prod_family.contains("TUF") {
|
||||
warn!("A kernel patch is in progress for TUF RGB support");
|
||||
}
|
||||
}
|
||||
return Err(RogError::NoAuraKeyboard);
|
||||
}
|
||||
|
||||
if bright_node.is_none() {
|
||||
return Err(RogError::MissingFunction(
|
||||
"No brightness control, you may require a v5.11 series kernel or newer".into(),
|
||||
));
|
||||
}
|
||||
let led_node = if let Some(rog) = led_node {
|
||||
info!("Found ROG USB keyboard");
|
||||
LEDNode::Rog(rog)
|
||||
} else if rgb_led.has_kbd_rgb_mode() {
|
||||
info!("Found TUF keyboard");
|
||||
LEDNode::KbdLed(rgb_led.clone())
|
||||
} else {
|
||||
LEDNode::None
|
||||
};
|
||||
|
||||
let ctrl = CtrlKbdLed {
|
||||
led_prod,
|
||||
led_node,
|
||||
bright_node: bright_node.unwrap(), // If was none then we already returned above
|
||||
kd_brightness: rgb_led, // If was none then we already returned above
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
per_key_mode_active: 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])
|
||||
self.kd_brightness
|
||||
.get_brightness()
|
||||
.map_err(RogError::Platform)
|
||||
}
|
||||
|
||||
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(())
|
||||
self.kd_brightness
|
||||
.set_brightness(brightness as u8)
|
||||
.map_err(RogError::Platform)
|
||||
}
|
||||
|
||||
pub fn next_brightness(&mut self) -> Result<(), RogError> {
|
||||
@@ -297,108 +153,82 @@ impl CtrlKbdLed {
|
||||
}
|
||||
|
||||
/// Set combination state for boot animation/sleep animation/all leds/keys leds/side leds LED active
|
||||
pub(super) fn set_power_states(&self, config: &AuraConfig) -> Result<(), RogError> {
|
||||
let set: Vec<AuraControl> = config.enabled.iter().map(|v| *v).collect();
|
||||
let bytes = AuraControl::to_bytes(&set);
|
||||
pub(super) fn set_power_states(&mut self) -> Result<(), RogError> {
|
||||
if let LEDNode::KbdLed(platform) = &mut self.led_node {
|
||||
if let Some(pwr) = AuraPowerConfig::to_tuf_bool_array(&self.config.enabled) {
|
||||
let buf = [1, pwr[1] as u8, pwr[2] as u8, pwr[3] as u8, pwr[4] as u8];
|
||||
platform.set_kbd_rgb_state(&buf)?;
|
||||
}
|
||||
} else if let LEDNode::Rog(hid_raw) = &self.led_node {
|
||||
let bytes = AuraPowerConfig::to_bytes(&self.config.enabled);
|
||||
let message = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2]];
|
||||
|
||||
// Quite ugly, must be a more idiomatic way to do
|
||||
let message = [
|
||||
0x5d, 0xbd, 0x01, bytes[0], bytes[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
self.write_bytes(&message)?;
|
||||
self.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY)?;
|
||||
hid_raw.write_bytes(&message)?;
|
||||
hid_raw.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
hid_raw.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
|
||||
/// Set an Aura effect if the effect mode or zone is supported.
|
||||
///
|
||||
/// 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);
|
||||
/// On success the aura config file is read to refresh cached values, then the effect is
|
||||
/// stored and config written to disk.
|
||||
pub(crate) fn set_effect(&mut self, effect: AuraEffect) -> Result<(), RogError> {
|
||||
if !self.supported_modes.standard.contains(&effect.mode)
|
||||
|| effect.zone != AuraZone::None
|
||||
&& !self.supported_modes.multizone.contains(&effect.zone)
|
||||
{
|
||||
return Err(RogError::AuraEffectNotSupported);
|
||||
}
|
||||
|
||||
self.write_mode(&effect)?;
|
||||
self.config.read(); // refresh config if successful
|
||||
self.config.set_builtin(effect);
|
||||
self.config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Write an effect block. This is for per-key, but can be repurposed to
|
||||
/// write the raw factory mode packets - when doing this it is expected that
|
||||
/// only the first `Vec` (`effect[0]`) is valid.
|
||||
pub fn write_effect_block(&mut self, effect: &PerKeyRaw) -> Result<(), RogError> {
|
||||
let pkt_type = effect[0][1];
|
||||
const PER_KEY_TYPE: u8 = 0xbc;
|
||||
|
||||
if pkt_type != PER_KEY_TYPE {
|
||||
self.per_key_mode_active = false;
|
||||
if let LEDNode::Rog(hid_raw) = &self.led_node {
|
||||
hid_raw.write_bytes(&effect[0])?;
|
||||
hid_raw.write_bytes(&LED_SET)?;
|
||||
// hid_raw.write_bytes(&LED_APPLY)?;
|
||||
}
|
||||
} else {
|
||||
if !self.per_key_mode_active {
|
||||
if let LEDNode::Rog(hid_raw) = &self.led_node {
|
||||
let init = KeyColourArray::get_init_msg();
|
||||
hid_raw.write_bytes(&init)?;
|
||||
}
|
||||
self.per_key_mode_active = true;
|
||||
}
|
||||
if let LEDNode::Rog(hid_raw) = &self.led_node {
|
||||
for row in effect.iter() {
|
||||
hid_raw.write_bytes(row)?;
|
||||
}
|
||||
} else if let LEDNode::KbdLed(tuf) = &self.led_node {
|
||||
for row in effect.iter() {
|
||||
let r = row[9];
|
||||
let g = row[10];
|
||||
let b = row[11];
|
||||
tuf.set_kbd_rgb_mode(&[0, 0, r, g, b, 0])?;
|
||||
}
|
||||
}
|
||||
self.flip_effect_write = !self.flip_effect_write;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
|
||||
let current = self.config.current_mode;
|
||||
if let Some(idx) = self
|
||||
@@ -424,26 +254,256 @@ impl CtrlKbdLed {
|
||||
let next = self.supported_modes.standard[idx];
|
||||
|
||||
self.config.read();
|
||||
if let Some(data) = self.config.builtins.get(&next) {
|
||||
self.write_mode(data)?;
|
||||
self.config.current_mode = next;
|
||||
}
|
||||
// if self.config.builtins.contains_key(&next) {
|
||||
self.config.current_mode = next;
|
||||
self.write_current_config_mode()?;
|
||||
// }
|
||||
self.config.write();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_mode(&self, mode: &AuraEffect) -> Result<(), RogError> {
|
||||
if !self.supported_modes.standard.contains(mode.mode()) {
|
||||
return Err(RogError::NotSupported);
|
||||
fn write_mode(&mut self, mode: &AuraEffect) -> Result<(), RogError> {
|
||||
if let LEDNode::KbdLed(platform) = &self.led_node {
|
||||
let buf = [
|
||||
1,
|
||||
mode.mode as u8,
|
||||
mode.colour1.0,
|
||||
mode.colour1.1,
|
||||
mode.colour1.2,
|
||||
mode.speed as u8,
|
||||
];
|
||||
platform.set_kbd_rgb_mode(&buf)?;
|
||||
} else if let LEDNode::Rog(hid_raw) = &self.led_node {
|
||||
let bytes: [u8; LED_MSG_LEN] = mode.into();
|
||||
hid_raw.write_bytes(&bytes)?;
|
||||
hid_raw.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
hid_raw.write_bytes(&LED_APPLY)?;
|
||||
} else {
|
||||
return Err(RogError::NoAuraKeyboard);
|
||||
}
|
||||
self.per_key_mode_active = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn write_current_config_mode(&mut self) -> Result<(), RogError> {
|
||||
if self.config.multizone_on {
|
||||
let mode = self.config.current_mode;
|
||||
let mut create = false;
|
||||
// There is no multizone config for this mode so create one here
|
||||
// using the colours of rainbow if it exists, or first available
|
||||
// mode, or random
|
||||
if self.config.multizone.is_none() {
|
||||
create = true;
|
||||
} else if let Some(multizones) = self.config.multizone.as_ref() {
|
||||
if !multizones.contains_key(&mode) {
|
||||
create = true;
|
||||
}
|
||||
}
|
||||
if create {
|
||||
info!("No user-set config for zone founding, attempting a default");
|
||||
self.create_multizone_default()?;
|
||||
}
|
||||
|
||||
if let Some(multizones) = self.config.multizone.as_mut() {
|
||||
if let Some(set) = multizones.get(&mode) {
|
||||
for mode in set.clone() {
|
||||
self.write_mode(&mode)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mode = self.config.current_mode;
|
||||
if let Some(effect) = self.config.builtins.get(&mode).cloned() {
|
||||
self.write_mode(&effect)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a default for the `current_mode` if multizone and no config exists.
|
||||
fn create_multizone_default(&mut self) -> Result<(), RogError> {
|
||||
let mut default = vec![];
|
||||
for (i, tmp) in self.supported_modes.multizone.iter().enumerate() {
|
||||
default.push(AuraEffect {
|
||||
mode: self.config.current_mode,
|
||||
zone: *tmp,
|
||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Left,
|
||||
});
|
||||
}
|
||||
if default.is_empty() {
|
||||
return Err(RogError::AuraEffectNotSupported);
|
||||
}
|
||||
|
||||
if let Some(multizones) = self.config.multizone.as_mut() {
|
||||
multizones.insert(self.config.current_mode, default);
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(self.config.current_mode, default);
|
||||
self.config.multizone = Some(tmp);
|
||||
}
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
|
||||
use rog_platform::keyboard_led::KeyboardLed;
|
||||
|
||||
use crate::{
|
||||
ctrl_aura::{config::AuraConfig, controller::LEDNode},
|
||||
laptops::LaptopLedData,
|
||||
};
|
||||
|
||||
use super::CtrlKbdLed;
|
||||
|
||||
#[test]
|
||||
// #[ignore = "Must be manually run due to detection stage"]
|
||||
fn check_set_mode_errors() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::default();
|
||||
let supported_modes = LaptopLedData {
|
||||
prod_family: String::new(),
|
||||
board_names: vec![],
|
||||
standard: vec![AuraModeNum::Static],
|
||||
multizone: vec![],
|
||||
per_key: false,
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_prod: None,
|
||||
led_node: LEDNode::None,
|
||||
kd_brightness: KeyboardLed::default(),
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
per_key_mode_active: false,
|
||||
config,
|
||||
};
|
||||
|
||||
let mut effect = AuraEffect {
|
||||
colour1: Colour(0xff, 0x00, 0xff),
|
||||
zone: AuraZone::None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// This error comes from write_bytes because we don't have a keyboard node stored
|
||||
assert_eq!(
|
||||
controller
|
||||
.set_effect(effect.clone())
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"No supported Aura keyboard"
|
||||
);
|
||||
|
||||
effect.mode = AuraModeNum::Laser;
|
||||
assert_eq!(
|
||||
controller
|
||||
.set_effect(effect.clone())
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"Aura effect not supported"
|
||||
);
|
||||
|
||||
effect.mode = AuraModeNum::Static;
|
||||
effect.zone = AuraZone::Key2;
|
||||
assert_eq!(
|
||||
controller
|
||||
.set_effect(effect.clone())
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"Aura effect not supported"
|
||||
);
|
||||
|
||||
controller.supported_modes.multizone.push(AuraZone::Key2);
|
||||
assert_eq!(
|
||||
controller.set_effect(effect).unwrap_err().to_string(),
|
||||
"No supported Aura keyboard"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_multizone_if_no_config() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::default();
|
||||
let supported_modes = LaptopLedData {
|
||||
prod_family: String::new(),
|
||||
board_names: vec![],
|
||||
standard: vec![AuraModeNum::Static],
|
||||
multizone: vec![],
|
||||
per_key: false,
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_prod: None,
|
||||
led_node: LEDNode::None,
|
||||
kd_brightness: KeyboardLed::default(),
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
per_key_mode_active: false,
|
||||
config,
|
||||
};
|
||||
|
||||
assert!(controller.config.multizone.is_none());
|
||||
assert!(controller.create_multizone_default().is_err());
|
||||
assert!(controller.config.multizone.is_none());
|
||||
|
||||
controller.supported_modes.multizone.push(AuraZone::Key1);
|
||||
controller.supported_modes.multizone.push(AuraZone::Key2);
|
||||
assert!(controller.create_multizone_default().is_ok());
|
||||
assert!(controller.config.multizone.is_some());
|
||||
|
||||
let m = controller.config.multizone.unwrap();
|
||||
assert!(m.contains_key(&AuraModeNum::Static));
|
||||
let e = m.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(e.len(), 2);
|
||||
assert_eq!(e[0].zone, AuraZone::Key1);
|
||||
assert_eq!(e[1].zone, AuraZone::Key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_mode_create_multizone_if_no_config() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::default();
|
||||
let supported_modes = LaptopLedData {
|
||||
prod_family: String::new(),
|
||||
board_names: vec![],
|
||||
standard: vec![AuraModeNum::Static],
|
||||
multizone: vec![AuraZone::Key1, AuraZone::Key2],
|
||||
per_key: false,
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_prod: None,
|
||||
led_node: LEDNode::None,
|
||||
kd_brightness: KeyboardLed::default(),
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
per_key_mode_active: false,
|
||||
config,
|
||||
};
|
||||
|
||||
assert!(controller.config.multizone.is_none());
|
||||
controller.config.multizone_on = true;
|
||||
// This is called in toggle_mode. It will error here because we have no
|
||||
// keyboard node in tests.
|
||||
assert_eq!(
|
||||
controller
|
||||
.write_current_config_mode()
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"No supported Aura keyboard"
|
||||
);
|
||||
assert!(controller.config.multizone.is_some());
|
||||
|
||||
let m = controller.config.multizone.unwrap();
|
||||
assert!(m.contains_key(&AuraModeNum::Static));
|
||||
let e = m.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(e.len(), 2);
|
||||
assert_eq!(e[0].zone, AuraZone::Key1);
|
||||
assert_eq!(e[1].zone, AuraZone::Key2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod controller;
|
||||
pub mod zbus;
|
||||
/// Implements CtrlTask, Reloadable, ZbusRun
|
||||
pub mod trait_impls;
|
||||
|
||||
320
daemon/src/ctrl_aura/trait_impls.rs
Normal file
@@ -0,0 +1,320 @@
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, LedBrightness, PerKeyRaw};
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use zbus::{
|
||||
dbus_interface,
|
||||
export::futures_util::{
|
||||
lock::{Mutex, MutexGuard},
|
||||
StreamExt,
|
||||
},
|
||||
Connection, SignalContext,
|
||||
};
|
||||
|
||||
use crate::{error::RogError, CtrlTask};
|
||||
|
||||
use super::controller::CtrlKbdLed;
|
||||
|
||||
pub(super) const ZBUS_PATH: &str = "/org/asuslinux/Aura";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlKbdLedZbus(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl CtrlKbdLedZbus {
|
||||
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
|
||||
let bright = lock.kd_brightness.get_brightness()?;
|
||||
lock.config.read();
|
||||
lock.config.brightness = (bright as u32).into();
|
||||
lock.config.write();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusRun for CtrlKbdLedZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// The main interface for changing, reading, or notfying signals
|
||||
///
|
||||
/// LED commands are split between Brightness, Modes, Per-Key
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlKbdLedZbus {
|
||||
/// Set the keyboard brightness level (0-3)
|
||||
async fn set_brightness(&mut self, brightness: LedBrightness) {
|
||||
let ctrl = self.0.lock().await;
|
||||
ctrl.set_brightness(brightness)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Set a variety of states, input is array of enum.
|
||||
/// `enabled` sets if the sent array should be disabled or enabled
|
||||
///
|
||||
/// ```text
|
||||
/// pub struct AuraPowerDev {
|
||||
/// pub x1866: Vec<AuraDev1866>,
|
||||
/// pub x19b6: Vec<AuraDev19b6>,
|
||||
/// }
|
||||
/// pub enum AuraDev1866 {
|
||||
/// Awake,
|
||||
/// Keyboard,
|
||||
/// Lightbar,
|
||||
/// Boot,
|
||||
/// Sleep,
|
||||
/// }
|
||||
/// enum AuraDev19b6 {
|
||||
/// BootLogo,
|
||||
/// BootKeyb,
|
||||
/// AwakeLogo,
|
||||
/// AwakeKeyb,
|
||||
/// SleepLogo,
|
||||
/// SleepKeyb,
|
||||
/// ShutdownLogo,
|
||||
/// ShutdownKeyb,
|
||||
/// AwakeBar,
|
||||
/// BootBar,
|
||||
/// SleepBar,
|
||||
/// ShutdownBar,
|
||||
/// }
|
||||
/// ```
|
||||
async fn set_leds_power(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
options: AuraPowerDev,
|
||||
enabled: bool,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
for p in options.tuf {
|
||||
ctrl.config.enabled.set_tuf(p, enabled);
|
||||
}
|
||||
for p in options.x1866 {
|
||||
ctrl.config.enabled.set_0x1866(p, enabled);
|
||||
}
|
||||
for p in options.x19b6 {
|
||||
ctrl.config.enabled.set_0x19b6(p, enabled);
|
||||
}
|
||||
|
||||
ctrl.config.write();
|
||||
|
||||
ctrl.set_power_states().map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
Self::notify_power_states(&ctxt, &AuraPowerDev::from(&ctrl.config.enabled))
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_led_mode(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
effect: AuraEffect,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
|
||||
ctrl.set_effect(effect).map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
Self::notify_led(&ctxt, mode.clone())
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_led_mode(
|
||||
&self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
|
||||
ctrl.toggle_mode(false).map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
Self::notify_led(&ctxt, mode.clone())
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prev_led_mode(
|
||||
&self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
|
||||
ctrl.toggle_mode(true).map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
Self::notify_led(&ctxt, mode.clone())
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_led_brightness(&self) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.next_brightness().map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prev_led_brightness(&self) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.prev_brightness().map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// As property doesn't work for AuraPowerDev (complexity of serialization?)
|
||||
// #[dbus_interface(property)]
|
||||
async fn leds_enabled(&self) -> AuraPowerDev {
|
||||
let ctrl = self.0.lock().await;
|
||||
AuraPowerDev::from(&ctrl.config.enabled)
|
||||
}
|
||||
|
||||
/// Return the current mode data
|
||||
async fn led_mode(&self) -> AuraModeNum {
|
||||
let ctrl = self.0.lock().await;
|
||||
ctrl.config.current_mode
|
||||
}
|
||||
|
||||
/// Return a list of available modes
|
||||
async fn led_modes(&self) -> BTreeMap<AuraModeNum, AuraEffect> {
|
||||
let ctrl = self.0.lock().await;
|
||||
ctrl.config.builtins.clone()
|
||||
}
|
||||
|
||||
async fn per_key_raw(&self, data: PerKeyRaw) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.write_effect_block(&data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
#[dbus_interface(property)]
|
||||
async fn led_brightness(&self) -> i8 {
|
||||
let ctrl = self.0.lock().await;
|
||||
ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_led(signal_ctxt: &SignalContext<'_>, data: AuraEffect) -> zbus::Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_power_states(
|
||||
signal_ctxt: &SignalContext<'_>,
|
||||
data: &AuraPowerDev,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlKbdLedZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let load_save = |start: bool, mut lock: MutexGuard<'_, CtrlKbdLed>| {
|
||||
// If waking up
|
||||
if !start {
|
||||
info!("CtrlKbdLedTask reloading brightness and modes");
|
||||
lock.set_brightness(lock.config.brightness)
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
lock.write_current_config_mode()
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
} else if start {
|
||||
info!("CtrlKbdLedTask saving last brightness");
|
||||
Self::update_config(&mut lock)
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
|
||||
let inner1 = self.0.clone();
|
||||
let inner2 = self.0.clone();
|
||||
let inner3 = self.0.clone();
|
||||
let inner4 = self.0.clone();
|
||||
self.create_sys_event_tasks(
|
||||
// Loop so that we do aquire the lock but also don't block other
|
||||
// threads (prevents potential deadlocks)
|
||||
move || loop {
|
||||
if let Some(lock) = inner1.try_lock() {
|
||||
load_save(true, lock);
|
||||
break;
|
||||
}
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner2.try_lock() {
|
||||
load_save(false, lock);
|
||||
break;
|
||||
}
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner3.try_lock() {
|
||||
load_save(true, lock);
|
||||
break;
|
||||
}
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner4.try_lock() {
|
||||
load_save(false, lock);
|
||||
break;
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let ctrl2 = self.0.clone();
|
||||
let ctrl = self.0.lock().await;
|
||||
let mut watch = ctrl.kd_brightness.monitor_brightness()?;
|
||||
tokio::spawn(async move {
|
||||
let mut buffer = [0; 32];
|
||||
watch
|
||||
.event_stream(&mut buffer)
|
||||
.unwrap()
|
||||
.for_each(|_| async {
|
||||
if let Some(lock) = ctrl2.try_lock() {
|
||||
load_save(true, lock);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Reloadable for CtrlKbdLedZbus {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.write_current_config_mode()?;
|
||||
ctrl.set_power_states().map_err(|err| warn!("{err}")).ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use log::warn;
|
||||
use rog_aura::{usb::AuraControl, AuraEffect, LedBrightness};
|
||||
use zbus::{dbus_interface, Connection, SignalContext};
|
||||
|
||||
use super::controller::CtrlKbdLedZbus;
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlKbdLedZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Led", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// The main interface for changing, reading, or notfying signals
|
||||
///
|
||||
/// LED commands are split between Brightness, Modes, Per-Key
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlKbdLedZbus {
|
||||
/// Set the keyboard brightness level (0-3)
|
||||
async fn set_brightness(&mut self, brightness: LedBrightness) {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
ctrl.set_brightness(brightness)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a variety of states, input is array of enum.
|
||||
///
|
||||
/// enum AuraControl {
|
||||
/// BootLogo,
|
||||
/// BootKeyb,
|
||||
/// AwakeLogo,
|
||||
/// AwakeKeyb,
|
||||
/// SleepLogo,
|
||||
/// SleepKeyb,
|
||||
/// ShutdownLogo,
|
||||
/// ShutdownKeyb,
|
||||
/// AwakeBar,
|
||||
/// BootBar,
|
||||
/// SleepBar,
|
||||
/// ShutdownBar,
|
||||
/// }
|
||||
async fn set_leds_enabled(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
enabled: Vec<AuraControl>,
|
||||
) {
|
||||
let mut states = None;
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
for s in enabled {
|
||||
ctrl.config.enabled.insert(s);
|
||||
}
|
||||
ctrl.config.write();
|
||||
|
||||
ctrl.set_power_states(&ctrl.config)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
|
||||
let set: Vec<AuraControl> = ctrl.config.enabled.iter().map(|v| *v).collect();
|
||||
states = Some(set);
|
||||
}
|
||||
// Need to pull state out like this due to MutexGuard
|
||||
if let Some(states) = states {
|
||||
Self::notify_power_states(&ctxt, &states)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_leds_disabled(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
disabled: Vec<AuraControl>,
|
||||
) {
|
||||
let mut states = None;
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
for s in disabled {
|
||||
ctrl.config.enabled.remove(&s);
|
||||
}
|
||||
ctrl.config.write();
|
||||
|
||||
ctrl.set_power_states(&ctrl.config)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
|
||||
let set: Vec<AuraControl> = ctrl.config.enabled.iter().map(|v| *v).collect();
|
||||
states = Some(set);
|
||||
}
|
||||
// Need to pull state out like this due to MutexGuard
|
||||
if let Some(states) = states {
|
||||
Self::notify_power_states(&ctxt, &states)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_led_mode(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
effect: AuraEffect,
|
||||
) {
|
||||
let mut led = None;
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
match ctrl.do_command(effect) {
|
||||
Ok(_) => {
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
led = Some(mode.clone());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(led) = led {
|
||||
Self::notify_led(&ctxt, led)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
async fn next_led_mode(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
|
||||
let mut led = None;
|
||||
if let Ok(mut ctrl) = self.0.lock() {
|
||||
ctrl.toggle_mode(false)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
led = Some(mode.clone());
|
||||
}
|
||||
}
|
||||
if let Some(led) = led {
|
||||
Self::notify_led(&ctxt, led)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
async fn prev_led_mode(&self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
|
||||
let mut led = None;
|
||||
if let Ok(mut ctrl) = self.0.lock() {
|
||||
ctrl.toggle_mode(true)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
led = Some(mode.clone());
|
||||
}
|
||||
}
|
||||
if let Some(led) = led {
|
||||
Self::notify_led(&ctxt, led)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
async fn next_led_brightness(&self) {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.next_brightness()
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
async fn prev_led_brightness(&self) {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.prev_brightness()
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn leds_enabled(&self) -> Vec<u8> {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
let set: Vec<AuraControl> = ctrl.config.enabled.iter().map(|v| *v).collect();
|
||||
return AuraControl::to_bytes(&set).to_vec();
|
||||
}
|
||||
vec![0, 0]
|
||||
}
|
||||
|
||||
/// Return the current mode data
|
||||
#[dbus_interface(property)]
|
||||
async fn led_mode(&self) -> String {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not deserialise".to_string()
|
||||
}
|
||||
|
||||
/// Return a list of available modes
|
||||
#[dbus_interface(property)]
|
||||
async fn led_modes(&self) -> String {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not serialise".to_string()
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
#[dbus_interface(property)]
|
||||
async fn led_brightness(&self) -> i8 {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1);
|
||||
}
|
||||
warn!("SetKeyBacklight could not serialise");
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_led(signal_ctxt: &SignalContext<'_>, data: AuraEffect) -> zbus::Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_power_states(
|
||||
signal_ctxt: &SignalContext<'_>,
|
||||
data: &[AuraControl],
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
use crate::CtrlTask;
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
use async_trait::async_trait;
|
||||
use log::{info, warn};
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use rog_supported::ChargeSupportedFunctions;
|
||||
use smol::stream::StreamExt;
|
||||
use smol::Executor;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
use zbus::Connection;
|
||||
use zbus::SignalContext;
|
||||
|
||||
static BAT_CHARGE_PATH0: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
|
||||
static BAT_CHARGE_PATH1: &str = "/sys/class/power_supply/BAT1/charge_control_end_threshold";
|
||||
static BAT_CHARGE_PATH2: &str = "/sys/class/power_supply/BAT2/charge_control_end_threshold";
|
||||
|
||||
impl GetSupported for CtrlCharge {
|
||||
type A = ChargeSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
ChargeSupportedFunctions {
|
||||
charge_level_set: CtrlCharge::get_battery_path().is_ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlCharge {
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlCharge {
|
||||
async fn set_limit(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
limit: u8,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
if !(20..=100).contains(&limit) {
|
||||
return Err(RogError::ChargeLimit(limit))?;
|
||||
}
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
Self::set(limit, &mut config)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Self::notify_charge(&ctxt, limit).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn limit(&self) -> i8 {
|
||||
if let Ok(config) = self.config.try_lock() {
|
||||
return config.bat_charge_limit as i8;
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_charge(ctxt: &SignalContext<'_>, limit: u8) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlCharge {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Charge", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlCharge {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
config.read();
|
||||
Self::set(config.bat_charge_limit, &mut config)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlCharge {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
CtrlCharge::get_battery_path()?;
|
||||
Ok(CtrlCharge { config })
|
||||
}
|
||||
|
||||
fn get_battery_path() -> Result<&'static str, RogError> {
|
||||
if Path::new(BAT_CHARGE_PATH0).exists() {
|
||||
Ok(BAT_CHARGE_PATH0)
|
||||
} else if Path::new(BAT_CHARGE_PATH1).exists() {
|
||||
Ok(BAT_CHARGE_PATH1)
|
||||
} else if Path::new(BAT_CHARGE_PATH2).exists() {
|
||||
Ok(BAT_CHARGE_PATH2)
|
||||
} else {
|
||||
Err(RogError::MissingFunction(
|
||||
"Charge control not available, you may require a v5.8.10 series kernel or newer"
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set(limit: u8, config: &mut Config) -> Result<(), RogError> {
|
||||
if !(20..=100).contains(&limit) {
|
||||
return Err(RogError::ChargeLimit(limit));
|
||||
}
|
||||
|
||||
let path = Self::get_battery_path()?;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
file.write_all(limit.to_string().as_bytes())
|
||||
.map_err(|err| RogError::Write(path.into(), err))?;
|
||||
info!("Battery charge limit: {}", limit);
|
||||
|
||||
config.read();
|
||||
config.bat_charge_limit = limit;
|
||||
config.write();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlCharge {
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("CtrlCharge could not create dbus connection");
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlCharge could not create ManagerProxy");
|
||||
|
||||
let config1 = self.config.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_sleep().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
// If waking up
|
||||
if !args.start {
|
||||
info!("CtrlCharge reloading charge limit");
|
||||
if let Ok(mut lock) = config1.try_lock() {
|
||||
Self::set(lock.bat_charge_limit, &mut lock)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("CtrlCharge could not create ManagerProxy");
|
||||
|
||||
let config = self.config.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Ok(notif) = manager.receive_prepare_for_shutdown().await {
|
||||
notif
|
||||
.for_each(|event| {
|
||||
if let Ok(args) = event.args() {
|
||||
// If waking up - intention is to catch hibernation event
|
||||
if !args.start {
|
||||
info!("CtrlCharge reloading charge limit");
|
||||
loop {
|
||||
if let Ok(mut lock) = config.clone().try_lock() {
|
||||
Self::set(lock.bat_charge_limit, &mut lock)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
375
daemon/src/ctrl_platform.rs
Normal file
@@ -0,0 +1,375 @@
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
use crate::{task_watch_item, CtrlTask};
|
||||
use async_trait::async_trait;
|
||||
use log::{info, warn};
|
||||
use rog_platform::platform::{AsusPlatform, GpuMode};
|
||||
use rog_platform::supported::RogBiosSupportedFunctions;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use zbus::export::futures_util::lock::Mutex;
|
||||
use zbus::Connection;
|
||||
use zbus::{dbus_interface, SignalContext};
|
||||
|
||||
const ZBUS_PATH: &str = "/org/asuslinux/Platform";
|
||||
const ASUS_POST_LOGO_SOUND: &str =
|
||||
"/sys/firmware/efi/efivars/AsusPostLogoSound-607005d5-3f75-4b2e-98f0-85ba66797a3e";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlPlatform {
|
||||
platform: AsusPlatform,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlPlatform {
|
||||
type A = RogBiosSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
let mut panel_overdrive = false;
|
||||
let mut dgpu_disable = false;
|
||||
let mut egpu_enable = false;
|
||||
let mut gpu_mux = false;
|
||||
|
||||
if let Ok(platform) = AsusPlatform::new() {
|
||||
panel_overdrive = platform.has_panel_od();
|
||||
dgpu_disable = platform.has_dgpu_disable();
|
||||
egpu_enable = platform.has_egpu_enable();
|
||||
gpu_mux = platform.has_gpu_mux_mode();
|
||||
}
|
||||
|
||||
RogBiosSupportedFunctions {
|
||||
post_sound: Path::new(ASUS_POST_LOGO_SOUND).exists(),
|
||||
gpu_mux,
|
||||
panel_overdrive,
|
||||
dgpu_disable,
|
||||
egpu_enable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlPlatform {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
let platform = AsusPlatform::new()?;
|
||||
|
||||
if !platform.has_gpu_mux_mode() {
|
||||
info!("G-Sync Switchable Graphics or GPU MUX not detected");
|
||||
info!("Standard graphics switching will still work.");
|
||||
}
|
||||
|
||||
if Path::new(ASUS_POST_LOGO_SOUND).exists() {
|
||||
CtrlPlatform::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
|
||||
} else {
|
||||
info!("Switch for POST boot sound not detected");
|
||||
}
|
||||
|
||||
Ok(CtrlPlatform { platform, config })
|
||||
}
|
||||
|
||||
fn set_path_mutable(path: &str) -> Result<(), RogError> {
|
||||
let output = Command::new("/usr/bin/chattr")
|
||||
.arg("-i")
|
||||
.arg(path)
|
||||
.output()
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
info!("Set {} writeable: status: {}", path, output.status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_gfx_mode(&self, mode: GpuMode) -> Result<(), RogError> {
|
||||
self.platform.set_gpu_mux_mode(mode.to_mux_attr())?;
|
||||
// self.update_initramfs(enable)?;
|
||||
if mode == GpuMode::Discrete {
|
||||
info!("Set system-level graphics mode: Dedicated Nvidia");
|
||||
} else {
|
||||
info!("Set system-level graphics mode: Optimus");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_boot_sound() -> Result<i8, RogError> {
|
||||
let path = ASUS_POST_LOGO_SOUND;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)
|
||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
Ok(data[idx] as i8)
|
||||
}
|
||||
|
||||
pub(super) fn set_boot_sound(on: bool) -> Result<(), RogError> {
|
||||
let path = ASUS_POST_LOGO_SOUND;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)
|
||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
if on {
|
||||
data[idx] = 1;
|
||||
info!("Set boot POST sound on");
|
||||
} else {
|
||||
data[idx] = 0;
|
||||
info!("Set boot POST sound off");
|
||||
}
|
||||
file.write_all(&data)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_panel_overdrive(&self, enable: bool) -> Result<(), RogError> {
|
||||
self.platform.set_panel_od(enable).map_err(|err| {
|
||||
warn!("CtrlRogBios: set_panel_overdrive {}", err);
|
||||
err
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlPlatform {
|
||||
async fn set_gpu_mux_mode(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
mode: GpuMode,
|
||||
) {
|
||||
self.set_gfx_mode(mode)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_gpu_mux_mode {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
Self::notify_gpu_mux_mode(&ctxt, mode).await.ok();
|
||||
}
|
||||
|
||||
fn gpu_mux_mode(&self) -> GpuMode {
|
||||
match self.platform.get_gpu_mux_mode() {
|
||||
Ok(m) => GpuMode::from_mux(m as u8),
|
||||
Err(e) => {
|
||||
warn!("CtrlRogBios: get_gfx_mode {}", e);
|
||||
GpuMode::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_gpu_mux_mode(
|
||||
signal_ctxt: &SignalContext<'_>,
|
||||
mode: GpuMode,
|
||||
) -> zbus::Result<()> {
|
||||
}
|
||||
|
||||
async fn set_post_boot_sound(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
on: bool,
|
||||
) {
|
||||
Self::set_boot_sound(on)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_post_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
Self::notify_post_boot_sound(&ctxt, on).await.ok();
|
||||
}
|
||||
|
||||
fn post_boot_sound(&self) -> i8 {
|
||||
Self::get_boot_sound()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(-1)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_post_boot_sound(ctxt: &SignalContext<'_>, on: bool) -> zbus::Result<()> {}
|
||||
|
||||
async fn set_panel_od(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
overdrive: bool,
|
||||
) {
|
||||
match self.platform.set_panel_od(overdrive) {
|
||||
Ok(_) => {
|
||||
if let Some(mut lock) = self.config.try_lock() {
|
||||
lock.panel_od = overdrive;
|
||||
lock.write();
|
||||
}
|
||||
Self::notify_panel_od(&ctxt, overdrive).await.ok();
|
||||
}
|
||||
Err(err) => warn!("CtrlRogBios: set_panel_overdrive {}", err),
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the `panel_od` value from platform. Updates the stored value in internal config also.
|
||||
fn panel_od(&self) -> bool {
|
||||
let od = self
|
||||
.platform
|
||||
.get_panel_od()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_panel_od {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if let Some(mut lock) = self.config.try_lock() {
|
||||
lock.panel_od = od;
|
||||
lock.write();
|
||||
}
|
||||
od
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_panel_od(signal_ctxt: &SignalContext<'_>, overdrive: bool) -> zbus::Result<()> {
|
||||
}
|
||||
|
||||
async fn set_dgpu_disable(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
disable: bool,
|
||||
) {
|
||||
match self.platform.set_dgpu_disable(disable) {
|
||||
Ok(_) => {
|
||||
Self::notify_dgpu_disable(&ctxt, disable).await.ok();
|
||||
}
|
||||
Err(err) => warn!("CtrlRogBios: set_dgpu_disable {}", err),
|
||||
};
|
||||
}
|
||||
|
||||
fn dgpu_disable(&self) -> bool {
|
||||
self.platform
|
||||
.get_dgpu_disable()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_dgpu_disable {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_dgpu_disable(
|
||||
signal_ctxt: &SignalContext<'_>,
|
||||
disable: bool,
|
||||
) -> zbus::Result<()> {
|
||||
}
|
||||
|
||||
async fn set_egpu_enable(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
enable: bool,
|
||||
) {
|
||||
match self.platform.set_egpu_enable(enable) {
|
||||
Ok(_) => {
|
||||
Self::notify_egpu_enable(&ctxt, enable).await.ok();
|
||||
}
|
||||
Err(err) => warn!("CtrlRogBios: set_egpu_enable {}", err),
|
||||
};
|
||||
}
|
||||
|
||||
fn egpu_enable(&self) -> bool {
|
||||
self.platform
|
||||
.get_egpu_enable()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_egpu_enable {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_egpu_enable(signal_ctxt: &SignalContext<'_>, enable: bool) -> zbus::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusRun for CtrlPlatform {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Platform", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Reloadable for CtrlPlatform {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
if self.platform.has_panel_od() {
|
||||
let p = if let Some(lock) = self.config.try_lock() {
|
||||
lock.panel_od
|
||||
} else {
|
||||
false
|
||||
};
|
||||
self.set_panel_overdrive(p)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlPlatform {
|
||||
task_watch_item!(panel_od platform);
|
||||
task_watch_item!(dgpu_disable platform);
|
||||
task_watch_item!(egpu_enable platform);
|
||||
task_watch_item!(gpu_mux_mode platform);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlPlatform {
|
||||
fn zbus_path() -> &'static str {
|
||||
ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let platform1 = self.clone();
|
||||
let platform2 = self.clone();
|
||||
self.create_sys_event_tasks(
|
||||
move || {},
|
||||
move || {
|
||||
info!("CtrlRogBios reloading panel_od");
|
||||
if let Some(lock) = platform1.config.try_lock() {
|
||||
if platform1.platform.has_panel_od() {
|
||||
platform1
|
||||
.set_panel_overdrive(lock.panel_od)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
},
|
||||
move || {},
|
||||
move || {
|
||||
info!("CtrlRogBios reloading panel_od");
|
||||
if let Some(lock) = platform2.config.try_lock() {
|
||||
if platform2.platform.has_panel_od() {
|
||||
platform2
|
||||
.set_panel_overdrive(lock.panel_od)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
self.watch_panel_od(signal_ctxt.clone()).await?;
|
||||
self.watch_dgpu_disable(signal_ctxt.clone()).await?;
|
||||
self.watch_egpu_enable(signal_ctxt.clone()).await?;
|
||||
self.watch_gpu_mux_mode(signal_ctxt.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
239
daemon/src/ctrl_power.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
use crate::systemd::{do_systemd_unit_action, SystemdUnitAction};
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
use crate::{task_watch_item, CtrlTask};
|
||||
use async_trait::async_trait;
|
||||
use log::{info, warn};
|
||||
use rog_platform::power::AsusPower;
|
||||
use rog_platform::supported::ChargeSupportedFunctions;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use zbus::dbus_interface;
|
||||
use zbus::export::futures_util::lock::Mutex;
|
||||
use zbus::Connection;
|
||||
use zbus::SignalContext;
|
||||
|
||||
const ZBUS_PATH: &str = "/org/asuslinux/Power";
|
||||
const NVIDIA_POWERD: &str = "nvidia-powerd.service";
|
||||
|
||||
impl GetSupported for CtrlPower {
|
||||
type A = ChargeSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
ChargeSupportedFunctions {
|
||||
charge_level_set: if let Ok(power) = AsusPower::new() {
|
||||
power.has_charge_control_end_threshold()
|
||||
} else {
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlPower {
|
||||
power: AsusPower,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlPower {
|
||||
async fn set_charge_control_end_threshold(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
limit: u8,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
if !(20..=100).contains(&limit) {
|
||||
return Err(RogError::ChargeLimit(limit))?;
|
||||
}
|
||||
self.set(limit)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
Self::notify_charge_control_end_threshold(&ctxt, limit)
|
||||
.await
|
||||
.ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn charge_control_end_threshold(&self) -> u8 {
|
||||
loop {
|
||||
if let Some(mut config) = self.config.try_lock() {
|
||||
let limit = self
|
||||
.power
|
||||
.get_charge_control_end_threshold()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: get_charge_control_end_threshold {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(100);
|
||||
|
||||
config.read();
|
||||
config.bat_charge_limit = limit;
|
||||
config.write();
|
||||
|
||||
return config.bat_charge_limit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mains_online(&self) -> bool {
|
||||
if self.power.has_online() {
|
||||
if let Ok(v) = self.power.get_online() {
|
||||
return v == 1;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_charge_control_end_threshold(
|
||||
ctxt: &SignalContext<'_>,
|
||||
limit: u8,
|
||||
) -> zbus::Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_mains_online(ctxt: &SignalContext<'_>, on: bool) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusRun for CtrlPower {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Reloadable for CtrlPower {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Some(mut config) = self.config.try_lock() {
|
||||
config.read();
|
||||
self.set(config.bat_charge_limit)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlPower {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
Ok(CtrlPower {
|
||||
power: AsusPower::new()?,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn set(&self, limit: u8) -> Result<(), RogError> {
|
||||
if !(20..=100).contains(&limit) {
|
||||
return Err(RogError::ChargeLimit(limit));
|
||||
}
|
||||
|
||||
self.power.set_charge_control_end_threshold(limit)?;
|
||||
|
||||
info!("Battery charge limit: {}", limit);
|
||||
|
||||
if let Some(mut config) = self.config.try_lock() {
|
||||
config.read();
|
||||
config.bat_charge_limit = limit;
|
||||
config.write();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
task_watch_item!(charge_control_end_threshold power);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlPower {
|
||||
fn zbus_path() -> &'static str {
|
||||
ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let power1 = self.clone();
|
||||
let power2 = self.clone();
|
||||
self.create_sys_event_tasks(
|
||||
move || {},
|
||||
move || {
|
||||
info!("CtrlCharge reloading charge limit");
|
||||
if let Some(lock) = power1.config.try_lock() {
|
||||
power1
|
||||
.set(lock.bat_charge_limit)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
if let Ok(value) = power1.power.get_online() {
|
||||
let action = if value == 1 {
|
||||
SystemdUnitAction::Restart
|
||||
} else {
|
||||
SystemdUnitAction::Stop
|
||||
};
|
||||
if do_systemd_unit_action(action, NVIDIA_POWERD).is_ok() {
|
||||
info!("CtrlPower task: did {action:?} on {NVIDIA_POWERD}");
|
||||
}
|
||||
}
|
||||
},
|
||||
move || {},
|
||||
move || {
|
||||
info!("CtrlCharge reloading charge limit");
|
||||
if let Some(lock) = power2.config.try_lock() {
|
||||
power2
|
||||
.set(lock.bat_charge_limit)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
if let Ok(value) = power2.power.get_online() {
|
||||
let action = if value == 1 {
|
||||
SystemdUnitAction::Restart
|
||||
} else {
|
||||
SystemdUnitAction::Stop
|
||||
};
|
||||
if do_systemd_unit_action(action, NVIDIA_POWERD).is_ok() {
|
||||
info!("CtrlPower task: did {action:?} on {NVIDIA_POWERD}");
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
self.watch_charge_control_end_threshold(signal_ctxt.clone())
|
||||
.await?;
|
||||
|
||||
let ctrl = self.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut online = 10;
|
||||
loop {
|
||||
if let Ok(value) = ctrl.power.get_online() {
|
||||
if online != value {
|
||||
online = value;
|
||||
let action = if value == 1 {
|
||||
SystemdUnitAction::Restart
|
||||
} else {
|
||||
SystemdUnitAction::Stop
|
||||
};
|
||||
if do_systemd_unit_action(action, NVIDIA_POWERD).is_ok() {
|
||||
info!("CtrlPower task: did {action:?} on {NVIDIA_POWERD}");
|
||||
}
|
||||
|
||||
Self::notify_mains_online(&signal_ctxt, value == 1)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
// The inotify doesn't pick up events when the kernel changes internal value
|
||||
// so we need to watch it with a thread and sleep unfortunately
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -23,17 +23,6 @@ impl ProfileConfig {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -46,7 +35,6 @@ impl ProfileConfig {
|
||||
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;
|
||||
@@ -55,7 +43,8 @@ impl ProfileConfig {
|
||||
"Could not deserialise {}.\nWill rename to {}-old and recreate config",
|
||||
config_path, config_path
|
||||
);
|
||||
let cfg_old = config_path.clone() + "-old";
|
||||
let mut cfg_old = config_path.clone();
|
||||
cfg_old.push_str("-old");
|
||||
std::fs::rename(config_path.clone(), cfg_old).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
@@ -63,11 +52,9 @@ impl ProfileConfig {
|
||||
)
|
||||
});
|
||||
config = Self::new(config_path);
|
||||
config.set_defaults_and_save();
|
||||
}
|
||||
} else {
|
||||
config = Self::new(config_path);
|
||||
config.set_defaults_and_save();
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::error::RogError;
|
||||
use crate::{CtrlTask, GetSupported};
|
||||
use async_trait::async_trait;
|
||||
use crate::GetSupported;
|
||||
use log::{info, warn};
|
||||
use rog_platform::platform::AsusPlatform;
|
||||
use rog_platform::supported::PlatformProfileFunctions;
|
||||
use rog_profiles::error::ProfileError;
|
||||
use rog_profiles::{FanCurveProfiles, Profile};
|
||||
use rog_supported::PlatformProfileFunctions;
|
||||
use smol::Executor;
|
||||
|
||||
use super::config::ProfileConfig;
|
||||
|
||||
pub struct CtrlPlatformProfile {
|
||||
pub config: ProfileConfig,
|
||||
pub platform: AsusPlatform,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlPlatformProfile {
|
||||
@@ -20,12 +18,7 @@ impl GetSupported for CtrlPlatformProfile {
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
if !Profile::is_platform_profile_supported() {
|
||||
warn!(
|
||||
r#"
|
||||
platform_profile kernel interface not found, your laptop does not support this, or the interface is missing.
|
||||
To enable profile support you require a kernel version 5.15.2 minimum.
|
||||
"#
|
||||
);
|
||||
warn!("platform_profile kernel interface not found, your laptop does not support this, or the interface is missing.");
|
||||
}
|
||||
|
||||
let res = FanCurveProfiles::is_supported();
|
||||
@@ -35,14 +28,7 @@ To enable profile support you require a kernel version 5.15.2 minimum.
|
||||
};
|
||||
|
||||
if !fan_curve_supported {
|
||||
info!(
|
||||
r#"
|
||||
fan curves kernel interface not found, your laptop does not support this, or the interface is missing.
|
||||
To enable fan-curve support you require a kernel with the following patch applied:
|
||||
https://lkml.org/lkml/2021/10/23/250
|
||||
This patch has been accepted upstream for 5.17 kernel release.
|
||||
"#
|
||||
);
|
||||
info!("fan curves kernel interface not found, your laptop does not support this, or the interface is missing.");
|
||||
}
|
||||
|
||||
PlatformProfileFunctions {
|
||||
@@ -52,31 +38,33 @@ This patch has been accepted upstream for 5.17 kernel release.
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlPlatformProfile {
|
||||
/// Fetch the active profile and use that to set all related components up
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Some(curves) = &mut self.config.fan_curves {
|
||||
if let Ok(mut device) = FanCurveProfiles::get_device() {
|
||||
// There is a possibility that the curve was default zeroed, so this call initialises
|
||||
// the data from system read and we need to save it after
|
||||
curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?;
|
||||
self.config.write();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlPlatformProfile {
|
||||
pub fn new(config: ProfileConfig) -> Result<Self, RogError> {
|
||||
if Profile::is_platform_profile_supported() {
|
||||
let platform = AsusPlatform::new()?;
|
||||
if platform.has_platform_profile() || platform.has_throttle_thermal_policy() {
|
||||
info!("Device has profile control available");
|
||||
|
||||
let mut controller = CtrlPlatformProfile { config, platform };
|
||||
if FanCurveProfiles::get_device().is_ok() {
|
||||
info!("Device has fan curves available");
|
||||
if controller.config.fan_curves.is_none() {
|
||||
controller.config.fan_curves = Some(Default::default());
|
||||
for _ in [Profile::Balanced, Profile::Performance, Profile::Quiet] {
|
||||
controller.set_next_profile()?;
|
||||
controller.set_active_curve_to_defaults()?;
|
||||
|
||||
let active = Profile::get_active_profile().unwrap_or(Profile::Balanced);
|
||||
if let Some(curves) = controller.config.fan_curves.as_ref() {
|
||||
info!(
|
||||
"{active:?}: {}",
|
||||
String::from(curves.get_fan_curves_for(active))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(CtrlPlatformProfile { config });
|
||||
return Ok(controller);
|
||||
}
|
||||
|
||||
Err(ProfileError::NotSupported.into())
|
||||
@@ -126,32 +114,3 @@ impl CtrlPlatformProfile {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlProfileTask {
|
||||
ctrl: Arc<Mutex<CtrlPlatformProfile>>,
|
||||
}
|
||||
|
||||
impl CtrlProfileTask {
|
||||
pub fn new(ctrl: Arc<Mutex<CtrlPlatformProfile>>) -> Self {
|
||||
Self { ctrl }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlProfileTask {
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let ctrl = self.ctrl.clone();
|
||||
self.repeating_task(666, executor, move || {
|
||||
if let Ok(ref mut lock) = ctrl.try_lock() {
|
||||
let new_profile = Profile::get_active_profile().unwrap();
|
||||
if new_profile != lock.config.active_profile {
|
||||
lock.config.active_profile = new_profile;
|
||||
lock.write_profile_curve_to_platform().unwrap();
|
||||
lock.save_config();
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod controller;
|
||||
pub mod zbus;
|
||||
/// Implements `CtrlTask`, Reloadable, `ZbusRun`
|
||||
pub mod trait_impls;
|
||||
|
||||
246
daemon/src/ctrl_profiles/trait_impls.rs
Normal file
@@ -0,0 +1,246 @@
|
||||
use async_trait::async_trait;
|
||||
use log::warn;
|
||||
use rog_profiles::fan_curve_set::CurveData;
|
||||
use rog_profiles::fan_curve_set::FanCurveSet;
|
||||
use rog_profiles::FanCurveProfiles;
|
||||
use rog_profiles::Profile;
|
||||
use zbus::export::futures_util::lock::Mutex;
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
use zbus::Connection;
|
||||
use zbus::SignalContext;
|
||||
|
||||
use std::sync::Arc;
|
||||
use zbus::{dbus_interface, fdo::Error};
|
||||
|
||||
use crate::error::RogError;
|
||||
use crate::CtrlTask;
|
||||
|
||||
use super::controller::CtrlPlatformProfile;
|
||||
|
||||
const ZBUS_PATH: &str = "/org/asuslinux/Profile";
|
||||
const UNSUPPORTED_MSG: &str =
|
||||
"Fan curves are not supported on this laptop or you require a patched kernel";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ProfileZbus(pub Arc<Mutex<CtrlPlatformProfile>>);
|
||||
|
||||
#[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_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Toggle to next platform_profile. Names provided by `Profiles`.
|
||||
/// If fan-curves are supported will also activate a fan curve for profile.
|
||||
async fn next_profile(&mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.set_next_profile()
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
ctrl.save_config();
|
||||
|
||||
Self::notify_profile(&ctxt, ctrl.config.active_profile)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Fetch the active profile name
|
||||
async fn active_profile(&mut self) -> zbus::fdo::Result<Profile> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.config.read();
|
||||
Ok(ctrl.config.active_profile)
|
||||
}
|
||||
|
||||
/// Set this platform_profile name as active
|
||||
async fn set_active_profile(
|
||||
&self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
profile: Profile,
|
||||
) {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
// 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::notify_profile(&ctxt, ctrl.config.active_profile)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Get a list of profiles that have fan-curves enabled.
|
||||
async fn enabled_fan_profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.config.read();
|
||||
if let Some(curves) = &ctrl.config.fan_curves {
|
||||
return Ok(curves.get_enabled_curve_profiles());
|
||||
}
|
||||
Err(Error::Failed(UNSUPPORTED_MSG.to_owned()))
|
||||
}
|
||||
|
||||
/// Set a profile fan curve enabled status. Will also activate a fan curve if in the
|
||||
/// same profile mode
|
||||
async fn set_fan_curve_enabled(
|
||||
&mut self,
|
||||
profile: Profile,
|
||||
enabled: bool,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
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();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Failed(UNSUPPORTED_MSG.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the fan-curve data for the currently active Profile
|
||||
async fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result<FanCurveSet> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.config.read();
|
||||
if let Some(curves) = &ctrl.config.fan_curves {
|
||||
let curve = curves.get_fan_curves_for(profile);
|
||||
return Ok(curve.clone());
|
||||
}
|
||||
Err(Error::Failed(UNSUPPORTED_MSG.to_owned()))
|
||||
}
|
||||
|
||||
/// Set the fan curve for the specified profile.
|
||||
/// Will also activate the fan curve if the user is in the same mode.
|
||||
async fn set_fan_curve(&self, profile: Profile, curve: CurveData) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
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_owned()));
|
||||
}
|
||||
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.
|
||||
async fn set_active_curve_to_defaults(&self) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
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(())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
async fn reset_profile_curves(&self, profile: Profile) -> zbus::fdo::Result<()> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.config.read();
|
||||
let active = Profile::get_active_profile().unwrap_or(Profile::Balanced);
|
||||
|
||||
Profile::set_profile(profile)
|
||||
.map_err(|e| warn!("set_profile, {}", e))
|
||||
.ok();
|
||||
ctrl.set_active_curve_to_defaults()
|
||||
.map_err(|e| warn!("Profile::set_active_curve_to_defaults, {}", e))
|
||||
.ok();
|
||||
|
||||
Profile::set_profile(active)
|
||||
.map_err(|e| warn!("set_profile, {}", e))
|
||||
.ok();
|
||||
ctrl.save_config();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_profile(signal_ctxt: &SignalContext<'_>, profile: Profile) -> zbus::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusRun for ProfileZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for ProfileZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let ctrl = self.0.clone();
|
||||
let mut watch = self.0.lock().await.platform.monitor_platform_profile()?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buffer = [0; 32];
|
||||
watch
|
||||
.event_stream(&mut buffer)
|
||||
.unwrap()
|
||||
.for_each(|_| async {
|
||||
let mut lock = ctrl.lock().await;
|
||||
let new_profile = Profile::get_active_profile().unwrap();
|
||||
if new_profile != lock.config.active_profile {
|
||||
lock.config.active_profile = new_profile;
|
||||
lock.write_profile_curve_to_platform().unwrap();
|
||||
lock.save_config();
|
||||
}
|
||||
Self::notify_profile(&signal_ctxt.clone(), lock.config.active_profile)
|
||||
.await
|
||||
.ok();
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Reloadable for ProfileZbus {
|
||||
/// Fetch the active profile and use that to set all related components up
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
let active = ctrl.config.active_profile;
|
||||
if let Some(curves) = &mut ctrl.config.fan_curves {
|
||||
if let Ok(mut device) = FanCurveProfiles::get_device() {
|
||||
// There is a possibility that the curve was default zeroed, so this call initialises
|
||||
// the data from system read and we need to save it after
|
||||
curves.write_profile_curve_to_platform(active, &mut device)?;
|
||||
ctrl.config.write();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use log::warn;
|
||||
use rog_profiles::fan_curve_set::CurveData;
|
||||
use rog_profiles::fan_curve_set::FanCurveSet;
|
||||
use rog_profiles::Profile;
|
||||
use zbus::Connection;
|
||||
use zbus::SignalContext;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::{dbus_interface, fdo::Error};
|
||||
|
||||
use super::controller::CtrlPlatformProfile;
|
||||
|
||||
static UNSUPPORTED_MSG: &str =
|
||||
"Fan curves are not supported on this laptop or you require a patched kernel";
|
||||
|
||||
pub struct ProfileZbus {
|
||||
inner: Arc<Mutex<CtrlPlatformProfile>>,
|
||||
}
|
||||
|
||||
impl ProfileZbus {
|
||||
pub fn new(inner: Arc<Mutex<CtrlPlatformProfile>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl ProfileZbus {
|
||||
/// Fetch profile names
|
||||
fn profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
|
||||
if let Ok(profiles) = Profile::get_profile_names() {
|
||||
return Ok(profiles);
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get all profile details".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Toggle to next platform_profile. Names provided by `Profiles`.
|
||||
/// If fan-curves are supported will also activate a fan curve for profile.
|
||||
async fn next_profile(&mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
|
||||
let mut profile = None;
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.set_next_profile()
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
ctrl.save_config();
|
||||
profile = Some(ctrl.config.active_profile);
|
||||
}
|
||||
if let Some(profile) = profile {
|
||||
Self::notify_profile(&ctxt, profile).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the active profile name
|
||||
fn active_profile(&mut self) -> zbus::fdo::Result<Profile> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
return Ok(ctrl.config.active_profile);
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get active profile name".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Set this platform_profile name as active
|
||||
async fn set_active_profile(
|
||||
&self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
profile: Profile,
|
||||
) {
|
||||
let mut tmp = None;
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
// Read first just incase the user has modified the config before calling this
|
||||
ctrl.config.read();
|
||||
Profile::set_profile(profile)
|
||||
.map_err(|e| warn!("set_profile, {}", e))
|
||||
.ok();
|
||||
ctrl.config.active_profile = profile;
|
||||
ctrl.write_profile_curve_to_platform()
|
||||
.map_err(|e| warn!("write_profile_curve_to_platform, {}", e))
|
||||
.ok();
|
||||
|
||||
ctrl.save_config();
|
||||
tmp = Some(ctrl.config.active_profile);
|
||||
}
|
||||
if let Some(profile) = tmp {
|
||||
Self::notify_profile(&ctxt, profile).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a list of profiles that have fan-curves enabled.
|
||||
fn enabled_fan_profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
if let Some(curves) = &ctrl.config.fan_curves {
|
||||
return Ok(curves.get_enabled_curve_profiles().to_vec());
|
||||
}
|
||||
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get enabled fan curve names".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Set a profile fan curve enabled status. Will also activate a fan curve if in the
|
||||
/// same profile mode
|
||||
fn set_fan_curve_enabled(&mut self, profile: Profile, enabled: bool) -> zbus::fdo::Result<()> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
return if let Some(curves) = &mut ctrl.config.fan_curves {
|
||||
curves.set_profile_curve_enabled(profile, enabled);
|
||||
|
||||
ctrl.write_profile_curve_to_platform()
|
||||
.map_err(|e| warn!("write_profile_curve_to_platform, {}", e))
|
||||
.ok();
|
||||
|
||||
ctrl.save_config();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Failed(UNSUPPORTED_MSG.to_string()))
|
||||
};
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get enabled fan curve names".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Get the fan-curve data for the currently active Profile
|
||||
fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result<FanCurveSet> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
if let Some(curves) = &ctrl.config.fan_curves {
|
||||
let curve = curves.get_fan_curves_for(profile);
|
||||
return Ok(curve.clone());
|
||||
}
|
||||
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
|
||||
}
|
||||
Err(Error::Failed("Failed to get fan curve data".to_string()))
|
||||
}
|
||||
|
||||
/// Set the fan curve for the specified profile.
|
||||
/// Will also activate the fan curve if the user is in the same mode.
|
||||
fn set_fan_curve(&self, profile: Profile, curve: CurveData) -> zbus::fdo::Result<()> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
if let Some(curves) = &mut ctrl.config.fan_curves {
|
||||
curves
|
||||
.save_fan_curve(curve, profile)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
} else {
|
||||
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
|
||||
}
|
||||
ctrl.write_profile_curve_to_platform()
|
||||
.map_err(|e| warn!("Profile::set_profile, {}", e))
|
||||
.ok();
|
||||
ctrl.save_config();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset the stored (self) and device curve to the defaults of the platform.
|
||||
///
|
||||
/// Each platform_profile has a different default and the defualt can be read
|
||||
/// only for the currently active profile.
|
||||
fn set_active_curve_to_defaults(&self) -> zbus::fdo::Result<()> {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.config.read();
|
||||
ctrl.set_active_curve_to_defaults()
|
||||
.map_err(|e| warn!("Profile::set_active_curve_to_defaults, {}", e))
|
||||
.ok();
|
||||
ctrl.save_config();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_profile(signal_ctxt: &SignalContext<'_>, profile: Profile) -> zbus::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for ProfileZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Profile", server).await;
|
||||
}
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use rog_supported::RogBiosSupportedFunctions;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufRead;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::Connection;
|
||||
use zbus::{dbus_interface, SignalContext};
|
||||
|
||||
const INITRAMFS_PATH: &str = "/usr/sbin/update-initramfs";
|
||||
const DRACUT_PATH: &str = "/usr/bin/dracut";
|
||||
|
||||
static ASUS_SWITCH_GRAPHIC_MODE: &str =
|
||||
"/sys/firmware/efi/efivars/AsusSwitchGraphicMode-607005d5-3f75-4b2e-98f0-85ba66797a3e";
|
||||
static ASUS_POST_LOGO_SOUND: &str =
|
||||
"/sys/firmware/efi/efivars/AsusPostLogoSound-607005d5-3f75-4b2e-98f0-85ba66797a3e";
|
||||
|
||||
pub struct CtrlRogBios {
|
||||
_config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlRogBios {
|
||||
type A = RogBiosSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
RogBiosSupportedFunctions {
|
||||
post_sound_toggle: Path::new(ASUS_POST_LOGO_SOUND).exists(),
|
||||
dedicated_gfx_toggle: Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlRogBios {
|
||||
async fn set_dedicated_graphic_mode(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
dedicated: bool,
|
||||
) {
|
||||
self.set_gfx_mode(dedicated)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_asus_switch_graphic_mode {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
Self::notify_dedicated_graphic_mode(&ctxt, dedicated)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn dedicated_graphic_mode(&self) -> i8 {
|
||||
Self::get_gfx_mode()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_gfx_mode {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(-1)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_dedicated_graphic_mode(
|
||||
signal_ctxt: &SignalContext<'_>,
|
||||
dedicated: bool,
|
||||
) -> zbus::Result<()> {
|
||||
}
|
||||
|
||||
async fn set_post_boot_sound(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
on: bool,
|
||||
) {
|
||||
Self::set_boot_sound(on)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_post_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
Self::notify_post_boot_sound(&ctxt, on).await.ok();
|
||||
}
|
||||
|
||||
fn post_boot_sound(&self) -> i8 {
|
||||
Self::get_boot_sound()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(-1)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn notify_post_boot_sound(ctxt: &SignalContext<'_>, on: bool) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for CtrlRogBios {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/RogBios", server).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlRogBios {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlRogBios {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
if Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists() {
|
||||
CtrlRogBios::set_path_mutable(ASUS_SWITCH_GRAPHIC_MODE)?;
|
||||
} else {
|
||||
info!("G-Sync Switchable Graphics not detected");
|
||||
info!("If your laptop is not a G-Sync enabled laptop then you can ignore this. Standard graphics switching will still work.");
|
||||
}
|
||||
|
||||
if Path::new(ASUS_POST_LOGO_SOUND).exists() {
|
||||
CtrlRogBios::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
|
||||
} else {
|
||||
info!("Switch for POST boot sound not detected");
|
||||
}
|
||||
|
||||
Ok(CtrlRogBios { _config: config })
|
||||
}
|
||||
|
||||
fn set_path_mutable(path: &str) -> Result<(), RogError> {
|
||||
let output = Command::new("/usr/bin/chattr")
|
||||
.arg("-i")
|
||||
.arg(path)
|
||||
.output()
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
info!("Set {} writeable: status: {}", path, output.status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has_dedicated_gfx_toggle() -> bool {
|
||||
Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists()
|
||||
}
|
||||
|
||||
pub fn get_gfx_mode() -> Result<i8, RogError> {
|
||||
let path = ASUS_SWITCH_GRAPHIC_MODE;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)
|
||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
Ok(data[idx] as i8)
|
||||
}
|
||||
|
||||
pub(super) fn set_gfx_mode(&self, dedicated: bool) -> Result<(), RogError> {
|
||||
let path = ASUS_SWITCH_GRAPHIC_MODE;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
if dedicated {
|
||||
data[idx] = 1;
|
||||
info!("Set system-level graphics mode: Dedicated Nvidia");
|
||||
} else {
|
||||
data[idx] = 0;
|
||||
info!("Set system-level graphics mode: Optimus");
|
||||
}
|
||||
file.write_all(&data)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
self.update_initramfs(dedicated)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_boot_sound() -> Result<i8, RogError> {
|
||||
let path = ASUS_POST_LOGO_SOUND;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)
|
||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
Ok(data[idx] as i8)
|
||||
}
|
||||
|
||||
pub(super) fn set_boot_sound(on: bool) -> Result<(), RogError> {
|
||||
let path = ASUS_POST_LOGO_SOUND;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)
|
||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
if on {
|
||||
data[idx] = 1;
|
||||
info!("Set boot POST sound on");
|
||||
} else {
|
||||
data[idx] = 0;
|
||||
info!("Set boot POST sound off");
|
||||
}
|
||||
file.write_all(&data)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// required for g-sync mode
|
||||
fn update_initramfs(&self, dedicated: bool) -> Result<(), RogError> {
|
||||
let mut initfs_cmd = None;
|
||||
|
||||
if Path::new(INITRAMFS_PATH).exists() {
|
||||
let mut cmd = Command::new("update-initramfs");
|
||||
cmd.arg("-u");
|
||||
initfs_cmd = Some(cmd);
|
||||
info!("Using initramfs update command 'update-initramfs'");
|
||||
} else if Path::new(DRACUT_PATH).exists() {
|
||||
let mut cmd = Command::new("dracut");
|
||||
cmd.arg("-f");
|
||||
cmd.arg("-q");
|
||||
initfs_cmd = Some(cmd);
|
||||
info!("Using initramfs update command 'dracut'");
|
||||
}
|
||||
|
||||
if let Some(mut cmd) = initfs_cmd {
|
||||
info!("Updating initramfs");
|
||||
|
||||
// If switching to Nvidia dedicated we need these modules included
|
||||
if Path::new(DRACUT_PATH).exists() && dedicated {
|
||||
cmd.arg("--add-drivers");
|
||||
cmd.arg("nvidia nvidia-drm nvidia-modeset nvidia-uvm");
|
||||
info!("System uses dracut, forcing nvidia modules to be included in init");
|
||||
} else if Path::new(INITRAMFS_PATH).exists() {
|
||||
let modules = vec![
|
||||
"nvidia\n",
|
||||
"nvidia-drm\n",
|
||||
"nvidia-modeset\n",
|
||||
"nvidia-uvm\n",
|
||||
];
|
||||
|
||||
let module_include = Path::new("/etc/initramfs-tools/modules");
|
||||
|
||||
if dedicated {
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(module_include)
|
||||
.map_err(|err| {
|
||||
RogError::Write(module_include.to_string_lossy().to_string(), err)
|
||||
})?;
|
||||
// add nvidia modules to module_include
|
||||
file.write_all(modules.concat().as_bytes())?;
|
||||
} else {
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(module_include)
|
||||
.map_err(|err| {
|
||||
RogError::Write(module_include.to_string_lossy().to_string(), err)
|
||||
})?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
// remove modules
|
||||
for line in std::io::BufReader::new(file).lines().flatten() {
|
||||
if !modules.contains(&line.as_str()) {
|
||||
buf.append(&mut line.as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.open(module_include)
|
||||
.map_err(|err| {
|
||||
RogError::Write(module_include.to_string_lossy().to_string(), err)
|
||||
})?;
|
||||
std::io::BufWriter::new(file).write_all(&buf)?;
|
||||
}
|
||||
}
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|err| RogError::Write(format!("{:?}", cmd), err))?;
|
||||
if !status.success() {
|
||||
error!("Ram disk update failed");
|
||||
return Err(RogError::Initramfs("Ram disk update failed".into()));
|
||||
} else {
|
||||
info!("Successfully updated initramfs");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
use async_trait::async_trait;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use zbus::dbus_interface;
|
||||
use zbus::Connection;
|
||||
use zvariant::Type;
|
||||
use zbus::{dbus_interface, zvariant::Type, Connection};
|
||||
|
||||
use crate::{
|
||||
ctrl_anime::CtrlAnime, ctrl_aura::controller::CtrlKbdLed, ctrl_charge::CtrlCharge,
|
||||
ctrl_profiles::controller::CtrlPlatformProfile, ctrl_rog_bios::CtrlRogBios, GetSupported,
|
||||
ctrl_anime::CtrlAnime, ctrl_aura::controller::CtrlKbdLed, ctrl_platform::CtrlPlatform,
|
||||
ctrl_power::CtrlPower, ctrl_profiles::controller::CtrlPlatformProfile, GetSupported,
|
||||
};
|
||||
|
||||
use rog_supported::{
|
||||
AnimeSupportedFunctions, ChargeSupportedFunctions, LedSupportedFunctions,
|
||||
PlatformProfileFunctions, RogBiosSupportedFunctions,
|
||||
};
|
||||
use rog_platform::supported::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Type)]
|
||||
pub struct SupportedFunctions {
|
||||
@@ -31,7 +26,7 @@ impl SupportedFunctions {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusAdd for SupportedFunctions {
|
||||
impl crate::ZbusRun for SupportedFunctions {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, "/org/asuslinux/Supported", server).await;
|
||||
}
|
||||
@@ -44,9 +39,9 @@ impl GetSupported for SupportedFunctions {
|
||||
SupportedFunctions {
|
||||
anime_ctrl: CtrlAnime::get_supported(),
|
||||
keyboard_led: CtrlKbdLed::get_supported(),
|
||||
charge_ctrl: CtrlCharge::get_supported(),
|
||||
charge_ctrl: CtrlPower::get_supported(),
|
||||
platform_profile: CtrlPlatformProfile::get_supported(),
|
||||
rog_bios_ctrl: CtrlRogBios::get_supported(),
|
||||
rog_bios_ctrl: CtrlPlatform::get_supported(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use ::zbus::export::futures_util::lock::Mutex;
|
||||
use ::zbus::Connection;
|
||||
use daemon::ctrl_profiles::controller::CtrlProfileTask;
|
||||
use daemon::ctrl_anime::CtrlAnime;
|
||||
use log::LevelFilter;
|
||||
use log::{error, info, warn};
|
||||
use smol::Executor;
|
||||
use tokio::time::sleep;
|
||||
use zbus::SignalContext;
|
||||
|
||||
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_anime::{config::AnimeConfig, trait_impls::CtrlAnimeZbus};
|
||||
use daemon::ctrl_aura::{config::AuraConfig, controller::CtrlKbdLed, trait_impls::CtrlKbdLedZbus};
|
||||
use daemon::ctrl_platform::CtrlPlatform;
|
||||
use daemon::ctrl_power::CtrlPower;
|
||||
use daemon::ctrl_profiles::{
|
||||
config::ProfileConfig, controller::CtrlPlatformProfile, trait_impls::ProfileZbus,
|
||||
};
|
||||
use daemon::ctrl_charge::CtrlCharge;
|
||||
use daemon::ctrl_profiles::config::ProfileConfig;
|
||||
use daemon::ctrl_rog_bios::CtrlRogBios;
|
||||
use daemon::laptops::LaptopLedData;
|
||||
use daemon::{
|
||||
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
|
||||
};
|
||||
use daemon::{
|
||||
ctrl_profiles::{controller::CtrlPlatformProfile, zbus::ProfileZbus},
|
||||
laptops::LaptopLedData,
|
||||
};
|
||||
use daemon::{CtrlTask, Reloadable, ZbusAdd};
|
||||
use daemon::{CtrlTask, Reloadable, ZbusRun};
|
||||
use rog_dbus::DBUS_NAME;
|
||||
use rog_profiles::Profile;
|
||||
|
||||
static PROFILE_CONFIG_PATH: &str = "/etc/asusd/profile.conf";
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut logger = env_logger::Builder::new();
|
||||
logger
|
||||
.target(env_logger::Target::Stdout)
|
||||
@@ -59,16 +57,14 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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);
|
||||
info!("rog-platform v{}", rog_platform::VERSION);
|
||||
|
||||
let mut executor = Executor::new();
|
||||
|
||||
smol::block_on(start_daemon(&mut executor))?;
|
||||
start_daemon().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The actual main loop for the daemon
|
||||
async fn start_daemon(executor: &mut Executor<'_>) -> Result<(), Box<dyn Error>> {
|
||||
async fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
let supported = SupportedFunctions::get_supported();
|
||||
print_board_info();
|
||||
println!("{}", serde_json::to_string_pretty(&supported)?);
|
||||
@@ -81,74 +77,50 @@ async fn start_daemon(executor: &mut Executor<'_>) -> Result<(), Box<dyn Error>>
|
||||
|
||||
supported.add_to_server(&mut connection).await;
|
||||
|
||||
match CtrlRogBios::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
// Do a reload of any settings
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
|
||||
// Then register to dbus server
|
||||
ctrl.add_to_server(&mut connection).await;
|
||||
match CtrlPlatform::new(config.clone()) {
|
||||
Ok(ctrl) => {
|
||||
let sig_ctx = CtrlPlatform::signal_context(&connection)?;
|
||||
start_tasks(ctrl, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("rog_bios_control: {}", err);
|
||||
error!("CtrlPlatform: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlCharge::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
// Do a reload of any settings
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
|
||||
// Then register to dbus server
|
||||
ctrl.add_to_server(&mut connection).await;
|
||||
|
||||
let task = CtrlCharge::new(config)?;
|
||||
task.create_tasks(executor).await.ok();
|
||||
match CtrlPower::new(config.clone()) {
|
||||
Ok(ctrl) => {
|
||||
let sig_ctx = CtrlPower::signal_context(&connection)?;
|
||||
start_tasks(ctrl, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("charge_control: {}", err);
|
||||
error!("CtrlPower: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if Profile::is_platform_profile_supported() {
|
||||
let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into());
|
||||
match CtrlPlatformProfile::new(profile_config) {
|
||||
Ok(mut ctrl) => {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Profile control: {}", err));
|
||||
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
let task = CtrlProfileTask::new(tmp.clone());
|
||||
task.create_tasks(executor).await.ok();
|
||||
|
||||
let task = ProfileZbus::new(tmp.clone());
|
||||
task.add_to_server(&mut connection).await;
|
||||
Ok(ctrl) => {
|
||||
let zbus = ProfileZbus(Arc::new(Mutex::new(ctrl)));
|
||||
let sig_ctx = ProfileZbus::signal_context(&connection)?;
|
||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Profile control: {}", err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("platform_profile support not found. This requires kernel 5.15.x or the patch applied: https://lkml.org/lkml/2021/8/18/1022");
|
||||
warn!("platform_profile support not found");
|
||||
}
|
||||
|
||||
match CtrlAnime::new(AnimeConfig::load()) {
|
||||
Ok(ctrl) => {
|
||||
let inner = Arc::new(Mutex::new(ctrl));
|
||||
|
||||
let mut reload = CtrlAnimeReloader(inner.clone());
|
||||
reload
|
||||
.reload()
|
||||
.unwrap_or_else(|err| warn!("AniMe: {}", err));
|
||||
|
||||
let zbus = CtrlAnimeZbus(inner.clone());
|
||||
zbus.add_to_server(&mut connection).await;
|
||||
|
||||
let task = CtrlAnimeTask::new(inner).await;
|
||||
task.create_tasks(executor).await.ok();
|
||||
let zbus = CtrlAnimeZbus(Arc::new(Mutex::new(ctrl)));
|
||||
let sig_ctx = CtrlAnimeZbus::signal_context(&connection)?;
|
||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("AniMe control: {}", err);
|
||||
info!("AniMe control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,19 +128,9 @@ async fn start_daemon(executor: &mut Executor<'_>) -> Result<(), Box<dyn Error>>
|
||||
let aura_config = AuraConfig::load(&laptop);
|
||||
match CtrlKbdLed::new(laptop, aura_config) {
|
||||
Ok(ctrl) => {
|
||||
let inner = Arc::new(Mutex::new(ctrl));
|
||||
|
||||
let mut reload = CtrlKbdLedReloader(inner.clone());
|
||||
reload
|
||||
.reload()
|
||||
.unwrap_or_else(|err| warn!("Keyboard LED control: {}", err));
|
||||
|
||||
CtrlKbdLedZbus::new(inner.clone())
|
||||
.add_to_server(&mut connection)
|
||||
.await;
|
||||
|
||||
let task = CtrlKbdLedTask::new(inner);
|
||||
task.create_tasks(executor).await.ok();
|
||||
let zbus = CtrlKbdLedZbus(Arc::new(Mutex::new(ctrl)));
|
||||
let sig_ctx = CtrlKbdLedZbus::signal_context(&connection)?;
|
||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Keyboard control: {}", err);
|
||||
@@ -177,7 +139,28 @@ async fn start_daemon(executor: &mut Executor<'_>) -> Result<(), Box<dyn Error>>
|
||||
|
||||
// Request dbus name after finishing initalizing all functions
|
||||
connection.request_name(DBUS_NAME).await?;
|
||||
|
||||
loop {
|
||||
smol::block_on(executor.tick());
|
||||
// This is just a blocker to idle and ensure the reator reacts
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_tasks<T>(
|
||||
mut zbus: T,
|
||||
connection: &mut Connection,
|
||||
signal_ctx: SignalContext<'static>,
|
||||
) -> Result<(), Box<dyn Error>>
|
||||
where
|
||||
T: ZbusRun + Reloadable + CtrlTask + Clone,
|
||||
{
|
||||
let task = zbus.clone();
|
||||
|
||||
zbus.reload()
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("Controller error: {}", err));
|
||||
zbus.add_to_server(connection).await;
|
||||
|
||||
task.create_tasks(signal_ctx).await.ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_platform::error::PlatformError;
|
||||
use rog_profiles::error::ProfileError;
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
@@ -23,11 +25,19 @@ pub enum RogError {
|
||||
Io(std::io::Error),
|
||||
Zbus(zbus::Error),
|
||||
ChargeLimit(u8),
|
||||
AuraEffectNotSupported,
|
||||
NoAuraKeyboard,
|
||||
NoAuraNode,
|
||||
Anime(AnimeError),
|
||||
Platform(PlatformError),
|
||||
SystemdUnitAction(String),
|
||||
SystemdUnitWaitTimeout(String),
|
||||
Command(String, std::io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for RogError {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RogError::ParseVendor => write!(f, "Parse gfx vendor error"),
|
||||
RogError::ParseLed => write!(f, "Parse LED error"),
|
||||
@@ -41,13 +51,29 @@ impl fmt::Display for RogError {
|
||||
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::ReloadFail(deets) => write!(f, "Reload error: {}", deets),
|
||||
RogError::Profiles(deets) => write!(f, "Profile error: {}", deets),
|
||||
RogError::Initramfs(detail) => write!(f, "Initiramfs error: {}", detail),
|
||||
RogError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
|
||||
RogError::Io(detail) => write!(f, "std::io error: {}", detail),
|
||||
RogError::Zbus(detail) => write!(f, "Zbus error: {}", detail),
|
||||
RogError::ChargeLimit(value) => write!(f, "Invalid charging limit, not in range 20-100%: {}", value),
|
||||
RogError::AuraEffectNotSupported => write!(f, "Aura effect not supported"),
|
||||
RogError::NoAuraKeyboard => write!(f, "No supported Aura keyboard"),
|
||||
RogError::NoAuraNode => write!(f, "No Aura keyboard node found"),
|
||||
RogError::Anime(deets) => write!(f, "AniMe Matrix error: {}", deets),
|
||||
RogError::Platform(deets) => write!(f, "Asus Platform error: {}", deets),
|
||||
RogError::SystemdUnitAction(action) => {
|
||||
write!(f, "systemd unit action {} failed", action)
|
||||
}
|
||||
RogError::SystemdUnitWaitTimeout(state) => {
|
||||
write!(
|
||||
f,
|
||||
"Timed out waiting for systemd unit change {} state",
|
||||
state
|
||||
)
|
||||
}
|
||||
RogError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +86,18 @@ impl From<ProfileError> for RogError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnimeError> for RogError {
|
||||
fn from(err: AnimeError) -> Self {
|
||||
RogError::Anime(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlatformError> for RogError {
|
||||
fn from(err: PlatformError) -> Self {
|
||||
RogError::Platform(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<zbus::Error> for RogError {
|
||||
fn from(err: zbus::Error) -> Self {
|
||||
RogError::Zbus(err)
|
||||
|
||||
@@ -71,7 +71,7 @@ impl LaptopLedData {
|
||||
}
|
||||
|
||||
impl LedSupportFile {
|
||||
/// Consumes the LEDModes
|
||||
/// Consumes the `LEDModes`
|
||||
fn matcher(self, prod_family: &str, board_name: &str) -> Option<LaptopLedData> {
|
||||
for config in self.led_data {
|
||||
if prod_family.contains(&config.prod_family) {
|
||||
@@ -90,7 +90,7 @@ impl LedSupportFile {
|
||||
let mut loaded = false;
|
||||
let mut data = LedSupportFile::default();
|
||||
// Load user configs first so they are first to be checked
|
||||
if let Ok(mut file) = OpenOptions::new().read(true).open(&ASUS_LED_MODE_USER_CONF) {
|
||||
if let Ok(mut file) = OpenOptions::new().read(true).open(ASUS_LED_MODE_USER_CONF) {
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
@@ -107,7 +107,7 @@ impl LedSupportFile {
|
||||
}
|
||||
}
|
||||
// Load and append the default LED support data
|
||||
if let Ok(mut file) = OpenOptions::new().read(true).open(&ASUS_LED_MODE_CONF) {
|
||||
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 {
|
||||
|
||||
@@ -1,52 +1,92 @@
|
||||
#![deny(unused_must_use)]
|
||||
/// Configuration loading, saving
|
||||
pub mod config;
|
||||
/// Control of AniMe matrix display
|
||||
/// Control of anime matrix display
|
||||
pub mod ctrl_anime;
|
||||
/// Keyboard LED brightness control, RGB, and LED display modes
|
||||
pub mod ctrl_aura;
|
||||
/// Control of battery charge level
|
||||
pub mod ctrl_charge;
|
||||
/// Control CPU min/max freq and turbo, fan mode, fan curves
|
||||
///
|
||||
/// Intel machines can control:
|
||||
/// - CPU min/max frequency
|
||||
/// - CPU turbo enable/disable
|
||||
/// - Fan mode (normal, boost, silent)
|
||||
///
|
||||
/// AMD machines can control:
|
||||
/// - CPU turbo enable/disable
|
||||
/// - Fan mode (normal, boost, silent)
|
||||
/// - Fan min/max RPM curve
|
||||
pub mod ctrl_profiles;
|
||||
/// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode
|
||||
pub mod ctrl_rog_bios;
|
||||
pub mod ctrl_platform;
|
||||
/// Control of battery charge level
|
||||
pub mod ctrl_power;
|
||||
/// Control platform profiles + fan-curves if available
|
||||
pub mod ctrl_profiles;
|
||||
/// Laptop matching to determine capabilities
|
||||
pub mod laptops;
|
||||
|
||||
pub mod systemd;
|
||||
|
||||
/// Fetch all supported functions for the laptop
|
||||
pub mod ctrl_supported;
|
||||
|
||||
pub mod error;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::error::RogError;
|
||||
use async_trait::async_trait;
|
||||
use config::Config;
|
||||
use log::warn;
|
||||
use smol::{stream::StreamExt, Executor, Timer};
|
||||
use zbus::Connection;
|
||||
use zvariant::ObjectPath;
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use zbus::{export::futures_util::StreamExt, zvariant::ObjectPath, Connection, SignalContext};
|
||||
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
/// This macro adds a function which spawns an `inotify` task on the passed in `Executor`.
|
||||
///
|
||||
/// The generated function is `watch_<name>()`. Self requires the following methods to be available:
|
||||
/// - `<name>() -> SomeValue`, functionally is a getter, but is allowed to have side effects.
|
||||
/// - `notify_<name>(SignalContext, SomeValue)`
|
||||
///
|
||||
/// In most cases if `SomeValue` is stored in a config then `<name>()` getter is expected to update it.
|
||||
/// The getter should *never* write back to the path or attribute that is being watched or an
|
||||
/// infinite loop will occur.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// impl CtrlRogBios {
|
||||
/// task_watch_item!(panel_od platform);
|
||||
/// task_watch_item!(gpu_mux_mode platform);
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! task_watch_item {
|
||||
($name:ident $self_inner:ident) => {
|
||||
concat_idents::concat_idents!(fn_name = watch_, $name {
|
||||
async fn fn_name(
|
||||
&self,
|
||||
signal_ctxt: SignalContext<'static>,
|
||||
) -> Result<(), RogError> {
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
|
||||
let ctrl = self.clone();
|
||||
concat_idents::concat_idents!(watch_fn = monitor_, $name {
|
||||
match self.$self_inner.watch_fn() {
|
||||
Ok(mut watch) => {
|
||||
tokio::spawn(async move {
|
||||
let mut buffer = [0; 32];
|
||||
watch.event_stream(&mut buffer).unwrap().for_each(|_| async {
|
||||
let value = ctrl.$name();
|
||||
concat_idents::concat_idents!(notif_fn = notify_, $name {
|
||||
Self::notif_fn(&signal_ctxt, value).await.ok();
|
||||
});
|
||||
}).await;
|
||||
});
|
||||
}
|
||||
Err(e) => info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e),
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[async_trait]
|
||||
pub trait Reloadable {
|
||||
fn reload(&mut self) -> Result<(), RogError>;
|
||||
async fn reload(&mut self) -> Result<(), RogError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ZbusAdd {
|
||||
pub trait ZbusRun {
|
||||
async fn add_to_server(self, server: &mut Connection);
|
||||
|
||||
async fn add_to_server_helper(
|
||||
@@ -69,32 +109,80 @@ pub trait ZbusAdd {
|
||||
/// Set up a task to run on the async executor
|
||||
#[async_trait]
|
||||
pub trait CtrlTask {
|
||||
fn zbus_path() -> &'static str;
|
||||
|
||||
fn signal_context(connection: &Connection) -> Result<SignalContext<'static>, zbus::Error> {
|
||||
SignalContext::new(connection, Self::zbus_path())
|
||||
}
|
||||
|
||||
/// Implement to set up various tasks that may be required, using the `Executor`.
|
||||
/// No blocking loops are allowed, or they must be run on a separate thread.
|
||||
async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError>;
|
||||
async fn create_tasks(&self, signal: SignalContext<'static>) -> Result<(), RogError>;
|
||||
|
||||
/// Create a timed repeating task
|
||||
async fn repeating_task(
|
||||
// /// Create a timed repeating task
|
||||
// async fn repeating_task(&self, millis: u64, mut task: impl FnMut() + Send + 'static) {
|
||||
// use std::time::Duration;
|
||||
// use tokio::time;
|
||||
// let mut timer = time::interval(Duration::from_millis(millis));
|
||||
// tokio::spawn(async move {
|
||||
// timer.tick().await;
|
||||
// task();
|
||||
// });
|
||||
// }
|
||||
|
||||
/// Free helper method to create tasks to run on: sleep, wake, shutdown, boot
|
||||
///
|
||||
/// The closures can potentially block, so execution time should be the minimal possible
|
||||
/// such as save a variable.
|
||||
async fn create_sys_event_tasks(
|
||||
&self,
|
||||
millis: u64,
|
||||
executor: &mut Executor,
|
||||
mut task: impl FnMut() + Send + 'static,
|
||||
mut on_sleep: impl FnMut() + Send + 'static,
|
||||
mut on_wake: impl FnMut() + Send + 'static,
|
||||
mut on_shutdown: impl FnMut() + Send + 'static,
|
||||
mut on_boot: impl FnMut() + Send + 'static,
|
||||
) {
|
||||
let timer = Timer::interval(Duration::from_millis(millis));
|
||||
executor
|
||||
.spawn(async move {
|
||||
timer.for_each(|_| task()).await;
|
||||
})
|
||||
.detach();
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("Controller could not create dbus connection");
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("Controller could not create ManagerProxy");
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Ok(mut notif) = manager.receive_prepare_for_sleep().await {
|
||||
while let Some(event) = notif.next().await {
|
||||
if let Ok(args) = event.args() {
|
||||
if args.start {
|
||||
on_sleep();
|
||||
} else if !args.start() {
|
||||
on_wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let manager = ManagerProxy::new(&connection)
|
||||
.await
|
||||
.expect("Controller could not create ManagerProxy");
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Ok(mut notif) = manager.receive_prepare_for_shutdown().await {
|
||||
while let Some(event) = notif.next().await {
|
||||
if let Ok(args) = event.args() {
|
||||
if args.start {
|
||||
on_shutdown();
|
||||
} else if !args.start() {
|
||||
on_boot();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CtrlTaskComplex {
|
||||
type A;
|
||||
|
||||
fn do_task(&mut self, config: &mut Config, event: Self::A);
|
||||
}
|
||||
|
||||
pub trait GetSupported {
|
||||
type A;
|
||||
|
||||
|
||||
90
daemon/src/systemd.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::process::Command;
|
||||
|
||||
use crate::error::RogError;
|
||||
|
||||
/// An action for `systemctl`
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum SystemdUnitAction {
|
||||
Stop,
|
||||
Start,
|
||||
Restart,
|
||||
}
|
||||
|
||||
impl From<SystemdUnitAction> for &str {
|
||||
fn from(s: SystemdUnitAction) -> Self {
|
||||
match s {
|
||||
SystemdUnitAction::Stop => "stop",
|
||||
SystemdUnitAction::Start => "start",
|
||||
SystemdUnitAction::Restart => "restart",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum SystemdUnitState {
|
||||
Active,
|
||||
Inactive,
|
||||
}
|
||||
|
||||
impl From<SystemdUnitState> for &str {
|
||||
fn from(s: SystemdUnitState) -> Self {
|
||||
match s {
|
||||
SystemdUnitState::Active => "active",
|
||||
SystemdUnitState::Inactive => "inactive",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the state of a systemd unit. Blocks while running command.
|
||||
pub fn do_systemd_unit_action(action: SystemdUnitAction, unit: &str) -> Result<(), RogError> {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
cmd.arg(<&str>::from(action));
|
||||
cmd.arg(unit);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
|
||||
if !status.success() {
|
||||
let msg = format!("systemctl {action:?} {unit} failed: {status:?}",);
|
||||
return Err(RogError::SystemdUnitAction(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get systemd unit state. Blocks while command is run.
|
||||
pub fn is_systemd_unit_state(state: SystemdUnitState, unit: &str) -> Result<bool, RogError> {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
cmd.arg("is-active");
|
||||
cmd.arg(unit);
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
|
||||
if output.stdout.starts_with(<&str>::from(state).as_bytes()) {
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Wait for a systemd unit to change to `state`. Checks state every 250ms for 3 seconds. Blocks while running wait.
|
||||
pub fn wait_systemd_unit_state(state: SystemdUnitState, unit: &str) -> Result<(), RogError> {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
cmd.arg("is-active");
|
||||
cmd.arg(unit);
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
while count <= (4 * 3) {
|
||||
// 3 seconds max
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
|
||||
if output.stdout.starts_with(<&str>::from(state).as_bytes()) {
|
||||
return Ok(());
|
||||
}
|
||||
// fine to block here, nobody doing shit now
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
count += 1;
|
||||
}
|
||||
Err(RogError::SystemdUnitWaitTimeout(<&str>::from(state).into()))
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
function _asusctl() {
|
||||
local line
|
||||
|
||||
_arguments -C \
|
||||
{-h,--help}'[print help message]' \
|
||||
{-v,--version}'[print version number]' \
|
||||
{-k,--kbd-bright}':[Set keyboard brightness (off, low, med, high)]' \
|
||||
{-p,--pwr-profile}':[Set power profile (silent, normal, boost)]' \
|
||||
{-c,--chg-limit}':[Set charging limit (20-100)]' \
|
||||
': :((led-mode\:"Set the keyboard lighting from built-in modes" profile\:"Create and configure profiles" graphics\:"Set the graphics mode"))' \
|
||||
'*::arg:->args'
|
||||
case $line[1] in
|
||||
led-mode)
|
||||
_arguments ': :((static\:"set a single static colour"
|
||||
breathe\:"pulse between one or two colours"
|
||||
strobe\:"strobe through all colours"
|
||||
rainbow\:"rainbow cycling in one of four directions"
|
||||
star\:"rain pattern mimicking raindrops"
|
||||
rain\:"rain pattern of three preset colours"
|
||||
highlight\:"pressed keys are highlighted to fade"
|
||||
laser\:"pressed keys generate horizontal laser"
|
||||
ripple\:"pressed keys ripple outwards like a splash"
|
||||
pulse\:"set a rapid pulse"
|
||||
comet\:"set a vertical line zooming from left"
|
||||
flash\:"set a wide vertical line zooming from left"
|
||||
multi-static\:"4-zone multi-colour"))' \
|
||||
{-h,--help}'[print help message]' \
|
||||
'-c:[set the RGB value e.g, ff00ff]' \
|
||||
'-s:[set the speed (low, med, high)]'
|
||||
;;
|
||||
profile)
|
||||
_arguments {-h,--help}'[print help message]' \
|
||||
{-c,--create}"[create the profile if it doesn't exist]" \
|
||||
{-t,--turbo}':[enable or disable cpu turbo]' \
|
||||
{-m,--min-percentage}':[set min cpu scaling (intel)]' \
|
||||
{-M,--max-percentage}':[set max cpu scaling (intel)]' \
|
||||
{-p,--preset}':[<silent, normal, boost>]' \
|
||||
{-C,--curve}':[set fan curve]'
|
||||
|
||||
;;
|
||||
graphics)
|
||||
_arguments {-h,--help}'[print help message]' \
|
||||
{-m,--mode}':[Set graphics mode (nvidia, hybrid, compute, integrated)]' \
|
||||
{-g,--get}'[Get the current mode]' \
|
||||
{-p,--pow}'[Get the current power status]' \
|
||||
{-f,--force}'[Do not ask for confirmation]'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
compdef _asusctl asusctl
|
||||
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=ASUS Notifications
|
||||
StartLimitInterval=200
|
||||
StartLimitBurst=2
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/usr/bin/sleep 2
|
||||
ExecStart=/usr/bin/asus-notify
|
||||
Restart=on-failure
|
||||
RestartSec=1
|
||||
Type=simple
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,13 +0,0 @@
|
||||
[Unit]
|
||||
Description=ASUS Notebook Control
|
||||
After=basic.target syslog.target
|
||||
|
||||
[Service]
|
||||
Environment=IS_SERVICE=1
|
||||
ExecStart=/usr/bin/asusd
|
||||
Restart=on-failure
|
||||
Type=dbus
|
||||
BusName=org.asuslinux.Daemon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,3 +1,24 @@
|
||||
[[led_data]]
|
||||
prod_family = "TUF"
|
||||
board_names = ["FA507"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "TUF Gaming"
|
||||
board_names = ["FX505D"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ASUS TUF Gaming A15"
|
||||
board_names = ["FA506I"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus S"
|
||||
board_names = ["GX502", "GX701", "G531", "GL531", "G532"]
|
||||
@@ -7,21 +28,7 @@ per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus M"
|
||||
board_names = ["GU502GV"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus M"
|
||||
board_names = ["GM501GS"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502LW", "GU502LV"]
|
||||
board_names = ["GU502G"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = []
|
||||
per_key = true
|
||||
@@ -33,9 +40,23 @@ standard = ["Static", "Breathe", "Strobe", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502L"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus M16"
|
||||
board_names = ["GU603Z", "GU603H"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus S17"
|
||||
board_names = ["GX703HS"]
|
||||
board_names = ["GX703H"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
@@ -43,21 +64,21 @@ per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus"
|
||||
board_names = ["GM501GM", "GX531"]
|
||||
board_names = ["GM501G", "GX531"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G531GW", "G533QR", "G533QS", "G733QS", "G733QR", "G513QR", "G713QR", "G513QM"]
|
||||
board_names = ["G531GW", "G533QR", "G533QS", "G733Q", "G513QR", "G713QR", "G513QM", "G713IC", "G713RS"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = []
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G513QE", "GX531", "G512LV", "G712LV", "G712LW", "G513IH", "G513QY", "G713QM", "G512"]
|
||||
board_names = ["G513QE", "GX531", "G512LV", "G712LV", "G712LW", "G513IH", "G513QY", "G713QM", "G512", "G713RM", "G713RW"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = ["Key1", "Key2", "Key3", "Key4"]
|
||||
per_key = false
|
||||
@@ -156,14 +177,21 @@ per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Flow X13"
|
||||
board_names = ["GV301QH", "GV301QE"]
|
||||
board_names = ["GV301Q"]
|
||||
standard = ["Static", "Breathe", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G513IC"]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G513IC", "G513RC", "G513RM"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Flow X16"
|
||||
board_names = ["GV601R"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Pulse"]
|
||||
multizone = []
|
||||
per_key = false
|
||||
|
||||
@@ -1,2 +1,20 @@
|
||||
ACTION=="add|change", SUBSYSTEM=="input", ENV{ID_VENDOR_ID}=="0b05", ENV{ID_MODEL_ID}=="1[89][a-zA-Z0-9][a-zA-Z0-9]|193b", ENV{ID_TYPE}=="hid", TAG+="systemd", ENV{SYSTEMD_WANTS}="asusd.service"
|
||||
ACTION=="add|remove", SUBSYSTEM=="input", ENV{ID_VENDOR_ID}=="0b05", ENV{ID_MODEL_ID}=="1[89][a-zA-Z0-9][a-zA-Z0-9]|193b", RUN+="systemctl restart asusd.service"
|
||||
#ACTION=="add|change", SUBSYSTEM=="input", ENV{ID_VENDOR_ID}=="0b05", ENV{ID_MODEL_ID}=="1[89][a-zA-Z0-9][a-zA-Z0-9]|193b", ENV{ID_TYPE}=="hid", TAG+="systemd", ENV{SYSTEMD_WANTS}="asusd.service"
|
||||
#ACTION=="add|remove", SUBSYSTEM=="input", ENV{ID_VENDOR_ID}=="0b05", ENV{ID_MODEL_ID}=="1[89][a-zA-Z0-9][a-zA-Z0-9]|193b", RUN+="systemctl restart asusd.service"
|
||||
|
||||
ENV{DMI_VENDOR}="$attr{[dmi/id]sys_vendor}"
|
||||
ENV{DMI_VENDOR}!="ASUSTeK COMPUTER INC.", GOTO="asusd_end"
|
||||
|
||||
ENV{DMI_FAMILY}="$attr{[dmi/id]product_family}"
|
||||
ENV{DMI_FAMILY}=="*TUF*", GOTO="asusd_start"
|
||||
ENV{DMI_FAMILY}=="*ROG*", GOTO="asusd_start"
|
||||
ENV{DMI_FAMILY}=="*Zephyrus*", GOTO="asusd_start"
|
||||
ENV{DMI_FAMILY}=="*Strix*", GOTO="asusd_start"
|
||||
ENV{DMI_FAMILY}=="*Vivo*ook*", GOTO="asusd_start"
|
||||
# No match so
|
||||
GOTO="asusd_end"
|
||||
|
||||
LABEL="asusd_start"
|
||||
ACTION=="add|change", DRIVER=="asus-nb-wmi", TAG+="systemd", ENV{SYSTEMD_WANTS}="asusd.service"
|
||||
ACTION=="add|remove", DRIVER=="asus-nb-wmi", TAG+="systemd", RUN+="systemctl restart asusd.service"
|
||||
|
||||
LABEL="asusd_end"
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
# 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
|
||||
|
||||
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 74 KiB |
69
deny.toml
Normal file
@@ -0,0 +1,69 @@
|
||||
# https://embarkstudios.github.io/cargo-deny/
|
||||
|
||||
targets = [
|
||||
{ triple = "aarch64-apple-darwin" },
|
||||
{ triple = "aarch64-linux-android" },
|
||||
{ triple = "wasm32-unknown-unknown" },
|
||||
{ triple = "x86_64-apple-darwin" },
|
||||
{ triple = "x86_64-pc-windows-msvc" },
|
||||
{ triple = "x86_64-unknown-linux-gnu" },
|
||||
{ triple = "x86_64-unknown-linux-musl" },
|
||||
]
|
||||
|
||||
[advisories]
|
||||
vulnerability = "deny"
|
||||
unmaintained = "warn"
|
||||
yanked = "deny"
|
||||
ignore = [
|
||||
"RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 - chrono/time: Potential segfault in the time crate
|
||||
"RUSTSEC-2020-0159", # https://rustsec.org/advisories/RUSTSEC-2020-0159 - chrono/time: Potential segfault in localtime_r invocations
|
||||
"RUSTSEC-2021-0127", # https://rustsec.org/advisories/RUSTSEC-2021-0127 - https://github.com/bheisler/criterion.rs/issues/534
|
||||
]
|
||||
|
||||
[bans]
|
||||
multiple-versions = "deny"
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
deny = [
|
||||
{ name = "openssl" }, # prefer rustls
|
||||
{ name = "openssl-sys" }, # prefer rustls
|
||||
]
|
||||
|
||||
skip-tree = [
|
||||
{ name = "criterion" }, # dev-dependency
|
||||
{ name = "glium" }, # legacy crate, lots of old dependencies
|
||||
{ name = "rfd" }, # example dependency
|
||||
{ name = "three-d" }, # example dependency
|
||||
]
|
||||
|
||||
|
||||
[licenses]
|
||||
unlicensed = "deny"
|
||||
allow-osi-fsf-free = "neither"
|
||||
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
|
||||
copyleft = "deny"
|
||||
allow = [
|
||||
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
|
||||
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
|
||||
"ISC", # https://tldrlegal.com/license/-isc-license
|
||||
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
|
||||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11
|
||||
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
|
||||
"OpenSSL", # https://www.openssl.org/source/license.html
|
||||
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
|
||||
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "webpki"
|
||||
expression = "ISC"
|
||||
license-files = [{ path = "LICENSE", hash = 0x001c7e6c }]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "ring"
|
||||
expression = "MIT AND ISC AND OpenSSL"
|
||||
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
|
||||
|
||||
@@ -18,81 +18,112 @@ Then for each trait that is required a new struct is required that can have the
|
||||
|
||||
Main controller:
|
||||
|
||||
For a very simple controller that doesn't need exclusive access you can clone across threads
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlAnime {
|
||||
<things the controller requires>
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
// This is the task trait used for such things as file watches, or logind
|
||||
// notifications (boot/suspend/shutdown etc)
|
||||
impl crate::CtrlTask for CtrlAnime {}
|
||||
|
||||
// The trait to easily add the controller to Zbus to enable the zbus derived functions
|
||||
// to be polled, run, react etc.
|
||||
impl crate::ZbusAdd for CtrlAnime {}
|
||||
|
||||
impl CtrlAnime {}
|
||||
```
|
||||
|
||||
Otherwise, you will need to share the controller via mutex
|
||||
|
||||
```rust
|
||||
pub struct CtrlAnime {
|
||||
<things the controller requires>
|
||||
}
|
||||
// Like this
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlAnimeTask(Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl CtrlAnime {
|
||||
<functions the controller exposes>
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlAnimeZbus(Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl CtrlAnime {}
|
||||
```
|
||||
|
||||
The task trait. There are three ways to implement this:
|
||||
The task trait:
|
||||
|
||||
```rust
|
||||
// Mutex should always be async mutex
|
||||
pub struct CtrlAnimeTask(Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl crate::CtrlTask for CtrlAnimeTask {
|
||||
// This will run once only
|
||||
fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
if let Ok(lock) = self.inner.try_lock() {
|
||||
<some action>
|
||||
}
|
||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let lock self.inner.lock().await;
|
||||
<some action>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This will run until the notification stream closes (which in most cases will be never)
|
||||
fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let connection = Connection::system().await.unwrap();
|
||||
let manager = ManagerProxy::new(&connection).await.unwrap();
|
||||
|
||||
let inner = self.inner.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
// A notification from logind dbus interface
|
||||
if let Ok(p) = manager.receive_prepare_for_sleep().await {
|
||||
// A stream that will continuously output events
|
||||
p.for_each(|_| {
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
// Do stuff here
|
||||
}
|
||||
})
|
||||
.await;
|
||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let inner1 = self.inner.clone();
|
||||
let inner2 = self.inner.clone();
|
||||
let inner3 = self.inner.clone();
|
||||
let inner4 = self.inner.clone();
|
||||
// This is a free method on CtrlTask trait
|
||||
self.create_sys_event_tasks(
|
||||
// Loop is required to try an attempt to get the mutex *without* blocking
|
||||
// other threads - it is possible to end up with deadlocks otherwise.
|
||||
move || loop {
|
||||
if let Some(lock) = inner1.try_lock() {
|
||||
run_action(true, lock, inner1.clone());
|
||||
break;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
// This task will run every 500 milliseconds
|
||||
fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> {
|
||||
let inner = self.inner.clone();
|
||||
// This is a provided free trait to help set up a repeating task
|
||||
self.repeating_task(500, executor, move || {
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
// Do stuff here
|
||||
}
|
||||
})
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner2.try_lock() {
|
||||
run_action(false, lock, inner2.clone());
|
||||
break;
|
||||
}
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner3.try_lock() {
|
||||
run_action(true, lock, inner3.clone());
|
||||
break;
|
||||
}
|
||||
},
|
||||
move || loop {
|
||||
if let Some(lock) = inner4.try_lock() {
|
||||
run_action(false, lock, inner4.clone());
|
||||
break;
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The reloader trait
|
||||
|
||||
```rust
|
||||
pub struct CtrlAnimeReloader(Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl crate::Reloadable for CtrlAnimeReloader {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(lock) = self.inner.try_lock() {
|
||||
<some action>
|
||||
}
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
let lock = self.inner.lock().await;
|
||||
<some action>
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The Zbus requirements:
|
||||
|
||||
```rust
|
||||
pub struct CtrlAnimeZbus(Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
@@ -106,10 +137,9 @@ impl crate::ZbusAdd for CtrlAnimeZbus {
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnimeZbus {
|
||||
fn <zbus method>() {
|
||||
if let Ok(lock) = self.inner.try_lock() {
|
||||
<some action>
|
||||
}
|
||||
async fn <zbus method>() {
|
||||
let lock = self.inner.lock().await;
|
||||
<some action>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
BIN
extra/fan-curves.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
extra/keyboard.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
extra/system.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "rog_anime"
|
||||
version = "1.3.4"
|
||||
license = "MPL-2.0"
|
||||
version.workspace = true
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
repository = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
||||
@@ -9,26 +9,25 @@ 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"
|
||||
edition = "2021"
|
||||
exclude = ["data"]
|
||||
|
||||
[features]
|
||||
default = ["dbus", "detect"]
|
||||
dbus = ["zvariant"]
|
||||
detect = ["udev", "sysfs-class"]
|
||||
dbus = ["zbus"]
|
||||
detect = ["sysfs-class"]
|
||||
|
||||
[dependencies]
|
||||
png_pong = "^0.8.0"
|
||||
pix = "0.13"
|
||||
gif = "^0.11.2"
|
||||
log = "*"
|
||||
png_pong.workspace = true
|
||||
pix.workspace = true
|
||||
gif.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
glam = { version = "0.20.5", features = ["serde"] }
|
||||
glam.workspace = true
|
||||
|
||||
zvariant = { version = "^3.0", optional = true }
|
||||
zbus = { workspace = true, optional = true }
|
||||
|
||||
udev = { version = "^0.6", optional = true }
|
||||
sysfs-class = { version = "^0.1", optional = true }
|
||||
sysfs-class = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -6,9 +7,12 @@ use std::{
|
||||
use log::info;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
#[cfg(feature = "dbus")]
|
||||
use zvariant::Type;
|
||||
use zbus::zvariant::Type;
|
||||
|
||||
use crate::{error::AnimeError, AnimTime, AnimeGif};
|
||||
use crate::{
|
||||
error::{AnimeError, Result},
|
||||
AnimTime, AnimeGif,
|
||||
};
|
||||
|
||||
/// The first 7 bytes of a USB packet are accounted for by `USB_PREFIX1` and `USB_PREFIX2`
|
||||
const BLOCK_START: usize = 7;
|
||||
@@ -25,7 +29,7 @@ const USB_PREFIX2: [u8; 7] = [0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02];
|
||||
const USB_PREFIX3: [u8; 7] = [0x5e, 0xc0, 0x02, 0xe7, 0x04, 0x73, 0x02];
|
||||
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
|
||||
pub struct AnimePowerStates {
|
||||
pub brightness: u8,
|
||||
pub enabled: bool,
|
||||
@@ -43,8 +47,7 @@ impl AnimeType {
|
||||
/// The width of diagonal images
|
||||
pub fn width(&self) -> usize {
|
||||
match self {
|
||||
AnimeType::GA401 => 74,
|
||||
AnimeType::GA402 => 74,
|
||||
AnimeType::GA401 | AnimeType::GA402 => 74,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,23 +102,28 @@ impl AnimeDataBuffer {
|
||||
|
||||
/// Create from a vector of bytes
|
||||
///
|
||||
/// # Panics
|
||||
/// Will panic if the vector length is not `ANIME_DATA_LEN`
|
||||
/// # Errors
|
||||
/// Will error if the vector length is not `ANIME_DATA_LEN`
|
||||
#[inline]
|
||||
pub fn from_vec(anime: AnimeType, data: Vec<u8>) -> Self {
|
||||
assert_eq!(data.len(), anime.data_length());
|
||||
pub fn from_vec(anime: AnimeType, data: Vec<u8>) -> Result<Self> {
|
||||
if data.len() != anime.data_length() {
|
||||
return Err(AnimeError::DataBufferLength);
|
||||
}
|
||||
|
||||
Self { data, anime }
|
||||
Ok(Self { data, anime })
|
||||
}
|
||||
}
|
||||
|
||||
/// The two packets to be written to USB
|
||||
pub type AnimePacketType = Vec<[u8; 640]>;
|
||||
|
||||
impl From<AnimeDataBuffer> for AnimePacketType {
|
||||
#[inline]
|
||||
fn from(anime: AnimeDataBuffer) -> Self {
|
||||
assert_eq!(anime.data.len(), anime.anime.data_length());
|
||||
impl TryFrom<AnimeDataBuffer> for AnimePacketType {
|
||||
type Error = AnimeError;
|
||||
|
||||
fn try_from(anime: AnimeDataBuffer) -> std::result::Result<Self, Self::Error> {
|
||||
if anime.data.len() != anime.anime.data_length() {
|
||||
return Err(AnimeError::DataBufferLength);
|
||||
}
|
||||
|
||||
let mut buffers = match anime.anime {
|
||||
AnimeType::GA401 => vec![[0; 640]; 2],
|
||||
@@ -131,17 +139,14 @@ impl From<AnimeDataBuffer> for AnimePacketType {
|
||||
if matches!(anime.anime, AnimeType::GA402) {
|
||||
buffers[2][..7].copy_from_slice(&USB_PREFIX3);
|
||||
}
|
||||
buffers
|
||||
Ok(buffers)
|
||||
}
|
||||
}
|
||||
|
||||
/// This runs the animations as a blocking loop by using the `callback` to write data
|
||||
///
|
||||
/// If `callback` is `Ok(true)` then `run_animation` will exit the animation loop early.
|
||||
pub fn run_animation(
|
||||
frames: &AnimeGif,
|
||||
callback: &dyn Fn(AnimeDataBuffer) -> Result<bool, AnimeError>,
|
||||
) -> Result<(), AnimeError> {
|
||||
pub fn run_animation(frames: &AnimeGif, callback: &dyn Fn(AnimeDataBuffer) -> Result<bool>) {
|
||||
let mut count = 0;
|
||||
let start = Instant::now();
|
||||
|
||||
@@ -206,9 +211,10 @@ pub fn run_animation(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Log this error
|
||||
if matches!(callback(output), Ok(true)) {
|
||||
info!("rog-anime: frame-loop callback asked to exit early");
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
if timed && Instant::now().duration_since(start) > run_time {
|
||||
@@ -223,5 +229,4 @@ pub fn run_animation(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::{path::Path, time::Duration};
|
||||
|
||||
use crate::{data::AnimeDataBuffer, error::AnimeError, AnimeType};
|
||||
use crate::{
|
||||
data::AnimeDataBuffer,
|
||||
error::{AnimeError, Result},
|
||||
AnimeType,
|
||||
};
|
||||
|
||||
/// Mostly intended to be used with ASUS gifs, but can be used for other purposes (like images)
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -40,7 +44,7 @@ impl AnimeDiagonal {
|
||||
duration: Option<Duration>,
|
||||
bright: f32,
|
||||
anime_type: AnimeType,
|
||||
) -> Result<Self, AnimeError> {
|
||||
) -> Result<Self> {
|
||||
let data = std::fs::read(path)?;
|
||||
let data = std::io::Cursor::new(data);
|
||||
let decoder = png_pong::Decoder::new(data)?.into_steps();
|
||||
@@ -48,39 +52,43 @@ impl AnimeDiagonal {
|
||||
|
||||
let mut matrix = AnimeDiagonal::new(anime_type, duration);
|
||||
|
||||
match raster {
|
||||
match &raster {
|
||||
png_pong::PngRaster::Gray8(ras) => {
|
||||
Self::pixels_from_8bit(ras, &mut matrix, bright, true)
|
||||
Self::pixels_from_8bit(ras, &mut matrix, bright, true);
|
||||
}
|
||||
png_pong::PngRaster::Graya8(ras) => {
|
||||
Self::pixels_from_8bit(ras, &mut matrix, bright, true)
|
||||
Self::pixels_from_8bit(ras, &mut matrix, bright, true);
|
||||
}
|
||||
png_pong::PngRaster::Rgb8(ras) => {
|
||||
Self::pixels_from_8bit(ras, &mut matrix, bright, false)
|
||||
Self::pixels_from_8bit(ras, &mut matrix, bright, false);
|
||||
}
|
||||
png_pong::PngRaster::Rgba8(ras) => {
|
||||
Self::pixels_from_8bit(ras, &mut matrix, bright, false)
|
||||
Self::pixels_from_8bit(ras, &mut matrix, bright, false);
|
||||
}
|
||||
png_pong::PngRaster::Gray16(ras) => {
|
||||
Self::pixels_from_16bit(ras, &mut matrix, bright, true)
|
||||
Self::pixels_from_16bit(ras, &mut matrix, bright, true);
|
||||
}
|
||||
png_pong::PngRaster::Rgb16(ras) => {
|
||||
Self::pixels_from_16bit(ras, &mut matrix, bright, false)
|
||||
Self::pixels_from_16bit(ras, &mut matrix, bright, false);
|
||||
}
|
||||
png_pong::PngRaster::Graya16(ras) => {
|
||||
Self::pixels_from_16bit(ras, &mut matrix, bright, true)
|
||||
Self::pixels_from_16bit(ras, &mut matrix, bright, true);
|
||||
}
|
||||
png_pong::PngRaster::Rgba16(ras) => {
|
||||
Self::pixels_from_16bit(ras, &mut matrix, bright, false)
|
||||
Self::pixels_from_16bit(ras, &mut matrix, bright, false);
|
||||
}
|
||||
_ => return Err(AnimeError::Format),
|
||||
png_pong::PngRaster::Palette(..) => return Err(AnimeError::Format),
|
||||
};
|
||||
|
||||
Ok(matrix)
|
||||
}
|
||||
|
||||
fn pixels_from_8bit<P>(ras: pix::Raster<P>, matrix: &mut AnimeDiagonal, bright: f32, grey: bool)
|
||||
where
|
||||
fn pixels_from_8bit<P>(
|
||||
ras: &pix::Raster<P>,
|
||||
matrix: &mut AnimeDiagonal,
|
||||
bright: f32,
|
||||
grey: bool,
|
||||
) where
|
||||
P: pix::el::Pixel<Chan = pix::chan::Ch8>,
|
||||
{
|
||||
let width = ras.width();
|
||||
@@ -101,7 +109,7 @@ impl AnimeDiagonal {
|
||||
}
|
||||
|
||||
fn pixels_from_16bit<P>(
|
||||
ras: pix::Raster<P>,
|
||||
ras: &pix::Raster<P>,
|
||||
matrix: &mut AnimeDiagonal,
|
||||
bright: f32,
|
||||
grey: bool,
|
||||
@@ -125,16 +133,16 @@ impl AnimeDiagonal {
|
||||
|
||||
/// Convert to a data buffer that can be sent over dbus
|
||||
#[inline]
|
||||
pub fn into_data_buffer(&self, anime_type: AnimeType) -> AnimeDataBuffer {
|
||||
pub fn into_data_buffer(&self, anime_type: AnimeType) -> Result<AnimeDataBuffer> {
|
||||
match anime_type {
|
||||
AnimeType::GA401 => self.into_ga401_packets(),
|
||||
AnimeType::GA402 => self.into_ga402_packets(),
|
||||
AnimeType::GA401 => self.to_ga401_packets(),
|
||||
AnimeType::GA402 => self.to_ga402_packets(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Do conversion from the nested Vec in AnimeMatrix to the two required
|
||||
/// Do conversion from the nested Vec in `AnimeMatrix` to the two required
|
||||
/// packets suitable for sending over USB
|
||||
fn into_ga401_packets(&self) -> AnimeDataBuffer {
|
||||
fn to_ga401_packets(&self) -> Result<AnimeDataBuffer> {
|
||||
let mut buf = vec![0u8; AnimeType::GA401.data_length()];
|
||||
|
||||
buf[1..=32].copy_from_slice(&self.get_row(0, 3, 32));
|
||||
@@ -196,12 +204,12 @@ impl AnimeDiagonal {
|
||||
AnimeDataBuffer::from_vec(crate::AnimeType::GA401, buf)
|
||||
}
|
||||
|
||||
fn into_ga402_packets(&self) -> AnimeDataBuffer {
|
||||
fn to_ga402_packets(&self) -> Result<AnimeDataBuffer> {
|
||||
let mut buf = vec![0u8; AnimeType::GA402.data_length()];
|
||||
let mut start_index: usize = 0;
|
||||
|
||||
fn copy_slice(
|
||||
buf: &mut Vec<u8>,
|
||||
buf: &mut [u8],
|
||||
anime: &AnimeDiagonal,
|
||||
x: usize,
|
||||
y: usize,
|
||||
@@ -282,7 +290,7 @@ impl AnimeDiagonal {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
use std::{convert::TryFrom, path::PathBuf};
|
||||
|
||||
use crate::{AnimeDiagonal, AnimePacketType, AnimeType};
|
||||
|
||||
@@ -374,8 +382,8 @@ mod tests {
|
||||
path.push("test/ga401-diagonal.png");
|
||||
|
||||
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA401).unwrap();
|
||||
let data = matrix.into_data_buffer(crate::AnimeType::GA401);
|
||||
let pkt = AnimePacketType::from(data);
|
||||
let data = matrix.into_data_buffer(crate::AnimeType::GA401).unwrap();
|
||||
let pkt = AnimePacketType::try_from(data).unwrap();
|
||||
|
||||
assert_eq!(pkt[0], pkt0_check);
|
||||
assert_eq!(pkt[1], pkt1_check);
|
||||
@@ -509,8 +517,8 @@ mod tests {
|
||||
path.push("test/ga402-diagonal.png");
|
||||
|
||||
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA402).unwrap();
|
||||
let data = matrix.into_data_buffer(crate::AnimeType::GA402);
|
||||
let pkt = AnimePacketType::from(data);
|
||||
let data = matrix.into_data_buffer(crate::AnimeType::GA402).unwrap();
|
||||
let pkt = AnimePacketType::try_from(data).unwrap();
|
||||
|
||||
assert_eq!(pkt[0], pkt0_check);
|
||||
assert_eq!(pkt[1], pkt1_check);
|
||||
@@ -654,8 +662,8 @@ mod tests {
|
||||
path.push("test/ga402-diagonal-fullbright.png");
|
||||
|
||||
let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::GA402).unwrap();
|
||||
let data = matrix.into_data_buffer(crate::AnimeType::GA402);
|
||||
let pkt = AnimePacketType::from(data);
|
||||
let data = matrix.into_data_buffer(crate::AnimeType::GA402).unwrap();
|
||||
let pkt = AnimePacketType::try_from(data).unwrap();
|
||||
|
||||
assert_eq!(pkt[0], pkt0_check);
|
||||
assert_eq!(pkt[1], pkt1_check);
|
||||
|
||||
@@ -3,6 +3,8 @@ use png_pong::decode::Error as PngError;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AnimeError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AnimeError {
|
||||
NoFrames,
|
||||
@@ -17,11 +19,14 @@ pub enum AnimeError {
|
||||
NoDevice,
|
||||
UnsupportedDevice,
|
||||
InvalidBrightness(f32),
|
||||
DataBufferLength,
|
||||
PixelGifWidth(usize),
|
||||
PixelGifHeight(usize),
|
||||
}
|
||||
|
||||
impl fmt::Display for AnimeError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AnimeError::NoFrames => write!(f, "No frames in PNG"),
|
||||
AnimeError::Io(e) => write!(f, "Could not open: {}", e),
|
||||
@@ -36,12 +41,23 @@ impl fmt::Display for AnimeError {
|
||||
AnimeError::Dbus(detail) => write!(f, "{}", detail),
|
||||
AnimeError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
|
||||
AnimeError::NoDevice => write!(f, "No AniMe Matrix device found"),
|
||||
AnimeError::DataBufferLength => write!(
|
||||
f,
|
||||
"The data buffer was incorrect length for generating USB packets"
|
||||
),
|
||||
AnimeError::UnsupportedDevice => write!(f, "Unsupported AniMe Matrix device found"),
|
||||
AnimeError::InvalidBrightness(bright) => write!(
|
||||
f,
|
||||
"Image brightness must be between 0.0 and 1.0 (inclusive), was {}",
|
||||
bright
|
||||
),
|
||||
AnimeError::PixelGifWidth(n) => {
|
||||
write!(f, "The gif used for pixel-perfect gif is is wider than {n}")
|
||||
}
|
||||
AnimeError::PixelGifHeight(n) => write!(
|
||||
f,
|
||||
"The gif used for pixel-perfect gif is is taller than {n}"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,3 +84,10 @@ impl From<DecodingError> for AnimeError {
|
||||
AnimeError::Gif(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnimeError> for zbus::fdo::Error {
|
||||
#[inline]
|
||||
fn from(err: AnimeError) -> Self {
|
||||
zbus::fdo::Error::Failed(format!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use glam::Vec2;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::{fs::File, path::Path, time::Duration};
|
||||
|
||||
use crate::{error::AnimeError, AnimeDataBuffer, AnimeDiagonal, AnimeImage, AnimeType, Pixel};
|
||||
use crate::error::AnimeError;
|
||||
use crate::{error::Result, AnimeDataBuffer, AnimeDiagonal, AnimeImage, AnimeType, Pixel};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct AnimeFrame {
|
||||
@@ -93,7 +95,7 @@ impl AnimeGif {
|
||||
duration: AnimTime,
|
||||
brightness: f32,
|
||||
anime_type: AnimeType,
|
||||
) -> Result<Self, AnimeError> {
|
||||
) -> Result<Self> {
|
||||
let mut matrix = AnimeDiagonal::new(anime_type, None);
|
||||
|
||||
let mut decoder = gif::DecodeOptions::new();
|
||||
@@ -116,13 +118,22 @@ impl AnimeGif {
|
||||
// should be t but not in some gifs? What, ASUS, what?
|
||||
continue;
|
||||
}
|
||||
matrix.get_mut()[y + frame.top as usize][x + frame.left as usize] =
|
||||
(px[0] as f32 * brightness) as u8;
|
||||
let tmp = matrix.get_mut();
|
||||
let y = y + frame.top as usize;
|
||||
if y >= tmp.len() {
|
||||
return Err(AnimeError::PixelGifHeight(tmp.len()));
|
||||
}
|
||||
let x = x + frame.left as usize;
|
||||
if x >= tmp[y].len() {
|
||||
return Err(AnimeError::PixelGifWidth(tmp[y].len()));
|
||||
}
|
||||
|
||||
matrix.get_mut()[y][x] = (px[0] as f32 * brightness) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
frames.push(AnimeFrame {
|
||||
data: matrix.into_data_buffer(anime_type),
|
||||
data: matrix.into_data_buffer(anime_type)?,
|
||||
delay: Duration::from_millis(wait as u64),
|
||||
});
|
||||
}
|
||||
@@ -136,7 +147,7 @@ impl AnimeGif {
|
||||
anime_type: AnimeType,
|
||||
duration: AnimTime,
|
||||
brightness: f32,
|
||||
) -> Result<Self, AnimeError> {
|
||||
) -> Result<Self> {
|
||||
let image = AnimeDiagonal::from_png(file_name, None, brightness, anime_type)?;
|
||||
|
||||
let mut total = Duration::from_millis(1000);
|
||||
@@ -150,7 +161,7 @@ impl AnimeGif {
|
||||
let frame_count = total.as_millis() / 30;
|
||||
|
||||
let single = AnimeFrame {
|
||||
data: image.into_data_buffer(anime_type),
|
||||
data: image.into_data_buffer(anime_type)?,
|
||||
delay: Duration::from_millis(30),
|
||||
};
|
||||
let frames = vec![single; frame_count as usize];
|
||||
@@ -169,7 +180,7 @@ impl AnimeGif {
|
||||
duration: AnimTime,
|
||||
brightness: f32,
|
||||
anime_type: AnimeType,
|
||||
) -> Result<Self, AnimeError> {
|
||||
) -> Result<Self> {
|
||||
let mut frames = Vec::new();
|
||||
|
||||
let mut decoder = gif::DecodeOptions::new();
|
||||
@@ -225,7 +236,7 @@ impl AnimeGif {
|
||||
image.update();
|
||||
|
||||
frames.push(AnimeFrame {
|
||||
data: <AnimeDataBuffer>::from(&image),
|
||||
data: <AnimeDataBuffer>::try_from(&image)?,
|
||||
delay: Duration::from_millis(wait as u64),
|
||||
});
|
||||
}
|
||||
@@ -244,7 +255,7 @@ impl AnimeGif {
|
||||
duration: AnimTime,
|
||||
brightness: f32,
|
||||
anime_type: AnimeType,
|
||||
) -> Result<Self, AnimeError> {
|
||||
) -> Result<Self> {
|
||||
let image =
|
||||
AnimeImage::from_png(file_name, scale, angle, translation, brightness, anime_type)?;
|
||||
|
||||
@@ -259,7 +270,7 @@ impl AnimeGif {
|
||||
let frame_count = total.as_millis() / 30;
|
||||
|
||||
let single = AnimeFrame {
|
||||
data: <AnimeDataBuffer>::from(&image),
|
||||
data: <AnimeDataBuffer>::try_from(&image)?,
|
||||
delay: Duration::from_millis(30),
|
||||
};
|
||||
let frames = vec![single; frame_count as usize];
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::data::AnimeDataBuffer;
|
||||
use crate::error::{AnimeError, Result};
|
||||
use crate::{AnimeImage, AnimeType};
|
||||
|
||||
// TODO: Adjust these sizes as WIDTH_GA401 WIDTH_GA402
|
||||
@@ -87,11 +90,12 @@ impl AnimeGrid {
|
||||
// }
|
||||
}
|
||||
|
||||
impl From<AnimeGrid> for AnimeDataBuffer {
|
||||
/// Do conversion from the nested Vec in AniMeMatrix to the two required
|
||||
impl TryFrom<AnimeGrid> for AnimeDataBuffer {
|
||||
type Error = AnimeError;
|
||||
|
||||
/// Do conversion from the nested Vec in anime matrix to the two required
|
||||
/// packets suitable for sending over USB
|
||||
#[inline]
|
||||
fn from(anime: AnimeGrid) -> Self {
|
||||
fn try_from(anime: AnimeGrid) -> Result<Self> {
|
||||
let mut buf = vec![0u8; anime.anime_type.data_length()];
|
||||
|
||||
for (idx, pos) in AnimeImage::generate_image_positioning(anime.anime_type)
|
||||
@@ -123,7 +127,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let matrix = <AnimeDataBuffer>::from(matrix);
|
||||
let matrix = <AnimeDataBuffer>::try_from(matrix).unwrap();
|
||||
|
||||
let data_cmp = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use std::path::Path;
|
||||
use std::{convert::TryFrom, path::Path};
|
||||
|
||||
pub use glam::Vec2;
|
||||
use glam::{Mat3, Vec3};
|
||||
|
||||
use crate::{data::AnimeDataBuffer, error::AnimeError, AnimeType};
|
||||
use crate::{
|
||||
data::AnimeDataBuffer,
|
||||
error::{AnimeError, Result},
|
||||
AnimeType,
|
||||
};
|
||||
|
||||
/// A single greyscale + alpha pixel in the image
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -26,7 +30,7 @@ impl Default for Pixel {
|
||||
/// is to be used to sample an image and set the LED brightness.
|
||||
///
|
||||
/// The position of the Led in `LedPositions` determines the placement in the final
|
||||
/// data packets when written to the AniMe.
|
||||
/// data packets when written to the `AniMe`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Led(f32, f32, u8);
|
||||
|
||||
@@ -54,7 +58,7 @@ impl Led {
|
||||
|
||||
/// Container of `Led`, each of which specifies a position within the image
|
||||
/// The main use of this is to position and sample colours for the final image
|
||||
/// to show on AniMe
|
||||
/// to show on `AniMe`
|
||||
pub struct AnimeImage {
|
||||
pub scale: Vec2,
|
||||
/// Angle in radians
|
||||
@@ -83,8 +87,8 @@ impl AnimeImage {
|
||||
pixels: Vec<Pixel>,
|
||||
width: u32,
|
||||
anime_type: AnimeType,
|
||||
) -> Result<Self, AnimeError> {
|
||||
if bright < 0.0 || bright > 1.0 {
|
||||
) -> Result<Self> {
|
||||
if !(0.0..=1.0).contains(&bright) {
|
||||
return Err(AnimeError::InvalidBrightness(bright));
|
||||
}
|
||||
|
||||
@@ -290,8 +294,8 @@ impl AnimeImage {
|
||||
let x0 = led_from_px.mul_vec3(pos + Vec3::new(0.0, -0.5, 0.0));
|
||||
|
||||
const GROUP: [f32; 4] = [0.0, 0.5, 1.0, 1.5];
|
||||
for u in GROUP.iter() {
|
||||
for v in GROUP.iter() {
|
||||
for u in &GROUP {
|
||||
for v in &GROUP {
|
||||
let sample = x0 + *u * du + *v * dv;
|
||||
|
||||
let x = sample.x as i32;
|
||||
@@ -388,14 +392,14 @@ impl AnimeImage {
|
||||
translation: Vec2,
|
||||
bright: f32,
|
||||
anime_type: AnimeType,
|
||||
) -> Result<Self, AnimeError> {
|
||||
) -> Result<Self> {
|
||||
let data = std::fs::read(path)?;
|
||||
let data = std::io::Cursor::new(data);
|
||||
let decoder = png_pong::Decoder::new(data)?.into_steps();
|
||||
let png_pong::Step { raster, delay: _ } = decoder.last().ok_or(AnimeError::NoFrames)??;
|
||||
|
||||
let width;
|
||||
let pixels = match raster {
|
||||
let pixels = match &raster {
|
||||
png_pong::PngRaster::Gray8(ras) => {
|
||||
width = ras.width();
|
||||
Self::pixels_from_8bit(ras, true)
|
||||
@@ -428,7 +432,7 @@ impl AnimeImage {
|
||||
width = ras.width();
|
||||
Self::pixels_from_16bit(ras, false)
|
||||
}
|
||||
_ => return Err(AnimeError::Format),
|
||||
png_pong::PngRaster::Palette(..) => return Err(AnimeError::Format),
|
||||
};
|
||||
|
||||
let mut matrix = AnimeImage::new(
|
||||
@@ -445,7 +449,7 @@ impl AnimeImage {
|
||||
Ok(matrix)
|
||||
}
|
||||
|
||||
fn pixels_from_8bit<P>(ras: pix::Raster<P>, grey: bool) -> Vec<Pixel>
|
||||
fn pixels_from_8bit<P>(ras: &pix::Raster<P>, grey: bool) -> Vec<Pixel>
|
||||
where
|
||||
P: pix::el::Pixel<Chan = pix::chan::Ch8>,
|
||||
{
|
||||
@@ -464,7 +468,7 @@ impl AnimeImage {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn pixels_from_16bit<P>(ras: pix::Raster<P>, grey: bool) -> Vec<Pixel>
|
||||
fn pixels_from_16bit<P>(ras: &pix::Raster<P>, grey: bool) -> Vec<Pixel>
|
||||
where
|
||||
P: pix::el::Pixel<Chan = pix::chan::Ch16>,
|
||||
{
|
||||
@@ -484,11 +488,12 @@ impl AnimeImage {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AnimeImage> for AnimeDataBuffer {
|
||||
/// Do conversion from the nested Vec in AnimeDataBuffer to the two required
|
||||
impl TryFrom<&AnimeImage> for AnimeDataBuffer {
|
||||
type Error = AnimeError;
|
||||
|
||||
/// Do conversion from the nested Vec in `AnimeDataBuffer` to the two required
|
||||
/// packets suitable for sending over USB
|
||||
#[inline]
|
||||
fn from(leds: &AnimeImage) -> Self {
|
||||
fn try_from(leds: &AnimeImage) -> Result<Self> {
|
||||
let mut l: Vec<u8> = leds
|
||||
.led_pos
|
||||
.iter()
|
||||
@@ -506,7 +511,7 @@ impl From<&AnimeImage> for AnimeDataBuffer {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
use std::{convert::TryFrom, path::PathBuf};
|
||||
|
||||
use crate::{image::*, AnimTime, AnimeGif, AnimePacketType};
|
||||
|
||||
@@ -731,10 +736,11 @@ mod tests {
|
||||
vec![Pixel::default(); 1000],
|
||||
100,
|
||||
AnimeType::GA402,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
matrix._edge_outline();
|
||||
let data = AnimeDataBuffer::from(&matrix);
|
||||
let pkt = AnimePacketType::from(data);
|
||||
let data = AnimeDataBuffer::try_from(&matrix).unwrap();
|
||||
let pkt = AnimePacketType::try_from(data).unwrap();
|
||||
|
||||
assert_eq!(pkt[0], pkt0_check);
|
||||
assert_eq!(pkt[1], pkt1_check);
|
||||
@@ -758,6 +764,6 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
matrix.frames()[0].frame();
|
||||
let _pkt = AnimePacketType::from(matrix.frames()[0].frame().clone());
|
||||
let _pkt = AnimePacketType::try_from(matrix.frames()[0].frame().clone()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ pub use data::*;
|
||||
mod grid;
|
||||
pub use grid::*;
|
||||
|
||||
/// Transform a PNG image for displaying on AniMe matrix display
|
||||
/// Transform a PNG image for displaying on `AniMe` matrix display
|
||||
mod image;
|
||||
pub use image::*;
|
||||
|
||||
/// A grid of data that is intended to be read out and displayed on the ANiMe as
|
||||
/// A grid of data that is intended to be read out and displayed on the `AniMe` as
|
||||
/// a diagonal
|
||||
mod diagonal;
|
||||
pub use diagonal::*;
|
||||
@@ -32,4 +32,4 @@ pub mod error;
|
||||
/// Provides const methods to create the USB HID control packets
|
||||
pub mod usb;
|
||||
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use glam::Vec2;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use crate::{
|
||||
error::AnimeError, AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType,
|
||||
error::Result, AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType,
|
||||
};
|
||||
|
||||
/// All the possible AniMe actions that can be used. This enum is intended to be
|
||||
/// All the possible `AniMe` actions that can be used. This enum is intended to be
|
||||
/// a helper for loading up `ActionData`.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub enum ActionLoader {
|
||||
@@ -44,7 +44,7 @@ pub enum ActionLoader {
|
||||
Pause(Duration),
|
||||
}
|
||||
|
||||
/// All the possible AniMe actions that can be used. The enum is intended to be
|
||||
/// All the possible `AniMe` actions that can be used. The enum is intended to be
|
||||
/// used in a array allowing the user to cycle through a series of actions.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub enum ActionData {
|
||||
@@ -65,10 +65,7 @@ pub enum ActionData {
|
||||
}
|
||||
|
||||
impl ActionData {
|
||||
pub fn from_anime_action(
|
||||
anime_type: AnimeType,
|
||||
action: &ActionLoader,
|
||||
) -> Result<ActionData, AnimeError> {
|
||||
pub fn from_anime_action(anime_type: AnimeType, action: &ActionLoader) -> Result<ActionData> {
|
||||
let a = match action {
|
||||
ActionLoader::AsusAnimation {
|
||||
file,
|
||||
@@ -87,7 +84,7 @@ impl ActionData {
|
||||
} => match time {
|
||||
AnimTime::Infinite => {
|
||||
let image = AnimeDiagonal::from_png(file, None, *brightness, anime_type)?;
|
||||
let data = image.into_data_buffer(anime_type);
|
||||
let data = image.into_data_buffer(anime_type)?;
|
||||
ActionData::Image(Box::new(data))
|
||||
}
|
||||
_ => ActionData::Animation(AnimeGif::from_diagonal_png(
|
||||
@@ -147,7 +144,7 @@ impl ActionData {
|
||||
*brightness,
|
||||
anime_type,
|
||||
)?;
|
||||
let data = <AnimeDataBuffer>::from(&image);
|
||||
let data = <AnimeDataBuffer>::try_from(&image)?;
|
||||
ActionData::Image(Box::new(data))
|
||||
}
|
||||
_ => ActionData::Animation(AnimeGif::from_png(
|
||||
@@ -180,7 +177,7 @@ impl Sequences {
|
||||
/// Use a base `AnimeAction` to generate the precomputed data and insert in to
|
||||
/// the run buffer
|
||||
#[inline]
|
||||
pub fn insert(&mut self, index: usize, action: &ActionLoader) -> Result<(), AnimeError> {
|
||||
pub fn insert(&mut self, index: usize, action: &ActionLoader) -> Result<()> {
|
||||
self.0
|
||||
.insert(index, ActionData::from_anime_action(self.1, action)?);
|
||||
Ok(())
|
||||
@@ -197,7 +194,7 @@ impl Sequences {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> ActionIterator {
|
||||
pub fn iter(&self) -> ActionIterator<'_> {
|
||||
ActionIterator {
|
||||
actions: self,
|
||||
next_idx: 0,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Utils for writing to the AniMe USB device
|
||||
//! Utils for writing to the `AniMe` USB device
|
||||
//!
|
||||
//! Use of the device requires a few steps:
|
||||
//! 1. Initialise the device by writing the two packets from `get_init_packets()`
|
||||
@@ -26,7 +26,7 @@ pub fn get_anime_type() -> Result<AnimeType, AnimeError> {
|
||||
let dmi = sysfs_class::DmiId::default();
|
||||
let board_name = dmi.board_name()?;
|
||||
|
||||
if board_name.contains("GA401Q") {
|
||||
if board_name.contains("GA401I") || board_name.contains("GA401Q") {
|
||||
return Ok(AnimeType::GA401);
|
||||
} else if board_name.contains("GA402R") {
|
||||
return Ok(AnimeType::GA402);
|
||||
@@ -34,30 +34,6 @@ pub fn get_anime_type() -> Result<AnimeType, AnimeError> {
|
||||
Err(AnimeError::UnsupportedDevice)
|
||||
}
|
||||
|
||||
/// Find the USB device node - known devices so far: `19b6`
|
||||
#[inline]
|
||||
pub fn find_node(id_product: &str) -> Result<String, AnimeError> {
|
||||
let mut enumerator =
|
||||
udev::Enumerator::new().map_err(|err| AnimeError::Udev("enumerator failed".into(), err))?;
|
||||
enumerator
|
||||
.match_subsystem("usb")
|
||||
.map_err(|err| AnimeError::Udev("match_subsystem failed".into(), err))?;
|
||||
|
||||
for device in enumerator
|
||||
.scan_devices()
|
||||
.map_err(|err| AnimeError::Udev("scan_devices failed".into(), err))?
|
||||
{
|
||||
if let Some(attr) = device.attribute_value("idProduct") {
|
||||
if attr == id_product {
|
||||
if let Some(dev_node) = device.devnode() {
|
||||
return Ok(dev_node.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(AnimeError::NoDevice)
|
||||
}
|
||||
|
||||
/// Get the two device initialization packets. These are required for device start
|
||||
/// after the laptop boots.
|
||||
#[inline]
|
||||
@@ -87,7 +63,7 @@ pub const fn pkt_for_flush() -> [u8; PACKET_SIZE] {
|
||||
}
|
||||
|
||||
/// Get the packet required for setting the device to on, on boot. Requires
|
||||
/// pkt_for_apply()` to be written after.
|
||||
/// `pkt_for_apply()` to be written after.
|
||||
#[inline]
|
||||
pub const fn pkt_for_set_boot(status: bool) -> [u8; PACKET_SIZE] {
|
||||
let mut pkt = [0; PACKET_SIZE];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "rog_aura"
|
||||
version = "1.1.1"
|
||||
license = "MPL-2.0"
|
||||
version.workspace = true
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
repository = "https://gitlab.com/asus-linux/asusctl"
|
||||
@@ -9,15 +9,18 @@ homepage = "https://gitlab.com/asus-linux/asusctl"
|
||||
documentation = "https://docs.rs/rog-anime"
|
||||
description = "Types useful for fancy keyboards on ASUS ROG laptops"
|
||||
keywords = ["ROG", "ASUS", "Aura"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
exclude = ["data"]
|
||||
|
||||
[features]
|
||||
default = ["dbus"]
|
||||
dbus = ["zvariant"]
|
||||
default = ["dbus", "toml"]
|
||||
dbus = ["zbus"]
|
||||
|
||||
[dependencies]
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
toml = { workspace = true, optional = true }
|
||||
zbus = { workspace = true, optional = true }
|
||||
|
||||
zvariant = { version = "^3.0", optional = true }
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
|
||||
161
rog-aura/data/layouts/g513_US.toml
Normal file
@@ -0,0 +1,161 @@
|
||||
matches = [
|
||||
'G513',
|
||||
]
|
||||
|
||||
locale = "US"
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'NormalSpacer',
|
||||
'FuncSpacer',
|
||||
'VolDown',
|
||||
'VolUp',
|
||||
'MicMute',
|
||||
'Fan',
|
||||
'Rog',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'Esc',
|
||||
'FuncSpacer',
|
||||
'F1',
|
||||
'F2',
|
||||
'F3',
|
||||
'F4',
|
||||
'FuncSpacer',
|
||||
'F5',
|
||||
'F6',
|
||||
'F7',
|
||||
'F8',
|
||||
'FuncSpacer',
|
||||
'F9',
|
||||
'F10',
|
||||
'F11',
|
||||
'F12',
|
||||
'RowEndSpacer',
|
||||
'NumPadDel',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tilde',
|
||||
'N1',
|
||||
'N2',
|
||||
'N3',
|
||||
'N4',
|
||||
'N5',
|
||||
'N6',
|
||||
'N7',
|
||||
'N8',
|
||||
'N9',
|
||||
'N0',
|
||||
'Hyphen',
|
||||
'Equals',
|
||||
'BkSpc',
|
||||
'RowEndSpacer',
|
||||
'Home',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tab',
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'LBracket',
|
||||
'RBracket',
|
||||
'BackSlash',
|
||||
'RowEndSpacer',
|
||||
'PgUp',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Caps',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'SemiColon',
|
||||
'Quote',
|
||||
'Return',
|
||||
'RowEndSpacer',
|
||||
'PgDn',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'LShift',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M',
|
||||
'Comma',
|
||||
'Period',
|
||||
'FwdSlash',
|
||||
'Rshift',
|
||||
'RowEndSpacer',
|
||||
'End',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.2
|
||||
row = [
|
||||
'LCtrl',
|
||||
'LFn',
|
||||
'Meta',
|
||||
'LAlt',
|
||||
'Space',
|
||||
'RAlt',
|
||||
'PrtSc',
|
||||
'RCtrl',
|
||||
'ArrowSpacer',
|
||||
'Up',
|
||||
'ArrowSpacer',
|
||||
'RowEndSpacer',
|
||||
'RFn',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'ArrowSpacer',
|
||||
'Left',
|
||||
'Down',
|
||||
'Right',
|
||||
'ArrowSpacer',
|
||||
]
|
||||
135
rog-aura/data/layouts/g533_US.toml
Normal file
@@ -0,0 +1,135 @@
|
||||
matches = [
|
||||
'G533',
|
||||
]
|
||||
|
||||
locale = "US"
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'NormalSpacer',
|
||||
'FuncSpacer',
|
||||
'VolDown',
|
||||
'VolUp',
|
||||
'MicMute',
|
||||
'Fan',
|
||||
'Rog',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'Esc',
|
||||
'FuncSpacer',
|
||||
'F1',
|
||||
'F2',
|
||||
'F3',
|
||||
'F4',
|
||||
'FuncSpacer',
|
||||
'F5',
|
||||
'F6',
|
||||
'F7',
|
||||
'F8',
|
||||
'FuncSpacer',
|
||||
'F9',
|
||||
'F10',
|
||||
'F11',
|
||||
'F12',
|
||||
'Del',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tilde',
|
||||
'N1',
|
||||
'N2',
|
||||
'N3',
|
||||
'N4',
|
||||
'N5',
|
||||
'N6',
|
||||
'N7',
|
||||
'N8',
|
||||
'N9',
|
||||
'N0',
|
||||
'Hyphen',
|
||||
'Equals',
|
||||
'BkSpc',
|
||||
'MediaPlay',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tab',
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'LBracket',
|
||||
'RBracket',
|
||||
'BackSlash',
|
||||
'MediaStop',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Caps',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'SemiColon',
|
||||
'Quote',
|
||||
'Return',
|
||||
'MediaPrev',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'LShift',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M',
|
||||
'Comma',
|
||||
'Period',
|
||||
'FwdSlash',
|
||||
'RshiftSmall',
|
||||
'UpRegular',
|
||||
'MediaNext',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'LCtrlMed',
|
||||
'LFn',
|
||||
'Meta',
|
||||
'LAlt',
|
||||
'Space',
|
||||
'RAlt',
|
||||
'PrtSc',
|
||||
'RCtrl',
|
||||
'ArrowRegularSpacer',
|
||||
'LeftRegular',
|
||||
'DownRegular',
|
||||
'RightRegular',
|
||||
]
|
||||
|
||||
154
rog-aura/data/layouts/ga401_US.toml
Normal file
@@ -0,0 +1,154 @@
|
||||
matches = [
|
||||
'GA401',
|
||||
'GA402',
|
||||
'GU603',
|
||||
'GV301',
|
||||
'GA502',
|
||||
'GA503',
|
||||
]
|
||||
|
||||
locale = "US"
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'NormalSpacer',
|
||||
'FuncSpacer',
|
||||
'VolDown',
|
||||
'VolUp',
|
||||
'MicMute',
|
||||
'Rog',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'Esc',
|
||||
'FuncSpacer',
|
||||
'F1',
|
||||
'F2',
|
||||
'F3',
|
||||
'F4',
|
||||
'FuncSpacer',
|
||||
'F5',
|
||||
'F6',
|
||||
'F7',
|
||||
'F8',
|
||||
'FuncSpacer',
|
||||
'F9',
|
||||
'F10',
|
||||
'F11',
|
||||
'F12',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tilde',
|
||||
'N1',
|
||||
'N2',
|
||||
'N3',
|
||||
'N4',
|
||||
'N5',
|
||||
'N6',
|
||||
'N7',
|
||||
'N8',
|
||||
'N9',
|
||||
'N0',
|
||||
'Hyphen',
|
||||
'Equals',
|
||||
'BkSpc',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tab',
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'LBracket',
|
||||
'RBracket',
|
||||
'BackSlash',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Caps',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'SemiColon',
|
||||
'Quote',
|
||||
'Return',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'LShift',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M',
|
||||
'Comma',
|
||||
'Period',
|
||||
'FwdSlash',
|
||||
'Rshift',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.2
|
||||
row = [
|
||||
'LCtrl',
|
||||
'LFn',
|
||||
'Meta',
|
||||
'LAlt',
|
||||
'Space',
|
||||
'RAlt',
|
||||
'PrtSc',
|
||||
'RCtrl',
|
||||
'ArrowSpacer',
|
||||
'Up',
|
||||
'ArrowSpacer',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'FuncSpacer',
|
||||
'FuncSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'Left',
|
||||
'Down',
|
||||
'Right',
|
||||
'ArrowSpacer',
|
||||
]
|
||||
|
||||
180
rog-aura/data/layouts/gl504_US.toml
Normal file
@@ -0,0 +1,180 @@
|
||||
matches = [
|
||||
'GL504',
|
||||
]
|
||||
|
||||
locale = "US"
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'NormalSpacer',
|
||||
'FuncSpacer',
|
||||
'VolDown',
|
||||
'VolUp',
|
||||
'MicMute',
|
||||
'Rog',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'Esc',
|
||||
'FuncSpacer',
|
||||
'F1',
|
||||
'F2',
|
||||
'F3',
|
||||
'F4',
|
||||
'FuncSpacer',
|
||||
'F5',
|
||||
'F6',
|
||||
'F7',
|
||||
'F8',
|
||||
'FuncSpacer',
|
||||
'F9',
|
||||
'F10',
|
||||
'F11',
|
||||
'F12',
|
||||
'RowEndSpacer',
|
||||
'Del',
|
||||
'NumPadPause',
|
||||
'NumPadPrtSc',
|
||||
'NumPadHome',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tilde',
|
||||
'N1',
|
||||
'N2',
|
||||
'N3',
|
||||
'N4',
|
||||
'N5',
|
||||
'N6',
|
||||
'N7',
|
||||
'N8',
|
||||
'N9',
|
||||
'N0',
|
||||
'Hyphen',
|
||||
'Equals',
|
||||
'BkSpc',
|
||||
'RowEndSpacer',
|
||||
'NumLock',
|
||||
'FwdSlash',
|
||||
'Star',
|
||||
'Hyphen',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tab',
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'LBracket',
|
||||
'RBracket',
|
||||
'BackSlash',
|
||||
'RowEndSpacer',
|
||||
'N7',
|
||||
'N8',
|
||||
'N9',
|
||||
'NumPadPlus',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Caps',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'SemiColon',
|
||||
'Quote',
|
||||
'Return',
|
||||
'RowEndSpacer',
|
||||
'N4',
|
||||
'N5',
|
||||
'N6',
|
||||
'NumPadPlus',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'LShift',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M',
|
||||
'Comma',
|
||||
'Period',
|
||||
'FwdSlash',
|
||||
'Rshift',
|
||||
'RowEndSpacer',
|
||||
'N1',
|
||||
'N2',
|
||||
'N3',
|
||||
'NumPadEnter',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'LCtrl',
|
||||
'LFn',
|
||||
'Meta',
|
||||
'LAlt',
|
||||
'Space',
|
||||
'RAlt',
|
||||
'RFn',
|
||||
'RFn',
|
||||
'RCtrlLarge',
|
||||
'RowEndSpacer',
|
||||
'UpRegular',
|
||||
'N0',
|
||||
'NumPadDel',
|
||||
'NumPadEnter',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'FuncSpacer',
|
||||
'FuncSpacer',
|
||||
'FuncSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'LeftRegular',
|
||||
'RowEndSpacer',
|
||||
'DownRegular',
|
||||
'RightRegular',
|
||||
'NormalSpacer',
|
||||
]
|
||||
|
||||
172
rog-aura/data/layouts/gx502_US.toml
Normal file
@@ -0,0 +1,172 @@
|
||||
matches = [
|
||||
'GX502',
|
||||
'GU502',
|
||||
]
|
||||
|
||||
locale = "US"
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'NormalSpacer',
|
||||
'FuncSpacer',
|
||||
'VolDown',
|
||||
'VolUp',
|
||||
'MicMute',
|
||||
'Rog',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'Esc',
|
||||
'FuncSpacer',
|
||||
'F1',
|
||||
'F2',
|
||||
'F3',
|
||||
'F4',
|
||||
'FuncSpacer',
|
||||
'F5',
|
||||
'F6',
|
||||
'F7',
|
||||
'F8',
|
||||
'FuncSpacer',
|
||||
'F9',
|
||||
'F10',
|
||||
'F11',
|
||||
'F12',
|
||||
'RowEndSpacer',
|
||||
'Del',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tilde',
|
||||
'N1',
|
||||
'N2',
|
||||
'N3',
|
||||
'N4',
|
||||
'N5',
|
||||
'N6',
|
||||
'N7',
|
||||
'N8',
|
||||
'N9',
|
||||
'N0',
|
||||
'Hyphen',
|
||||
'Equals',
|
||||
'BkSpc3_1',
|
||||
'BkSpc3_2',
|
||||
'BkSpc3_3',
|
||||
'RowEndSpacer',
|
||||
'Home',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Tab',
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'LBracket',
|
||||
'RBracket',
|
||||
'BackSlash',
|
||||
'RowEndSpacer',
|
||||
'PgUp',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'Caps',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'SemiColon',
|
||||
'Quote',
|
||||
'Return3_1',
|
||||
'Return3_2',
|
||||
'Return3_3',
|
||||
'RowEndSpacer',
|
||||
'PgDn',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.0
|
||||
row = [
|
||||
'LShift',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M',
|
||||
'Comma',
|
||||
'Period',
|
||||
'FwdSlash',
|
||||
'Rshift3_1',
|
||||
'Rshift3_2',
|
||||
'Rshift3_3',
|
||||
'RowEndSpacer',
|
||||
'End',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 1.2
|
||||
row = [
|
||||
'LCtrl',
|
||||
'LFn',
|
||||
'Meta',
|
||||
'LAlt',
|
||||
'Space5_1',
|
||||
'Space5_2',
|
||||
'Space5_3',
|
||||
'Space5_4',
|
||||
'Space5_5',
|
||||
'RAlt',
|
||||
'PrtSc',
|
||||
'RCtrl',
|
||||
'ArrowSpacer',
|
||||
'Up',
|
||||
'ArrowSpacer',
|
||||
'RowEndSpacer',
|
||||
'RFn',
|
||||
]
|
||||
|
||||
[[rows]]
|
||||
height = 0.8
|
||||
row = [
|
||||
'FuncSpacer',
|
||||
'FuncSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'NormalSpacer',
|
||||
'Left',
|
||||
'Down',
|
||||
'Right',
|
||||
'ArrowSpacer',
|
||||
]
|
||||
|
||||
@@ -5,14 +5,14 @@ pub const LED_INIT4: &str = "^ASUS Tech.Inc."; // ^ == 0x5e
|
||||
pub const LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
#[cfg(feature = "dbus")]
|
||||
use zvariant::Type;
|
||||
use zbus::zvariant::Type;
|
||||
|
||||
use crate::{error::Error, LED_MSG_LEN};
|
||||
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum LedBrightness {
|
||||
Off,
|
||||
Low,
|
||||
@@ -20,19 +20,11 @@ pub enum LedBrightness {
|
||||
High,
|
||||
}
|
||||
|
||||
impl LedBrightness {
|
||||
pub fn as_char_code(&self) -> u8 {
|
||||
std::char::from_digit(*self as u32, 10)
|
||||
.expect("LedBrightness.as_char_code failed to convert") as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for LedBrightness {
|
||||
fn from(bright: u32) -> Self {
|
||||
match bright {
|
||||
0 => LedBrightness::Off,
|
||||
1 => LedBrightness::Low,
|
||||
2 => LedBrightness::Med,
|
||||
3 => LedBrightness::High,
|
||||
_ => LedBrightness::Med,
|
||||
}
|
||||
@@ -40,7 +32,7 @@ impl From<u32> for LedBrightness {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Debug, Clone, PartialEq, Copy, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Copy, Deserialize, Serialize)]
|
||||
pub struct Colour(pub u8, pub u8, pub u8);
|
||||
|
||||
impl Default for Colour {
|
||||
@@ -63,8 +55,36 @@ impl FromStr for Colour {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[f32; 3]> for Colour {
|
||||
fn from(c: &[f32; 3]) -> Self {
|
||||
Self(
|
||||
(255.0 * c[0]) as u8,
|
||||
(255.0 * c[1]) as u8,
|
||||
(255.0 * c[2]) as u8,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Colour> for [f32; 3] {
|
||||
fn from(c: Colour) -> Self {
|
||||
[c.0 as f32 / 255.0, c.1 as f32 / 255.0, c.2 as f32 / 255.0]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8; 3]> for Colour {
|
||||
fn from(c: &[u8; 3]) -> Self {
|
||||
Self(c[0], c[1], c[2])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Colour> for [u8; 3] {
|
||||
fn from(c: Colour) -> Self {
|
||||
[c.0, c.1, c.2]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum Speed {
|
||||
Low = 0xe1,
|
||||
Med = 0xeb,
|
||||
@@ -89,11 +109,20 @@ impl FromStr for Speed {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Speed> for u8 {
|
||||
fn from(s: Speed) -> u8 {
|
||||
match s {
|
||||
Speed::Low => 0,
|
||||
Speed::Med => 1,
|
||||
Speed::High => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Used for Rainbow mode.
|
||||
///
|
||||
/// Enum corresponds to the required integer value
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum Direction {
|
||||
Right,
|
||||
Left,
|
||||
@@ -122,8 +151,11 @@ impl FromStr for Direction {
|
||||
|
||||
/// Enum of modes that convert to the actual number required by a USB HID packet
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Deserialize, Serialize)]
|
||||
#[derive(
|
||||
Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Deserialize, Serialize,
|
||||
)]
|
||||
pub enum AuraModeNum {
|
||||
#[default]
|
||||
Static = 0,
|
||||
Breathe = 1,
|
||||
Strobe = 2,
|
||||
@@ -138,23 +170,15 @@ pub enum AuraModeNum {
|
||||
Flash = 12,
|
||||
}
|
||||
|
||||
impl Display for AuraModeNum {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", <&str>::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuraModeNum> for String {
|
||||
fn from(mode: AuraModeNum) -> Self {
|
||||
match mode {
|
||||
AuraModeNum::Static => "Static",
|
||||
AuraModeNum::Breathe => "Breathe",
|
||||
AuraModeNum::Strobe => "Strobe",
|
||||
AuraModeNum::Rainbow => "Rainbow",
|
||||
AuraModeNum::Star => "Stars",
|
||||
AuraModeNum::Rain => "Rain",
|
||||
AuraModeNum::Highlight => "Highlight",
|
||||
AuraModeNum::Laser => "Laser",
|
||||
AuraModeNum::Ripple => "Ripple",
|
||||
AuraModeNum::Pulse => "Pulse",
|
||||
AuraModeNum::Comet => "Comet",
|
||||
AuraModeNum::Flash => "Flash",
|
||||
}
|
||||
.to_string()
|
||||
<&str>::from(&mode).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +203,6 @@ impl From<&AuraModeNum> for &str {
|
||||
impl From<&str> for AuraModeNum {
|
||||
fn from(mode: &str) -> Self {
|
||||
match mode {
|
||||
"Static" => AuraModeNum::Static,
|
||||
"Breathe" => AuraModeNum::Breathe,
|
||||
"Strobe" => AuraModeNum::Strobe,
|
||||
"Rainbow" => AuraModeNum::Rainbow,
|
||||
@@ -199,7 +222,6 @@ impl From<&str> for AuraModeNum {
|
||||
impl From<u8> for AuraModeNum {
|
||||
fn from(mode: u8) -> Self {
|
||||
match mode {
|
||||
0 => AuraModeNum::Static,
|
||||
1 => AuraModeNum::Breathe,
|
||||
2 => AuraModeNum::Strobe,
|
||||
3 => AuraModeNum::Rainbow,
|
||||
@@ -218,9 +240,10 @@ impl From<u8> for AuraModeNum {
|
||||
|
||||
/// Base effects have no zoning, while multizone is 1-4
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum AuraZone {
|
||||
/// Used if keyboard has no zones, or if setting all
|
||||
#[default]
|
||||
None,
|
||||
/// Leftmost zone
|
||||
Key1,
|
||||
@@ -238,34 +261,20 @@ pub enum AuraZone {
|
||||
BarRight,
|
||||
}
|
||||
|
||||
impl Default for AuraZone {
|
||||
fn default() -> Self {
|
||||
AuraZone::None
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AuraZone {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"0" => Ok(AuraZone::None),
|
||||
"none" => Ok(AuraZone::None),
|
||||
"1" => Ok(AuraZone::Key1),
|
||||
"one" => Ok(AuraZone::Key1),
|
||||
"2" => Ok(AuraZone::Key2),
|
||||
"two" => Ok(AuraZone::Key2),
|
||||
"3" => Ok(AuraZone::Key3),
|
||||
"three" => Ok(AuraZone::Key3),
|
||||
"4" => Ok(AuraZone::Key4),
|
||||
"four" => Ok(AuraZone::Key4),
|
||||
"5" => Ok(AuraZone::Logo),
|
||||
"logo" => Ok(AuraZone::Logo),
|
||||
"6" => Ok(AuraZone::BarLeft),
|
||||
"lightbar-left" => Ok(AuraZone::BarLeft),
|
||||
"7" => Ok(AuraZone::BarRight),
|
||||
"lightbar-right" => Ok(AuraZone::BarRight),
|
||||
"0" | "none" => Ok(AuraZone::None),
|
||||
"1" | "one" => Ok(AuraZone::Key1),
|
||||
"2" | "two" => Ok(AuraZone::Key2),
|
||||
"3" | "three" => Ok(AuraZone::Key3),
|
||||
"4" | "four" => Ok(AuraZone::Key4),
|
||||
"5" | "logo" => Ok(AuraZone::Logo),
|
||||
"6" | "lightbar-left" => Ok(AuraZone::BarLeft),
|
||||
"7" | "lightbar-right" => Ok(AuraZone::BarRight),
|
||||
_ => Err(Error::ParseSpeed),
|
||||
}
|
||||
}
|
||||
@@ -297,8 +306,8 @@ impl AuraEffect {
|
||||
&self.mode
|
||||
}
|
||||
|
||||
pub fn mode_name(&self) -> String {
|
||||
(<&str>::from(&self.mode)).to_string()
|
||||
pub fn mode_name(&self) -> &str {
|
||||
<&str>::from(&self.mode)
|
||||
}
|
||||
|
||||
pub fn mode_num(&self) -> u8 {
|
||||
@@ -330,6 +339,55 @@ impl Default for AuraEffect {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuraParameters {
|
||||
pub zone: bool,
|
||||
pub colour1: bool,
|
||||
pub colour2: bool,
|
||||
pub speed: bool,
|
||||
pub direction: bool,
|
||||
}
|
||||
|
||||
impl AuraParameters {
|
||||
pub const fn new(
|
||||
zone: bool,
|
||||
colour1: bool,
|
||||
colour2: bool,
|
||||
speed: bool,
|
||||
direction: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
zone,
|
||||
colour1,
|
||||
colour2,
|
||||
speed,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuraEffect {
|
||||
/// A helper to provide detail on what effects have which parameters, e.g the static
|
||||
/// factory mode accepts only one colour.
|
||||
pub const fn allowed_parameters(mode: AuraModeNum) -> AuraParameters {
|
||||
match mode {
|
||||
AuraModeNum::Static
|
||||
| AuraModeNum::Highlight
|
||||
| AuraModeNum::Pulse
|
||||
| AuraModeNum::Comet
|
||||
| AuraModeNum::Flash => AuraParameters::new(true, true, false, false, false),
|
||||
AuraModeNum::Breathe => AuraParameters::new(true, true, true, true, false),
|
||||
AuraModeNum::Strobe | AuraModeNum::Rain => {
|
||||
AuraParameters::new(true, false, false, true, false)
|
||||
}
|
||||
AuraModeNum::Rainbow => AuraParameters::new(true, false, false, true, true),
|
||||
AuraModeNum::Star => AuraParameters::new(true, true, true, true, true),
|
||||
AuraModeNum::Laser | AuraModeNum::Ripple => {
|
||||
AuraParameters::new(true, true, false, true, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses `AuraEffect` in to packet data for writing to the USB interface
|
||||
///
|
||||
/// Byte structure where colour is RGB, one byte per R, G, B:
|
||||
@@ -357,6 +415,25 @@ impl From<&AuraEffect> for [u8; LED_MSG_LEN] {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuraEffect> for Vec<u8> {
|
||||
fn from(aura: &AuraEffect) -> Self {
|
||||
let mut msg = vec![0u8; LED_MSG_LEN];
|
||||
msg[0] = 0x5d;
|
||||
msg[1] = 0xb3;
|
||||
msg[2] = aura.zone as u8;
|
||||
msg[3] = aura.mode as u8;
|
||||
msg[4] = aura.colour1.0;
|
||||
msg[5] = aura.colour1.1;
|
||||
msg[6] = aura.colour1.2;
|
||||
msg[7] = aura.speed as u8;
|
||||
msg[8] = aura.direction as u8;
|
||||
msg[10] = aura.colour2.0;
|
||||
msg[11] = aura.colour2.1;
|
||||
msg[12] = aura.colour2.2;
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed, LED_MSG_LEN};
|
||||
|
||||
@@ -7,20 +7,34 @@ pub enum Error {
|
||||
ParseSpeed,
|
||||
ParseDirection,
|
||||
ParseBrightness,
|
||||
ParseAnime,
|
||||
Io(std::io::Error),
|
||||
Toml(toml::de::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::ParseColour => write!(f, "Could not parse colour"),
|
||||
Error::ParseSpeed => write!(f, "Could not parse speed"),
|
||||
Error::ParseDirection => write!(f, "Could not parse direction"),
|
||||
Error::ParseBrightness => write!(f, "Could not parse brightness"),
|
||||
Error::ParseAnime => write!(f, "Could not parse anime"),
|
||||
Error::Io(io) => write!(f, "IO Error: {io}"),
|
||||
Error::Toml(e) => write!(f, "TOML Parse Error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Self::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for Error {
|
||||
fn from(e: toml::de::Error) -> Self {
|
||||
Self::Toml(e)
|
||||
}
|
||||
}
|
||||
|
||||
155
rog-aura/src/key_to_str.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
use crate::keys::Key;
|
||||
|
||||
impl From<Key> for &str {
|
||||
fn from(k: Key) -> Self {
|
||||
(&k).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Key> for &str {
|
||||
fn from(k: &Key) -> Self {
|
||||
match k {
|
||||
Key::VolUp => "Volume Up",
|
||||
Key::VolDown => "Volume Down",
|
||||
Key::MicMute => "Mute Mic",
|
||||
Key::Rog => "ROG",
|
||||
Key::Fan => "Fan Control",
|
||||
Key::Esc => "Escape",
|
||||
Key::F1 => "F1",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F6 => "F6",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
Key::F9 => "F9",
|
||||
Key::F10 => "F10",
|
||||
Key::F11 => "F11",
|
||||
Key::F12 => "F12",
|
||||
Key::Del => "Delete",
|
||||
Key::Tilde => "Tilde",
|
||||
Key::N1 => "1",
|
||||
Key::N2 => "2",
|
||||
Key::N3 => "3",
|
||||
Key::N4 => "4",
|
||||
Key::N5 => "5",
|
||||
Key::N6 => "6",
|
||||
Key::N7 => "7",
|
||||
Key::N8 => "8",
|
||||
Key::N9 => "9",
|
||||
Key::N0 => "0",
|
||||
Key::Hyphen => "-",
|
||||
Key::Equals => "=",
|
||||
Key::BkSpc => "Backspace",
|
||||
Key::BkSpc3_1 => "Backspace LED 1",
|
||||
Key::BkSpc3_2 => "Backspace LED 2",
|
||||
Key::BkSpc3_3 => "Backspace LED 3",
|
||||
Key::Home => "Home",
|
||||
Key::Tab => "Tab",
|
||||
Key::Q => "Q",
|
||||
Key::W => "W",
|
||||
Key::E => "E",
|
||||
Key::R => "R",
|
||||
Key::T => "T",
|
||||
Key::Y => "Y",
|
||||
Key::U => "U",
|
||||
Key::I => "I",
|
||||
Key::O => "O",
|
||||
Key::P => "P",
|
||||
Key::LBracket => "[",
|
||||
Key::RBracket => "]",
|
||||
Key::BackSlash => "\\",
|
||||
Key::PgUp => "Page Up",
|
||||
Key::Caps => "Caps Lock",
|
||||
Key::A => "A",
|
||||
Key::S => "S",
|
||||
Key::D => "D",
|
||||
Key::F => "F",
|
||||
Key::G => "G",
|
||||
Key::H => "H",
|
||||
Key::J => "J",
|
||||
Key::K => "K",
|
||||
Key::L => "L",
|
||||
Key::SemiColon => ";",
|
||||
Key::Quote => "'",
|
||||
Key::Return => "Return",
|
||||
Key::Return3_1 => "Return LED 1",
|
||||
Key::Return3_2 => "Return LED 2",
|
||||
Key::Return3_3 => "Return LED 3",
|
||||
Key::PgDn => "Page Down",
|
||||
Key::LShift => "Left Shift",
|
||||
Key::LShift3_1 => "Left Shift LED 1",
|
||||
Key::LShift3_2 => "Left Shift LED 2",
|
||||
Key::LShift3_3 => "Left Shift LED 3",
|
||||
Key::Z => "Z",
|
||||
Key::X => "X",
|
||||
Key::C => "C",
|
||||
Key::V => "V",
|
||||
Key::B => "B",
|
||||
Key::N => "N",
|
||||
Key::M => "M",
|
||||
Key::Comma => ",",
|
||||
Key::Period => ".",
|
||||
Key::Star => "*",
|
||||
Key::NumPadDel => "Delete",
|
||||
Key::NumPadPlus => "+",
|
||||
Key::NumPadEnter => "Enter",
|
||||
Key::NumPadPause => "Pause",
|
||||
Key::NumPadPrtSc => "Print Screen",
|
||||
Key::NumPadHome => "Home",
|
||||
Key::NumLock => "Num-Lock",
|
||||
Key::FwdSlash => "/",
|
||||
Key::Rshift => "Right Shift",
|
||||
Key::RshiftSmall => "Right Shift",
|
||||
Key::Rshift3_1 => "Right Shift LED 1",
|
||||
Key::Rshift3_2 => "Right Shift LED 2",
|
||||
Key::Rshift3_3 => "Right Shift LED 3",
|
||||
Key::End => "End",
|
||||
Key::LCtrl => "Left Control",
|
||||
Key::LCtrlMed => "Left Control",
|
||||
Key::LFn => "Left Fn",
|
||||
Key::Meta => "Meta",
|
||||
Key::LAlt => "Left Alt",
|
||||
Key::Space => "Space",
|
||||
Key::Space5_1 => "Space LED 1",
|
||||
Key::Space5_2 => "Space LED 2",
|
||||
Key::Space5_3 => "Space LED 3",
|
||||
Key::Space5_4 => "Space LED 4",
|
||||
Key::Space5_5 => "Space LED 5",
|
||||
Key::RAlt => "Right Alt",
|
||||
Key::PrtSc => "Print Screen",
|
||||
Key::RCtrl => "Right Control",
|
||||
Key::RCtrlLarge => "Right Control",
|
||||
Key::Pause => "Pause",
|
||||
Key::Up => "Up",
|
||||
Key::Down => "Down",
|
||||
Key::Left => "Left",
|
||||
Key::Right => "Right",
|
||||
Key::UpRegular => "Up",
|
||||
Key::DownRegular => "Down",
|
||||
Key::LeftRegular => "Left",
|
||||
Key::RightRegular => "Right",
|
||||
Key::UpSplit => "Up",
|
||||
Key::DownSplit => "Down",
|
||||
Key::LeftSplit => "Left",
|
||||
Key::RightSplit => "Right",
|
||||
Key::RFn => "Right Fn",
|
||||
Key::MediaPlay => "Media Play",
|
||||
Key::MediaStop => "Media Stop",
|
||||
Key::MediaNext => "Media Next",
|
||||
Key::MediaPrev => "Media Previous",
|
||||
Key::NormalBlank
|
||||
| Key::NormalSpacer
|
||||
| Key::FuncBlank
|
||||
| Key::FuncSpacer
|
||||
| Key::ArrowBlank
|
||||
| Key::ArrowSpacer
|
||||
| Key::ArrowRegularBlank
|
||||
| Key::ArrowRegularSpacer
|
||||
| Key::ArrowSplitBlank
|
||||
| Key::ArrowSplitSpacer
|
||||
| Key::RowEndSpacer => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
358
rog-aura/src/keys.rs
Normal file
@@ -0,0 +1,358 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum Key {
|
||||
VolUp,
|
||||
VolDown,
|
||||
MicMute,
|
||||
#[default]
|
||||
Rog,
|
||||
Fan,
|
||||
Esc,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
Del,
|
||||
Tilde,
|
||||
N1,
|
||||
N2,
|
||||
N3,
|
||||
N4,
|
||||
N5,
|
||||
N6,
|
||||
N7,
|
||||
N8,
|
||||
N9,
|
||||
N0,
|
||||
Hyphen,
|
||||
Equals,
|
||||
BkSpc,
|
||||
BkSpc3_1,
|
||||
BkSpc3_2,
|
||||
BkSpc3_3,
|
||||
Home,
|
||||
Tab,
|
||||
Q,
|
||||
W,
|
||||
E,
|
||||
R,
|
||||
T,
|
||||
Y,
|
||||
U,
|
||||
I,
|
||||
O,
|
||||
P,
|
||||
LBracket,
|
||||
RBracket,
|
||||
BackSlash,
|
||||
PgUp,
|
||||
Caps,
|
||||
A,
|
||||
S,
|
||||
D,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
SemiColon,
|
||||
Quote,
|
||||
Return,
|
||||
Return3_1,
|
||||
Return3_2,
|
||||
Return3_3,
|
||||
PgDn,
|
||||
LShift,
|
||||
LShift3_1,
|
||||
LShift3_2,
|
||||
LShift3_3,
|
||||
Z,
|
||||
X,
|
||||
C,
|
||||
V,
|
||||
B,
|
||||
N,
|
||||
M,
|
||||
Comma,
|
||||
Period,
|
||||
FwdSlash,
|
||||
Star,
|
||||
NumPadDel,
|
||||
NumPadPlus,
|
||||
NumPadEnter,
|
||||
NumPadPause,
|
||||
NumPadPrtSc,
|
||||
NumPadHome,
|
||||
NumLock,
|
||||
Rshift,
|
||||
RshiftSmall,
|
||||
Rshift3_1,
|
||||
Rshift3_2,
|
||||
Rshift3_3,
|
||||
End,
|
||||
LCtrl,
|
||||
LCtrlMed,
|
||||
LFn,
|
||||
Meta,
|
||||
LAlt,
|
||||
Space,
|
||||
Space5_1,
|
||||
Space5_2,
|
||||
Space5_3,
|
||||
Space5_4,
|
||||
Space5_5,
|
||||
Pause,
|
||||
RAlt,
|
||||
PrtSc,
|
||||
RCtrl,
|
||||
RCtrlLarge,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
UpRegular,
|
||||
DownRegular,
|
||||
LeftRegular,
|
||||
RightRegular,
|
||||
UpSplit,
|
||||
DownSplit,
|
||||
LeftSplit,
|
||||
RightSplit,
|
||||
RFn,
|
||||
MediaPlay,
|
||||
MediaStop,
|
||||
MediaNext,
|
||||
MediaPrev,
|
||||
NormalBlank,
|
||||
/// To be ignored by per-key effects
|
||||
NormalSpacer,
|
||||
FuncBlank,
|
||||
/// To be ignored by per-key effects
|
||||
FuncSpacer,
|
||||
ArrowBlank,
|
||||
/// To be ignored by per-key effects
|
||||
ArrowSpacer,
|
||||
ArrowRegularBlank,
|
||||
/// To be ignored by per-key effects
|
||||
ArrowRegularSpacer,
|
||||
ArrowSplitBlank,
|
||||
/// To be ignored by per-key effects
|
||||
ArrowSplitSpacer,
|
||||
/// A gap between regular rows and the rightside buttons
|
||||
RowEndSpacer,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn is_placeholder(&self) -> bool {
|
||||
let shape = KeyShape::from(self);
|
||||
shape.is_blank() || shape.is_spacer()
|
||||
}
|
||||
}
|
||||
|
||||
/// Types of shapes of LED on keyboards. The shape is used for visual representations
|
||||
///
|
||||
/// A post fix of Spacer *must be ignored by per-key effects
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize)]
|
||||
pub enum KeyShape {
|
||||
Tilde,
|
||||
#[default]
|
||||
Normal,
|
||||
NormalBlank,
|
||||
NormalSpacer,
|
||||
Func,
|
||||
FuncBlank,
|
||||
FuncSpacer,
|
||||
Space,
|
||||
Space5,
|
||||
LCtrlMed,
|
||||
LShift,
|
||||
/// Used in a group of 3 (LED's)
|
||||
LShift3,
|
||||
RShift,
|
||||
RshiftSmall,
|
||||
/// Used in a group of 3 (LED's)
|
||||
RShift3,
|
||||
Return,
|
||||
Return3,
|
||||
Tab,
|
||||
Caps,
|
||||
Backspace,
|
||||
/// Used in a group of 3 (LED's)
|
||||
Backspace3,
|
||||
Arrow,
|
||||
ArrowBlank,
|
||||
ArrowSpacer,
|
||||
ArrowSplit,
|
||||
ArrowSplitBlank,
|
||||
ArrowSplitSpacer,
|
||||
ArrowRegularBlank,
|
||||
ArrowRegularSpacer,
|
||||
RowEndSpacer,
|
||||
}
|
||||
|
||||
impl KeyShape {
|
||||
pub const fn width(&self) -> f32 {
|
||||
match self {
|
||||
Self::Tilde | Self::Arrow => 0.8,
|
||||
Self::Normal
|
||||
| Self::NormalBlank
|
||||
| Self::NormalSpacer
|
||||
| Self::Func
|
||||
| Self::FuncBlank
|
||||
| Self::Space5
|
||||
| Self::ArrowBlank
|
||||
| Self::ArrowSpacer
|
||||
| Self::ArrowSplit
|
||||
| Self::ArrowSplitBlank
|
||||
| Self::ArrowSplitSpacer => 1.0,
|
||||
Self::FuncSpacer => 0.6,
|
||||
Self::Space => 5.0,
|
||||
Self::LCtrlMed => 1.1,
|
||||
Self::LShift | Self::Backspace => 2.0,
|
||||
Self::LShift3 => 0.67,
|
||||
Self::RShift => 2.8,
|
||||
Self::RshiftSmall => 1.8,
|
||||
Self::RShift3 => 0.93,
|
||||
Self::Return => 2.2,
|
||||
Self::Return3 => 0.7333,
|
||||
Self::Tab => 1.4,
|
||||
Self::Caps => 1.6,
|
||||
Self::Backspace3 => 0.666,
|
||||
Self::ArrowRegularBlank | Self::ArrowRegularSpacer => 0.7,
|
||||
|
||||
Self::RowEndSpacer => 0.1,
|
||||
}
|
||||
}
|
||||
|
||||
/// A blank is used to space keys out in GUI's and can be used or ignored
|
||||
/// depednign on the per-key effect
|
||||
pub const fn is_blank(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::NormalBlank
|
||||
| Self::FuncBlank
|
||||
| Self::ArrowBlank
|
||||
| Self::ArrowSplitBlank
|
||||
| Self::ArrowRegularBlank
|
||||
)
|
||||
}
|
||||
|
||||
/// A spacer is used to space keys out in GUI's, but ignored in per-key effects
|
||||
pub const fn is_spacer(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::FuncSpacer
|
||||
| Self::NormalSpacer
|
||||
| Self::ArrowSpacer
|
||||
| Self::ArrowSplitSpacer
|
||||
| Self::ArrowRegularSpacer
|
||||
)
|
||||
}
|
||||
|
||||
/// All keys with a postfix of some number
|
||||
pub const fn is_group(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::LShift3 | Self::RShift3 | Self::Return3 | Self::Space5 | Self::Backspace3
|
||||
)
|
||||
}
|
||||
|
||||
/// Mostly intended as a helper for signalling when to draw a
|
||||
/// split/compact arrow cluster
|
||||
pub const fn is_arrow_cluster(&self) -> bool {
|
||||
matches!(self, Self::Arrow | Self::ArrowBlank | Self::ArrowSpacer)
|
||||
}
|
||||
|
||||
pub const fn is_arrow_splits(&self) -> bool {
|
||||
matches!(self, Self::Arrow | Self::ArrowBlank | Self::ArrowSpacer)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Key> for KeyShape {
|
||||
fn from(k: Key) -> Self {
|
||||
match k {
|
||||
Key::VolUp
|
||||
| Key::VolDown
|
||||
| Key::MicMute
|
||||
| Key::Rog
|
||||
| Key::Fan
|
||||
| Key::Esc
|
||||
| Key::F1
|
||||
| Key::F2
|
||||
| Key::F3
|
||||
| Key::F4
|
||||
| Key::F5
|
||||
| Key::F6
|
||||
| Key::F7
|
||||
| Key::F8
|
||||
| Key::F9
|
||||
| Key::F10
|
||||
| Key::F11
|
||||
| Key::F12
|
||||
| Key::Del => KeyShape::Func,
|
||||
Key::Tilde => KeyShape::Tilde,
|
||||
|
||||
Key::BkSpc => KeyShape::Backspace,
|
||||
Key::BkSpc3_1 | Key::BkSpc3_2 | Key::BkSpc3_3 => KeyShape::Backspace3,
|
||||
Key::Tab | Key::BackSlash => KeyShape::Tab,
|
||||
Key::Caps => KeyShape::Caps,
|
||||
|
||||
Key::Return => KeyShape::Return,
|
||||
Key::Return3_1 | Key::Return3_2 | Key::Return3_3 => KeyShape::Return3,
|
||||
Key::LCtrlMed => KeyShape::LCtrlMed,
|
||||
Key::LShift => KeyShape::LShift,
|
||||
|
||||
Key::Rshift | Key::RCtrlLarge => KeyShape::RShift,
|
||||
Key::RshiftSmall => KeyShape::RshiftSmall,
|
||||
Key::Rshift3_1 | Key::Rshift3_2 | Key::Rshift3_3 => KeyShape::RShift3,
|
||||
|
||||
Key::Space => KeyShape::Space,
|
||||
Key::Space5_1 | Key::Space5_2 | Key::Space5_3 | Key::Space5_4 | Key::Space5_5 => {
|
||||
KeyShape::Space5
|
||||
}
|
||||
|
||||
Key::NumPadPause | Key::NumPadPrtSc | Key::NumPadHome | Key::NumPadDel => {
|
||||
KeyShape::Func
|
||||
}
|
||||
|
||||
Key::NormalBlank => KeyShape::NormalBlank,
|
||||
Key::NormalSpacer => KeyShape::NormalSpacer,
|
||||
|
||||
Key::FuncBlank => KeyShape::FuncBlank,
|
||||
Key::FuncSpacer => KeyShape::FuncSpacer,
|
||||
|
||||
Key::Up | Key::Down | Key::Left | Key::Right => KeyShape::Arrow,
|
||||
Key::ArrowBlank => KeyShape::ArrowBlank,
|
||||
Key::ArrowSpacer => KeyShape::ArrowSpacer,
|
||||
|
||||
Key::ArrowRegularBlank => KeyShape::ArrowRegularBlank,
|
||||
Key::ArrowRegularSpacer => KeyShape::ArrowRegularSpacer,
|
||||
|
||||
Key::UpSplit | Key::LeftSplit | Key::DownSplit | Key::RightSplit => {
|
||||
KeyShape::ArrowSplit
|
||||
}
|
||||
Key::ArrowSplitBlank => KeyShape::ArrowSplitBlank,
|
||||
Key::ArrowSplitSpacer => KeyShape::ArrowSplitSpacer,
|
||||
|
||||
Key::RowEndSpacer => KeyShape::RowEndSpacer,
|
||||
|
||||
_ => KeyShape::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Key> for KeyShape {
|
||||
fn from(k: &Key) -> Self {
|
||||
(*k).into()
|
||||
}
|
||||
}
|
||||
170
rog-aura/src/layouts/g513.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use super::{KeyLayout, KeyRow};
|
||||
use crate::keys::Key;
|
||||
|
||||
impl KeyLayout {
|
||||
/// Similar to GX502, but not per-key enabled
|
||||
pub fn g513_layout() -> Self {
|
||||
Self {
|
||||
matches: vec!["G513".into()],
|
||||
locale: "US".to_owned(),
|
||||
rows: vec![
|
||||
KeyRow::new(
|
||||
0.8,
|
||||
vec![
|
||||
Key::NormalSpacer,
|
||||
Key::FuncSpacer,
|
||||
Key::VolDown,
|
||||
Key::VolUp,
|
||||
Key::MicMute,
|
||||
Key::Fan,
|
||||
Key::Rog,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
0.8,
|
||||
vec![
|
||||
Key::Esc,
|
||||
Key::FuncSpacer,
|
||||
Key::F1,
|
||||
Key::F2,
|
||||
Key::F3,
|
||||
Key::F4,
|
||||
Key::FuncSpacer, // not sure which key to put here
|
||||
Key::F5,
|
||||
Key::F6,
|
||||
Key::F7,
|
||||
Key::F8,
|
||||
Key::FuncSpacer,
|
||||
Key::F9,
|
||||
Key::F10,
|
||||
Key::F11,
|
||||
Key::F12,
|
||||
Key::RowEndSpacer,
|
||||
Key::Del,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Tilde,
|
||||
Key::N1,
|
||||
Key::N2,
|
||||
Key::N3,
|
||||
Key::N4,
|
||||
Key::N5,
|
||||
Key::N6,
|
||||
Key::N7,
|
||||
Key::N8,
|
||||
Key::N9,
|
||||
Key::N0,
|
||||
Key::Hyphen,
|
||||
Key::Equals,
|
||||
Key::BkSpc,
|
||||
Key::RowEndSpacer,
|
||||
Key::Home,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Tab,
|
||||
Key::Q,
|
||||
Key::W,
|
||||
Key::E,
|
||||
Key::R,
|
||||
Key::T,
|
||||
Key::Y,
|
||||
Key::U,
|
||||
Key::I,
|
||||
Key::O,
|
||||
Key::P,
|
||||
Key::LBracket,
|
||||
Key::RBracket,
|
||||
Key::BackSlash,
|
||||
Key::RowEndSpacer,
|
||||
Key::PgUp,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Caps,
|
||||
Key::A,
|
||||
Key::S,
|
||||
Key::D,
|
||||
Key::F,
|
||||
Key::G,
|
||||
Key::H,
|
||||
Key::J,
|
||||
Key::K,
|
||||
Key::L,
|
||||
Key::SemiColon,
|
||||
Key::Quote,
|
||||
Key::Return,
|
||||
Key::RowEndSpacer,
|
||||
Key::PgDn,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::LShift,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::C,
|
||||
Key::V,
|
||||
Key::B,
|
||||
Key::N,
|
||||
Key::M,
|
||||
Key::Comma,
|
||||
Key::Period,
|
||||
Key::FwdSlash,
|
||||
Key::Rshift,
|
||||
Key::RowEndSpacer,
|
||||
Key::End,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::LCtrl,
|
||||
Key::LFn,
|
||||
Key::Meta,
|
||||
Key::LAlt,
|
||||
Key::Space,
|
||||
Key::RAlt,
|
||||
Key::PrtSc,
|
||||
Key::RCtrl,
|
||||
Key::ArrowSpacer,
|
||||
Key::Up,
|
||||
Key::ArrowSpacer,
|
||||
Key::RowEndSpacer,
|
||||
Key::RFn,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::Left,
|
||||
Key::Down,
|
||||
Key::Right,
|
||||
Key::ArrowSpacer,
|
||||
],
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
156
rog-aura/src/layouts/ga401.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use super::{KeyLayout, KeyRow};
|
||||
use crate::keys::Key;
|
||||
|
||||
impl KeyLayout {
|
||||
pub fn ga401_layout() -> Self {
|
||||
Self {
|
||||
matches: vec!["GA401".into(), "GA402".into()],
|
||||
locale: "US".to_owned(),
|
||||
rows: vec![
|
||||
KeyRow::new(
|
||||
0.8,
|
||||
vec![
|
||||
Key::NormalSpacer,
|
||||
Key::FuncSpacer,
|
||||
Key::VolDown,
|
||||
Key::VolUp,
|
||||
Key::MicMute,
|
||||
Key::Rog,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
0.8,
|
||||
vec![
|
||||
Key::Esc,
|
||||
Key::FuncSpacer,
|
||||
Key::F1,
|
||||
Key::F2,
|
||||
Key::F3,
|
||||
Key::F4,
|
||||
Key::FuncSpacer, // not sure which key to put here
|
||||
Key::F5,
|
||||
Key::F6,
|
||||
Key::F7,
|
||||
Key::F8,
|
||||
Key::FuncSpacer,
|
||||
Key::F9,
|
||||
Key::F10,
|
||||
Key::F11,
|
||||
Key::F12,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Tilde,
|
||||
Key::N1,
|
||||
Key::N2,
|
||||
Key::N3,
|
||||
Key::N4,
|
||||
Key::N5,
|
||||
Key::N6,
|
||||
Key::N7,
|
||||
Key::N8,
|
||||
Key::N9,
|
||||
Key::N0,
|
||||
Key::Hyphen,
|
||||
Key::Equals,
|
||||
Key::BkSpc,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Tab,
|
||||
Key::Q,
|
||||
Key::W,
|
||||
Key::E,
|
||||
Key::R,
|
||||
Key::T,
|
||||
Key::Y,
|
||||
Key::U,
|
||||
Key::I,
|
||||
Key::O,
|
||||
Key::P,
|
||||
Key::LBracket,
|
||||
Key::RBracket,
|
||||
Key::BackSlash,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Caps,
|
||||
Key::A,
|
||||
Key::S,
|
||||
Key::D,
|
||||
Key::F,
|
||||
Key::G,
|
||||
Key::H,
|
||||
Key::J,
|
||||
Key::K,
|
||||
Key::L,
|
||||
Key::SemiColon,
|
||||
Key::Quote,
|
||||
Key::Return,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::LShift,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::C,
|
||||
Key::V,
|
||||
Key::B,
|
||||
Key::N,
|
||||
Key::M,
|
||||
Key::Comma,
|
||||
Key::Period,
|
||||
Key::FwdSlash,
|
||||
Key::Rshift,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::LCtrl,
|
||||
Key::LFn,
|
||||
Key::Meta,
|
||||
Key::LAlt,
|
||||
Key::Space,
|
||||
Key::RAlt,
|
||||
Key::PrtSc,
|
||||
Key::RCtrl,
|
||||
Key::ArrowSpacer,
|
||||
Key::Up,
|
||||
Key::ArrowSpacer,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.2,
|
||||
vec![
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::Left,
|
||||
Key::Down,
|
||||
Key::Right,
|
||||
Key::ArrowSpacer,
|
||||
],
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
178
rog-aura/src/layouts/gx502.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
use super::{KeyLayout, KeyRow};
|
||||
use crate::keys::Key;
|
||||
|
||||
impl KeyLayout {
|
||||
pub fn gx502_layout() -> Self {
|
||||
Self {
|
||||
matches: vec!["GX502".into(), "GU502".into()],
|
||||
locale: "US".to_owned(),
|
||||
rows: vec![
|
||||
KeyRow::new(
|
||||
0.8,
|
||||
vec![
|
||||
Key::NormalSpacer,
|
||||
Key::FuncSpacer,
|
||||
Key::VolDown,
|
||||
Key::VolUp,
|
||||
Key::MicMute,
|
||||
Key::Rog,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
0.8,
|
||||
vec![
|
||||
Key::Esc,
|
||||
Key::FuncSpacer,
|
||||
Key::F1,
|
||||
Key::F2,
|
||||
Key::F3,
|
||||
Key::F4,
|
||||
Key::FuncSpacer, // not sure which key to put here
|
||||
Key::F5,
|
||||
Key::F6,
|
||||
Key::F7,
|
||||
Key::F8,
|
||||
Key::FuncSpacer,
|
||||
Key::F9,
|
||||
Key::F10,
|
||||
Key::F11,
|
||||
Key::F12,
|
||||
Key::RowEndSpacer,
|
||||
Key::Del,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Tilde,
|
||||
Key::N1,
|
||||
Key::N2,
|
||||
Key::N3,
|
||||
Key::N4,
|
||||
Key::N5,
|
||||
Key::N6,
|
||||
Key::N7,
|
||||
Key::N8,
|
||||
Key::N9,
|
||||
Key::N0,
|
||||
Key::Hyphen,
|
||||
Key::Equals,
|
||||
Key::BkSpc3_1,
|
||||
Key::BkSpc3_2,
|
||||
Key::BkSpc3_3,
|
||||
Key::RowEndSpacer,
|
||||
Key::Home,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Tab,
|
||||
Key::Q,
|
||||
Key::W,
|
||||
Key::E,
|
||||
Key::R,
|
||||
Key::T,
|
||||
Key::Y,
|
||||
Key::U,
|
||||
Key::I,
|
||||
Key::O,
|
||||
Key::P,
|
||||
Key::LBracket,
|
||||
Key::RBracket,
|
||||
Key::BackSlash,
|
||||
Key::RowEndSpacer,
|
||||
Key::PgUp,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::Caps,
|
||||
Key::A,
|
||||
Key::S,
|
||||
Key::D,
|
||||
Key::F,
|
||||
Key::G,
|
||||
Key::H,
|
||||
Key::J,
|
||||
Key::K,
|
||||
Key::L,
|
||||
Key::SemiColon,
|
||||
Key::Quote,
|
||||
Key::Return3_1,
|
||||
Key::Return3_2,
|
||||
Key::Return3_3,
|
||||
Key::RowEndSpacer,
|
||||
Key::PgDn,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::LShift,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::C,
|
||||
Key::V,
|
||||
Key::B,
|
||||
Key::N,
|
||||
Key::M,
|
||||
Key::Comma,
|
||||
Key::Period,
|
||||
Key::FwdSlash,
|
||||
Key::Rshift3_1,
|
||||
Key::Rshift3_2,
|
||||
Key::Rshift3_3,
|
||||
Key::RowEndSpacer,
|
||||
Key::End,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::LCtrl,
|
||||
Key::LFn,
|
||||
Key::Meta,
|
||||
Key::LAlt,
|
||||
Key::Space5_1,
|
||||
Key::Space5_2,
|
||||
Key::Space5_3,
|
||||
Key::Space5_4,
|
||||
Key::Space5_5,
|
||||
Key::RAlt,
|
||||
Key::PrtSc,
|
||||
Key::RCtrl,
|
||||
Key::ArrowSpacer,
|
||||
Key::Up,
|
||||
Key::ArrowSpacer,
|
||||
Key::RowEndSpacer,
|
||||
Key::RFn,
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
1.0,
|
||||
vec![
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::ArrowSpacer,
|
||||
Key::Left,
|
||||
Key::Down,
|
||||
Key::Right,
|
||||
Key::ArrowSpacer,
|
||||
],
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
105
rog-aura/src/layouts/mod.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
//! A series of pre-defined layouts. These were mostly used to generate an editable
|
||||
//! config.
|
||||
|
||||
/// Hardcoded layout. Was used to generate a toml default
|
||||
pub mod g513;
|
||||
/// Hardcoded layout. Was used to generate a toml default
|
||||
pub mod ga401;
|
||||
/// Hardcoded layout. Was used to generate a toml default
|
||||
pub mod gx502;
|
||||
|
||||
use crate::{error::Error, keys::Key};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs::{self, OpenOptions},
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
slice::Iter,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct KeyLayout {
|
||||
/// A series of board names that this layout can be used for. The board names
|
||||
/// stored with the layout can be globbed, e.g, GA401 will match all of the
|
||||
/// GA401I and GA401Q range variants.
|
||||
///
|
||||
/// `/sys/class/dmi/id/board_name`
|
||||
matches: Vec<String>,
|
||||
locale: String,
|
||||
rows: Vec<KeyRow>,
|
||||
}
|
||||
|
||||
impl KeyLayout {
|
||||
pub fn from_file(path: &Path) -> Result<Self, Error> {
|
||||
let mut file = OpenOptions::new().read(true).open(path)?;
|
||||
let mut buf = String::new();
|
||||
let read_len = file.read_to_string(&mut buf)?;
|
||||
if read_len == 0 {
|
||||
Err(Error::Io(std::io::ErrorKind::InvalidData.into()))
|
||||
} else {
|
||||
Ok(toml::from_str::<Self>(&buf)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches(&self, board_name: &str) -> bool {
|
||||
let board = board_name.to_ascii_uppercase();
|
||||
for tmp in &self.matches {
|
||||
if board.contains(tmp.as_str()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn rows(&self) -> Iter<KeyRow> {
|
||||
self.rows.iter()
|
||||
}
|
||||
|
||||
pub fn rows_ref(&self) -> &[KeyRow] {
|
||||
&self.rows
|
||||
}
|
||||
|
||||
/// Find a layout matching the provided board name in the provided dir
|
||||
pub fn find_layout(board_name: &str, mut data_path: PathBuf) -> Result<Self, Error> {
|
||||
let mut layout = KeyLayout::ga401_layout(); // default
|
||||
|
||||
data_path.push("layouts");
|
||||
let path = data_path.as_path();
|
||||
for p in fs::read_dir(path).map_err(|e| {
|
||||
println!("{:?}, {e}", path);
|
||||
e
|
||||
})? {
|
||||
let tmp = KeyLayout::from_file(&p?.path()).unwrap();
|
||||
if tmp.matches(board_name) {
|
||||
layout = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(layout)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct KeyRow {
|
||||
height: f32,
|
||||
row: Vec<Key>,
|
||||
}
|
||||
|
||||
impl KeyRow {
|
||||
pub fn new(height: f32, row: Vec<Key>) -> Self {
|
||||
Self { height, row }
|
||||
}
|
||||
|
||||
pub fn row(&self) -> Iter<'_, Key> {
|
||||
self.row.iter()
|
||||
}
|
||||
|
||||
pub fn row_ref(&self) -> &[Key] {
|
||||
&self.row
|
||||
}
|
||||
|
||||
pub fn height(&self) -> f32 {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
168
rog-aura/src/layouts/old_gx502.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct GX502Layout(Vec<[Key; 17]>);
|
||||
|
||||
impl KeyLayout for GX502Layout {
|
||||
fn get_rows(&self) -> &Vec<[Key; 17]> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GX502Layout {
|
||||
fn default() -> Self {
|
||||
GX502Layout(vec![
|
||||
[
|
||||
Key::NormalSpacer,
|
||||
Key::FuncSpacer,
|
||||
Key::VolDown,
|
||||
Key::VolUp,
|
||||
Key::MicMute,
|
||||
Key::Rog,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
],
|
||||
[
|
||||
Key::Esc,
|
||||
Key::NormalBlank,
|
||||
Key::F1,
|
||||
Key::F2,
|
||||
Key::F3,
|
||||
Key::F4,
|
||||
Key::NormalBlank, // not sure which key to put here
|
||||
Key::F5,
|
||||
Key::F6,
|
||||
Key::F7,
|
||||
Key::F8,
|
||||
Key::NormalBlank,
|
||||
Key::F9,
|
||||
Key::F10,
|
||||
Key::F11,
|
||||
Key::F12,
|
||||
Key::Del,
|
||||
],
|
||||
[
|
||||
Key::Tilde,
|
||||
Key::N1,
|
||||
Key::N2,
|
||||
Key::N3,
|
||||
Key::N4,
|
||||
Key::N5,
|
||||
Key::N6,
|
||||
Key::N7,
|
||||
Key::N8,
|
||||
Key::N9,
|
||||
Key::N0,
|
||||
Key::Hyphen,
|
||||
Key::Equals,
|
||||
Key::BkSpc3_1,
|
||||
Key::BkSpc3_2,
|
||||
Key::BkSpc3_3,
|
||||
Key::Home,
|
||||
],
|
||||
[
|
||||
Key::Tab,
|
||||
Key::Q,
|
||||
Key::W,
|
||||
Key::E,
|
||||
Key::R,
|
||||
Key::T,
|
||||
Key::Y,
|
||||
Key::U,
|
||||
Key::I,
|
||||
Key::O,
|
||||
Key::P,
|
||||
Key::LBracket,
|
||||
Key::RBracket,
|
||||
Key::BackSlash,
|
||||
Key::BackSlash,
|
||||
Key::BackSlash,
|
||||
Key::PgUp,
|
||||
],
|
||||
[
|
||||
Key::Caps,
|
||||
Key::A,
|
||||
Key::S,
|
||||
Key::D,
|
||||
Key::F,
|
||||
Key::G,
|
||||
Key::H,
|
||||
Key::J,
|
||||
Key::K,
|
||||
Key::L,
|
||||
Key::SemiColon,
|
||||
Key::Quote,
|
||||
Key::Quote,
|
||||
Key::Return3_1,
|
||||
Key::Return3_2,
|
||||
Key::Return3_3,
|
||||
Key::PgDn,
|
||||
],
|
||||
[
|
||||
Key::LShift,
|
||||
Key::LShift,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::C,
|
||||
Key::V,
|
||||
Key::B,
|
||||
Key::N,
|
||||
Key::M,
|
||||
Key::Comma,
|
||||
Key::Period,
|
||||
Key::FwdSlash,
|
||||
Key::FwdSlash,
|
||||
Key::Rshift3_1,
|
||||
Key::Rshift3_2,
|
||||
Key::Rshift3_3,
|
||||
Key::End,
|
||||
],
|
||||
[
|
||||
Key::LCtrl,
|
||||
Key::LFn,
|
||||
Key::Meta,
|
||||
Key::LAlt,
|
||||
Key::Space5_1,
|
||||
Key::Space5_2,
|
||||
Key::Space5_3,
|
||||
Key::Space5_4,
|
||||
Key::Space5_5,
|
||||
Key::RAlt,
|
||||
Key::PrtSc,
|
||||
Key::RCtrl,
|
||||
Key::RCtrl,
|
||||
Key::Left,
|
||||
Key::Up,
|
||||
Key::Right,
|
||||
Key::RFn,
|
||||
],
|
||||
[
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::NormalBlank,
|
||||
Key::Left,
|
||||
Key::Down,
|
||||
Key::Right,
|
||||
Key::NormalBlank,
|
||||
],
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,24 @@ pub use builtin_modes::*;
|
||||
mod per_key_rgb;
|
||||
pub use per_key_rgb::*;
|
||||
|
||||
pub mod usb;
|
||||
mod per_zone;
|
||||
pub use per_zone::*;
|
||||
|
||||
pub mod error;
|
||||
pub mod key_to_str;
|
||||
pub mod keys;
|
||||
pub use key_to_str::*;
|
||||
pub mod layouts;
|
||||
pub mod usb;
|
||||
|
||||
pub const LED_MSG_LEN: usize = 17;
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const RED: Colour = Colour(0xff, 0x00, 0x00);
|
||||
pub const GREEN: Colour = Colour(0x00, 0xff, 0x00);
|
||||
pub const BLUE: Colour = Colour(0x00, 0x00, 0xff);
|
||||
pub const VIOLET: Colour = Colour(0x9B, 0x26, 0xB6);
|
||||
pub const TEAL: Colour = Colour(0x00, 0x7C, 0x80);
|
||||
pub const YELLOW: Colour = Colour(0xff, 0xef, 0x00);
|
||||
pub const ORANGE: Colour = Colour(0xff, 0xa4, 0x00);
|
||||
pub const GRADIENT: [Colour; 7] = [RED, VIOLET, BLUE, TEAL, GREEN, YELLOW, ORANGE];
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
use crate::keys::Key;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
#[cfg(feature = "dbus")]
|
||||
use zbus::zvariant::Type;
|
||||
|
||||
/// Represents the per-key raw USB packets
|
||||
pub type PerKeyRaw = Vec<Vec<u8>>;
|
||||
|
||||
/// A `KeyColourArray` contains all data to change the full set of keyboard
|
||||
/// key colours individually.
|
||||
///
|
||||
@@ -5,16 +13,20 @@
|
||||
/// to the keyboard EC. One row controls one group of keys, these keys are not
|
||||
/// necessarily all on the same row of the keyboard, with some splitting between
|
||||
/// two rows.
|
||||
#[derive(Clone)]
|
||||
pub struct KeyColourArray([[u8; 64]; 11]);
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct KeyColourArray(PerKeyRaw);
|
||||
|
||||
impl Default for KeyColourArray {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyColourArray {
|
||||
pub fn new() -> Self {
|
||||
let mut set = [[0u8; 64]; 11];
|
||||
let mut set = vec![vec![0u8; 64]; 11];
|
||||
// set[0].copy_from_slice(&KeyColourArray::get_init_msg());
|
||||
for (count, row) in set.iter_mut().enumerate() {
|
||||
row[0] = 0x5d; // Report ID
|
||||
row[1] = 0xbc; // Mode = custom??, 0xb3 is builtin
|
||||
@@ -45,16 +57,16 @@ impl KeyColourArray {
|
||||
|
||||
#[inline]
|
||||
pub fn set(&mut self, key: Key, r: u8, g: u8, b: u8) {
|
||||
if let Some((rr, gg, bb)) = self.key(key) {
|
||||
*rr = r;
|
||||
*gg = g;
|
||||
*bb = b;
|
||||
if let Some(c) = self.rgb_for_key(key) {
|
||||
c[0] = r;
|
||||
c[1] = g;
|
||||
c[2] = b;
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexes in to `KeyColourArray` at the correct row and column
|
||||
/// to set a series of three bytes to the chosen R,G,B values
|
||||
pub fn key(&mut self, key: Key) -> Option<(&mut u8, &mut u8, &mut u8)> {
|
||||
pub fn rgb_for_key(&mut self, key: Key) -> Option<&mut [u8]> {
|
||||
// Tuples are indexes in to array
|
||||
let (row, col) = match key {
|
||||
Key::VolDown => (0, 15),
|
||||
@@ -91,9 +103,9 @@ impl KeyColourArray {
|
||||
Key::N0 => (3, 21),
|
||||
Key::Hyphen => (3, 24),
|
||||
Key::Equals => (3, 27),
|
||||
Key::BkSpc1 => (3, 30),
|
||||
Key::BkSpc2 => (3, 33),
|
||||
Key::BkSpc3 => (3, 36),
|
||||
Key::BkSpc3_1 => (3, 30),
|
||||
Key::BkSpc3_2 => (3, 33),
|
||||
Key::BkSpc3_3 => (3, 36),
|
||||
Key::Home => (3, 39),
|
||||
Key::Tab => (3, 54),
|
||||
//
|
||||
@@ -125,11 +137,16 @@ impl KeyColourArray {
|
||||
Key::SemiColon => (5, 51),
|
||||
Key::Quote => (5, 54),
|
||||
//
|
||||
Key::Ret1 => (6, 12),
|
||||
Key::Ret2 => (6, 15),
|
||||
Key::Ret3 => (6, 18),
|
||||
Key::Return => (6, 9),
|
||||
Key::Return3_1 => (6, 12),
|
||||
Key::Return3_2 => (6, 15),
|
||||
Key::Return3_3 => (6, 18),
|
||||
Key::PgDn => (6, 21),
|
||||
Key::LShift => (6, 36),
|
||||
// TODO: Find correct locations
|
||||
Key::LShift3_1 => (6, 36),
|
||||
Key::LShift3_2 => (6, 36),
|
||||
Key::LShift3_3 => (6, 36),
|
||||
Key::Z => (6, 42),
|
||||
Key::X => (6, 45),
|
||||
Key::C => (6, 48),
|
||||
@@ -141,19 +158,21 @@ impl KeyColourArray {
|
||||
Key::Comma => (7, 15),
|
||||
Key::Period => (7, 18),
|
||||
Key::FwdSlash => (7, 21),
|
||||
Key::Rshift1 => (7, 27),
|
||||
Key::Rshift2 => (7, 30),
|
||||
Key::Rshift3 => (7, 33),
|
||||
Key::Rshift => (7, 24),
|
||||
Key::Rshift3_1 => (7, 27),
|
||||
Key::Rshift3_2 => (7, 30),
|
||||
Key::Rshift3_3 => (7, 33),
|
||||
Key::End => (7, 36),
|
||||
Key::LCtrl => (7, 51),
|
||||
Key::LFn => (7, 54),
|
||||
//
|
||||
Key::Meta => (8, 9),
|
||||
Key::LAlt => (8, 12),
|
||||
Key::Space1 => (8, 15),
|
||||
Key::Space2 => (8, 18),
|
||||
Key::Space3 => (8, 21),
|
||||
Key::Space4 => (8, 24),
|
||||
Key::Space5_1 => (8, 15),
|
||||
Key::Space5_2 => (8, 18),
|
||||
Key::Space5_3 => (8, 21),
|
||||
Key::Space5_4 => (8, 24),
|
||||
Key::Space5_5 => (8, 27),
|
||||
Key::RAlt => (8, 30),
|
||||
Key::PrtSc => (8, 33),
|
||||
Key::RCtrl => (8, 36),
|
||||
@@ -164,294 +183,65 @@ impl KeyColourArray {
|
||||
//
|
||||
Key::Down => (10, 9),
|
||||
Key::Right => (10, 12),
|
||||
Key::None => return None,
|
||||
Key::NormalBlank
|
||||
| Key::FuncBlank
|
||||
| Key::NormalSpacer
|
||||
| Key::FuncSpacer
|
||||
| Key::ArrowBlank
|
||||
| Key::ArrowSpacer
|
||||
| Key::UpRegular
|
||||
| Key::DownRegular
|
||||
| Key::LeftRegular
|
||||
| Key::RightRegular
|
||||
| Key::UpSplit
|
||||
| Key::DownSplit
|
||||
| Key::LeftSplit
|
||||
| Key::RightSplit
|
||||
| Key::ArrowRegularBlank
|
||||
| Key::ArrowRegularSpacer
|
||||
| Key::ArrowSplitBlank
|
||||
| Key::ArrowSplitSpacer
|
||||
| Key::RshiftSmall
|
||||
| Key::LCtrlMed
|
||||
| Key::MediaPlay
|
||||
| Key::MediaStop
|
||||
| Key::MediaPrev
|
||||
| Key::MediaNext
|
||||
| Key::Pause
|
||||
| Key::NumLock
|
||||
| Key::Star
|
||||
| Key::NumPadDel
|
||||
| Key::NumPadPlus
|
||||
| Key::NumPadEnter
|
||||
| Key::NumPadPause
|
||||
| Key::NumPadPrtSc
|
||||
| Key::NumPadHome
|
||||
| Key::RCtrlLarge
|
||||
| Key::RowEndSpacer => return None,
|
||||
Key::Fan | Key::Space | Key::BkSpc => return None,
|
||||
};
|
||||
// LOLOLOLOLOLOLOL! Look it's safe okay
|
||||
unsafe {
|
||||
Some((
|
||||
&mut *(&mut self.0[row][col] as *mut u8),
|
||||
&mut *(&mut self.0[row][col + 1] as *mut u8),
|
||||
&mut *(&mut self.0[row][col + 2] as *mut u8),
|
||||
))
|
||||
}
|
||||
|
||||
Some(&mut self.0[row][col..=col + 2])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self) -> &[[u8; 64]; 11] {
|
||||
pub fn get(&self) -> PerKeyRaw {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> &PerKeyRaw {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum Key {
|
||||
VolUp,
|
||||
VolDown,
|
||||
MicMute,
|
||||
Rog,
|
||||
Esc,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
Del,
|
||||
Tilde,
|
||||
N1,
|
||||
N2,
|
||||
N3,
|
||||
N4,
|
||||
N5,
|
||||
N6,
|
||||
N7,
|
||||
N8,
|
||||
N9,
|
||||
N0,
|
||||
Hyphen,
|
||||
Equals,
|
||||
BkSpc1,
|
||||
BkSpc2,
|
||||
BkSpc3,
|
||||
Home,
|
||||
Tab,
|
||||
Q,
|
||||
W,
|
||||
E,
|
||||
R,
|
||||
T,
|
||||
Y,
|
||||
U,
|
||||
I,
|
||||
O,
|
||||
P,
|
||||
LBracket,
|
||||
RBracket,
|
||||
BackSlash,
|
||||
PgUp,
|
||||
Caps,
|
||||
A,
|
||||
S,
|
||||
D,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
SemiColon,
|
||||
Quote,
|
||||
Ret1,
|
||||
Ret2,
|
||||
Ret3,
|
||||
PgDn,
|
||||
LShift,
|
||||
Z,
|
||||
X,
|
||||
C,
|
||||
V,
|
||||
B,
|
||||
N,
|
||||
M,
|
||||
Comma,
|
||||
Period,
|
||||
FwdSlash,
|
||||
Rshift1,
|
||||
Rshift2,
|
||||
Rshift3,
|
||||
End,
|
||||
LCtrl,
|
||||
LFn,
|
||||
Meta,
|
||||
LAlt,
|
||||
Space1,
|
||||
Space2,
|
||||
Space3,
|
||||
Space4,
|
||||
RAlt,
|
||||
PrtSc,
|
||||
RCtrl,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
RFn,
|
||||
None,
|
||||
}
|
||||
|
||||
pub trait KeyLayout {
|
||||
fn get_rows(&self) -> &Vec<[Key; 17]>;
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct GX502Layout(Vec<[Key; 17]>);
|
||||
|
||||
impl KeyLayout for GX502Layout {
|
||||
fn get_rows(&self) -> &Vec<[Key; 17]> {
|
||||
&self.0
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self) -> &mut PerKeyRaw {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GX502Layout {
|
||||
fn default() -> Self {
|
||||
GX502Layout(vec![
|
||||
[
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::VolDown,
|
||||
Key::VolUp,
|
||||
Key::MicMute,
|
||||
Key::Rog,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
],
|
||||
[
|
||||
Key::Esc,
|
||||
Key::None,
|
||||
Key::F1,
|
||||
Key::F2,
|
||||
Key::F3,
|
||||
Key::F4,
|
||||
Key::None, // not sure which key to put here
|
||||
Key::F5,
|
||||
Key::F6,
|
||||
Key::F7,
|
||||
Key::F8,
|
||||
Key::F9,
|
||||
Key::F9,
|
||||
Key::F10,
|
||||
Key::F11,
|
||||
Key::F12,
|
||||
Key::Del,
|
||||
],
|
||||
[
|
||||
Key::Tilde,
|
||||
Key::N1,
|
||||
Key::N2,
|
||||
Key::N3,
|
||||
Key::N4,
|
||||
Key::N5,
|
||||
Key::N6,
|
||||
Key::N7,
|
||||
Key::N8,
|
||||
Key::N9,
|
||||
Key::N0,
|
||||
Key::Hyphen,
|
||||
Key::Equals,
|
||||
Key::BkSpc1,
|
||||
Key::BkSpc2,
|
||||
Key::BkSpc3,
|
||||
Key::Home,
|
||||
],
|
||||
[
|
||||
Key::Tab,
|
||||
Key::Q,
|
||||
Key::W,
|
||||
Key::E,
|
||||
Key::R,
|
||||
Key::T,
|
||||
Key::Y,
|
||||
Key::U,
|
||||
Key::I,
|
||||
Key::O,
|
||||
Key::P,
|
||||
Key::LBracket,
|
||||
Key::RBracket,
|
||||
Key::BackSlash,
|
||||
Key::BackSlash,
|
||||
Key::BackSlash,
|
||||
Key::PgUp,
|
||||
],
|
||||
[
|
||||
Key::Caps,
|
||||
Key::A,
|
||||
Key::S,
|
||||
Key::D,
|
||||
Key::F,
|
||||
Key::G,
|
||||
Key::H,
|
||||
Key::J,
|
||||
Key::K,
|
||||
Key::L,
|
||||
Key::SemiColon,
|
||||
Key::Quote,
|
||||
Key::Quote,
|
||||
Key::Ret1,
|
||||
Key::Ret2,
|
||||
Key::Ret3,
|
||||
Key::PgDn,
|
||||
],
|
||||
[
|
||||
Key::LShift,
|
||||
Key::LShift,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::C,
|
||||
Key::V,
|
||||
Key::B,
|
||||
Key::N,
|
||||
Key::M,
|
||||
Key::Comma,
|
||||
Key::Period,
|
||||
Key::FwdSlash,
|
||||
Key::FwdSlash,
|
||||
Key::Rshift1,
|
||||
Key::Rshift2,
|
||||
Key::Rshift3,
|
||||
Key::End,
|
||||
],
|
||||
[
|
||||
Key::LCtrl,
|
||||
Key::LFn,
|
||||
Key::Meta,
|
||||
Key::LAlt,
|
||||
Key::Space1,
|
||||
Key::Space2,
|
||||
Key::Space3,
|
||||
Key::Space4,
|
||||
Key::Space4,
|
||||
Key::RAlt,
|
||||
Key::PrtSc,
|
||||
Key::RCtrl,
|
||||
Key::RCtrl,
|
||||
Key::Left,
|
||||
Key::Up,
|
||||
Key::Right,
|
||||
Key::RFn,
|
||||
],
|
||||
[
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::None,
|
||||
Key::Left,
|
||||
Key::Down,
|
||||
Key::Right,
|
||||
Key::None,
|
||||
],
|
||||
])
|
||||
impl From<KeyColourArray> for PerKeyRaw {
|
||||
fn from(k: KeyColourArray) -> Self {
|
||||
k.0
|
||||
}
|
||||
}
|
||||
|
||||