Compare commits

...

36 Commits
3.4.0 ... 3.6.0

Author SHA1 Message Date
Luke Jones
c14768182c Merge branch 'fluke/extras' into 'main'
Update config & dbus parts, cleanup deps, device power states

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

Closes #88 and #86

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

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

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

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

Closes #68, #73, and #81

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

Partial close of #75
2021-04-12 10:49:08 +12:00
Luke D Jones
59f54b76f6 aura: split out all aura related files to rog-aura crate 2021-04-12 10:31:36 +12:00
Luke D Jones
6f36d91281 Begin rog-aura crate 2021-04-12 10:31:36 +12:00
Luke D Jones
e9f1fa01fc index on anime-cli: 0657c6c anime: prep rog-anime for publish, rename *all* AniMe~ to Anime 2021-04-12 10:31:36 +12:00
Luke D Jones
0d3a5d266b Changelog 2021-04-12 10:31:07 +12:00
Luke D Jones
cc28cee8bd anime: fix init 2021-04-11 22:47:21 +12:00
92 changed files with 3783 additions and 1834 deletions

6
.gitignore vendored
View File

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

View File

@@ -6,13 +6,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
# [3.6.0] - 2021-05-15
### Changed
- Add GX550L led modes
- Don't save compute/vfio modes. Option in config for this is removed.
- Store a temporary non-serialised option in config for if compute/vfio is active
for informational purposes only (will not apply on boot)
- Save state for LEDs enabled + sleep animation enabled
- Save state for AnimMe enabled + boot animation enabled
- Add extra config options and dbus methods
- Add power state signals for anime and led
- Refactor to use channels for dbus signal handler send/recv
- Split out profiles independant parts to a rog-profiles crate
- Cleanup dependencies
- Fix some dbus Supported issues
# [3.5.2] - 2021-05-15
### Changed
- Bugfix: prevent the hang on compute/integrated mode change
# [3.5.1] - 2021-04-25
### Changed
+ Anime:
- Fix using multiple configs
# [3.5.0] - 2021-04-25
### Changed
+ Keyboard:
- Split out all aura functionality that isn't dependent on the daemon in to a
new crate `rog-aura` (incomplete)
- Keyboard LED control now includes:
+ Enable/disable LED's while laptop is awake
+ Enable/disable LED animation while laptop is suspended and AC plugged in
- Properly reload the last used keyboard mode on boot
+ Graphics:
- Correctly enable compute mode for nvidia plus no-reboot or logout if switching
from vfio/integrated/compute.
- Add asusd config option to not save compute/vfio mode switch.
+ Anime:
- Enable basic multiple user anime configs (asusd-user must still be restarted)
+ Profiles:
- Enable dbus methods for freq min/max, fan curve, fan preset, CPU turbo enable.
These options will apply to the active profile if no profile name is specified.
# [3.4.1] - 2021-04-11
### Changed
- Fix anime init sequence
# [3.4.0] - 2021-04-11
### Changed
- Revert zbus to 1.9.1
- Use enum to show power states, and catch missing pci path for nvidia.
- Partial user-daemon for anime/per-key done, `asusd-user`. Includes asusd-user systemd unit.
- user-daemon provides dbus emthods to insert anime actions, remove from index, set leds on/off
+ COnfig file is stored in `~/.config/rog/rog-user.cfg`
+ Config file is stored in `~/.config/rog/rog-user.cfg`
- AniMe display parts split out to individual crate in preparation for publishing
on crates.io

132
Cargo.lock generated
View File

@@ -36,22 +36,24 @@ dependencies = [
"daemon",
"notify-rust",
"rog_dbus",
"rog_profiles",
"rog_types",
"serde_json",
]
[[package]]
name = "asusctl"
version = "3.4.0"
version = "3.5.0"
dependencies = [
"daemon",
"gif",
"glam",
"gumdrop",
"rog_anime",
"rog_aura",
"rog_dbus",
"rog_fan_curve",
"rog_profiles",
"rog_types",
"serde_json",
"tinybmp",
"yansi-term",
]
@@ -205,15 +207,16 @@ dependencies = [
[[package]]
name = "daemon"
version = "3.4.0"
version = "3.6.0"
dependencies = [
"env_logger",
"intel-pstate",
"log",
"logind-zbus",
"rog_anime",
"rog_aura",
"rog_dbus",
"rog_fan_curve",
"rog_profiles",
"rog_types",
"rusb",
"serde",
@@ -224,11 +227,12 @@ dependencies = [
"udev",
"zbus",
"zvariant",
"zvariant_derive",
]
[[package]]
name = "daemon-user"
version = "1.0.0"
version = "1.1.1"
dependencies = [
"dirs 3.0.1",
"rog_anime",
@@ -318,20 +322,6 @@ dependencies = [
"termcolor",
]
[[package]]
name = "err-derive"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote 1.0.9",
"rustversion",
"syn 1.0.69",
"synstructure",
]
[[package]]
name = "fastrand"
version = "1.4.0"
@@ -524,12 +514,12 @@ dependencies = [
[[package]]
name = "intel-pstate"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0e7f68d8a6d149a5b2195ab645923c63ee35928fff58895b3c1d21541afe90c"
checksum = "0dbd48c2f4886e44c137f4acb6ba3cf8df15154a2c996a65ee5e57c54a04c01f"
dependencies = [
"err-derive",
"smart-default",
"thiserror",
]
[[package]]
@@ -801,30 +791,6 @@ dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.69",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
@@ -897,7 +863,7 @@ checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "rog_anime"
version = "1.0.2"
version = "1.0.4"
dependencies = [
"gif",
"glam",
@@ -905,19 +871,28 @@ dependencies = [
"png_pong",
"serde",
"serde_derive",
"zbus",
"zvariant",
"zvariant_derive",
]
[[package]]
name = "rog_aura"
version = "1.1.0"
dependencies = [
"serde",
"serde_derive",
"zvariant",
"zvariant_derive",
]
[[package]]
name = "rog_dbus"
version = "3.2.0"
version = "3.4.0"
dependencies = [
"rog_anime",
"rog_fan_curve",
"rog_aura",
"rog_profiles",
"rog_types",
"serde_json",
"zbus",
"zbus_macros",
"zvariant",
@@ -932,12 +907,23 @@ dependencies = [
"serde",
]
[[package]]
name = "rog_profiles"
version = "0.1.0"
dependencies = [
"intel-pstate",
"rog_fan_curve",
"serde",
"serde_derive",
"zvariant",
"zvariant_derive",
]
[[package]]
name = "rog_types"
version = "3.2.0"
dependencies = [
"gumdrop",
"rog_fan_curve",
"rog_aura",
"serde",
"serde_derive",
"zvariant",
@@ -966,12 +952,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "rustversion"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
[[package]]
name = "ryu"
version = "1.0.5"
@@ -1100,18 +1080,6 @@ dependencies = [
"unicode-xid 0.0.4",
]
[[package]]
name = "synstructure"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.69",
"unicode-xid 0.2.1",
]
[[package]]
name = "sysfs-class"
version = "0.1.3"
@@ -1130,6 +1098,26 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.69",
]
[[package]]
name = "time"
version = "0.1.44"

View File

@@ -1,5 +1,5 @@
[workspace]
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-types", "rog-dbus", "rog-anime"]
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-types", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles"]
[profile.release]
lto = true
@@ -13,4 +13,4 @@ opt-level = 1
[profile.bench]
debug = false
opt-level = 3
opt-level = 3

395
MANUAL.md Normal file
View File

@@ -0,0 +1,395 @@
# asusctrl manual
`asusd` is a utility for Linux to control many aspects of various ASUS laptops
but can also be used with non-asus laptops with reduced features.
## Programs Available
- `asusd`: The main system daemon. It is autostarted by a udev rule and systemd unit.
- `asusd-user`: The user level daemon. Currently will run an anime sequence, with RGB keyboard sequences soon.
- `asusctl`: The CLI for interacting with the system daemon
- `asus-notify`: A notification daemon with a user systemd unit that can be enabled.
## `asusd`
`asusd` is the main system-level daemon which will control/load/save various settings in a safe way for the user, along with exposing a *safe* dbus interface for these interactions. This section covers only the daemon plus the various configuration file options.
The functionality that `asusd` exposes is:
- graphics switching
- anime control
- led keyboard control (aura)
- charge limiting
- bios/efivar control
- profiles (fan/cpu)
each of these will be detailed in sections.
### Graphics switching
`asusd` can switch graphics modes between:
- `integrated`, uses the iGPU only and force-disables the dGPU
- `compute`, enables Nvidia without Xorg. Useful for ML/Cuda
- `hybrid`, enables Nvidia prime-offload mode
- `nvidia`, uses the Nvidia gpu only
- `vfio`, binds the Nvidia gpu to vfio for VM pass-through
Switching to/from Hybrid and Nvidia modes requires a logout only (no reboot). Switching between integrated/compute/vfio does not require a logout and is instant.
#### Required actions in distro
**Rebootless note:** You must edit `/etc/default/grub` to remove `nvidia-drm.modeset=1`
from the line `GRUB_CMDLINE_LINUX=` and then recreate your grub config. In fedora
you can do this with `sudo grub2-mkconfig -o /etc/grub2.cfg` - other distro may be
similar but with a different config location. It's possible that graphics driver updates
may change this.
This switcher conflicts with other gpu switchers like optimus-manager, suse-prime
or ubuntu-prime, system76-power, and bbswitch. If you have issues with `asusd`
always defaulting to `integrated` mode on boot then you will need to check for
stray configs blocking nvidia modules from loading in:
- `/etc/modprobe.d/`
- `/usr/lib/modprope.d/`
#### Config options
1. `"gfx_mode": "<MODE>",`: MODE can be <Integrated, Hybrid, Compute, Nvidia, vfio>
2. `"gfx_last_mode": "Nvidia",`: currently unused
3. `"gfx_managed": true,`: enable or disable graphics switching controller
4. `"gfx_vfio_enable": false,`: enable vfio switching for Nvidia GPU passthrough
5. `"gfx_save_compute_vfio": false,`: wether or not to save the vfio state (so it sticks between boots)
#### Graphics switching notes
**G-Sync note:** Some laptops are capable of using the dGPU as the sole GPU in the system which is generally to enable g-sync on the laptop display panel. This is controlled by the bios/efivar control and will be covered in that section.
**vfio note:** The vfio modules *must not* be compiled into the kernel, they need
to be separate modules. If you don't plan to use vfio mode then you can ignore this
otherwise you may need a custom built kernel.
### AniMe control
Controller for the fancy AniMe matrix display on the lid of some machines. This controller is a work in progress.
#### Config options
If you have an AniMe device a few system-level config options are enabled for you in `/etc/asusd/anime.conf`;
1. `"system": [],`: currently unused, is intended to be a default continuous sequence in future versions
2. `"boot": [],`: a sequence that plays on system boot (when asusd is loaded)
3. `"wake": [],`: a sequence that plays when waking from suspend
4. `"shutdown": [],`: a sequence that plays when shutdown begins
5. `"brightness": <FLOAT>`: global brightness control, where `<FLOAT> is 0.0-1.0
Some default examples are provided but are minimal. The full range of configuration options will be covered in another section of this manual.
### Led keyboard control
The LED controller (e.g, aura) enables setting many of the factory modes available if a laptop supports them. It also enables per-key RGB settings but this is a WIP and will likely be similar to how AniMe sequences can be created.
#### Supported laptops
Models GA401, GA502, GU502 support LED brightness change only (no RGB). However the GA401Q model can actually use three modes; static, breathe, and pulse, plus also use red to control the LED brightness intensity.
All models that have any form of LED mode control need to be enabled via the config file at `/etc/asusd/asusd-ledmodes.toml`. Unfortunately ASUS doesn't provide any easy way to find all the supported modes for all laptops (not even through Armory Crate and its various files, that progrma downloads only the required settings for the laptop it runs on) so each model must be added as needed.
#### Config options
The defaults are located at `/etc/asusd/asusd-ledmodes.toml`, and on `asusd` start it creates `/etc/asusd/aura.conf` whcih stores the per-mode settings. If you edit the defaults file you must remove `/etc/asusd/aura.conf` and restart `asusd.service` with `systemctl restart asusd`.
##### /etc/asusd/asusd-ledmodes.toml
Example:
```toml
[[led_data]]
prod_family = "ROG Zephyrus M15"
board_names = ["GU502LU"]
standard = ["Static", "Breathe", "Strobe", "Pulse"]
multizone = false
per_key = false
```
1. `prod_family`: you can find this in `journalctl -b -u asusd`, or `cat /sys/class/dmi/id/product_name`. It should be copied as written. There can be multiple `led-data` groups of the same `prod_family` with differing `board_names`.
2. `board_names`: is an array of board names in this product family. Find this in the journal as above or by `cat /sys/class/dmi/id/board_name`.
3. `standard` are the factory preset modes, the names should corrospond to Armory Crate names
4. `multizone`: some keyboards have 4 zones of LED control, this enables setting a colour in each zone. The keyboard must support this or it has no effect.
5. `per_key`: enable per-key RGB effects. The keyboard must support this or it has no effect.
##### /etc/asusd/aura.conf
This file can be manually edited if desired, but the `asusctl` CLI tool, or dbus methods are the preferred method. Any manual changes to this file mean that the `asusd.service` will need to be restarted, or you need to cycle between modes to force a reload.
### Charge control
Almost all modern ASUS laptops have charging limit control now. This can be controlled in `/etc/asusd/asusd.conf`.
```json
"bat_charge_limit": 80,
```
where the number is a percentage.
### Bios control
Some options that you find in Armory Crate are available under this controller, so far there is:
- POST sound: this is the sound you here on bios boot post
- G-Sync: this controls if the dGPU (Nvidia) is the *only* GPU, making it the main GPU and disabling the iGPU
These options are not written to the config file as they are stored in efivars. The only way to change these is to use the exposed safe dbus methods, or use the `asusctl` CLI tool.
### Profiles
Profiles provide a method setting up various basic CPU and fan settings in profile blocks which can then be switched between or cycled through. The CPU controls so far are:
- Min/Max percentage of CPU frequency (Intel only for now)
- CPU turbo boost enable or disable
- Fan presets. These are 0: Normal, 1: Boost, 2: Silent.
- Fan curves, override fan-preset. AMD only.
#### Config options
Example:
```json
"toggle_profiles": [
"normal",
"boost",
"silent"
],
"power_profiles": {
"boost": {
"min_percentage": 0,
"max_percentage": 100,
"turbo": true,
"fan_preset": 1,
"fan_curve": null
},
"normal": {
"min_percentage": 0,
"max_percentage": 100,
"turbo": true,
"fan_preset": 0,
"fan_curve": null
},
"silent": {
"min_percentage": 0,
"max_percentage": 100,
"turbo": true,
"fan_preset": 2,
"fan_curve": null
}
}
```
1. `"toggle_profiles": [],`: these are the profile names that will be cycled through when using a provided next/prev dbus method.
2. `"power_profiles": {}`: all the available profiles.
#### Fan curves
**fan_curve note:** This is a WIP. Currently it relies on `acpi_call` kernel module which is ancient and hacky, not intended for this purpose. A proper kernel driver is in progress.
See [this document](https://github.com/cronosun/atrofac/blob/master/ADVANCED.md#limits) for details on the string format required, e.g, `"fan_curve": "30c:0%,40c:5%,50c:10%,60c:20%,70c:35%,80c:55%,90c:65%,100c:65%"`.
### Support controller
There is one more controller; the support controller. The sole pupose of this controller is to querie all the other controllers for information about their support level for the host laptop. Returns a json string.
## asusd-user
`asusd-user` is a usermode daemon. The intended purpose is to provide a method for users to run there own custom per-key keyboard effects and modes, AniMe sequences, and possibly their own profiles - all without overwriting the *base* system config. As such some parts of the system daemon will migrate to the user daemon over time with the expectation that the Linux system runs both.
As of now only AniMe is active in this with configuration in `~/.config/rog/`. On first run defaults are created that are intended to work as examples.
The main config is `~/.config/rog/rog-user.cfg`
#### Config options: AniMe
`~/.config/rog/rog-user.cfg` contains a setting `"active_anime": "<FILENAME>"` where `<FILENAME>` is the name of the AniMe config to use, located in the same directory and without the file postfix, e.g, `"active_anime": "anime-doom"`
An AniMe config itself is a file with contents:
```json
{
"name": "<FILENAME>",
"anime": []
}
```
`<FILENAME>` is used as a reference internally. `"anime": []` is an array of sequences (WIP).
##### "anime" array options
Each object in the array can be one of:
1. AsusAnimation
2. ImageAnimation
3. Image
4. Pause
##### AsusAnimation
`AsusAnimation` is specifically for running the gif files that Armory Crate comes with. `asusctl` includes all of these in `/usr/share/asusd/anime/asus/`
```json
"AsusAnimation": {
"file": "<FILE_PATH>",
"time": <TIME>,
"brightness": <FLOAT>
}
```
##### ImageAnimation
`ImageAnimation` can play *any* gif of any size.
```json
"ImageAnimation": {
"file": "<FILE_PATH>",
"scale": <FLOAT>,
"angle": <FLOAT>,
"translation": [
<FLOAT>,
<FLOAT>
],
"time": <TIME>,
"brightness": <FLOAT>
}
},
```
##### Image
`Image` currently requires 8bit greyscale png. It will be able to use most in future.
```json
{
"Image": {
"file": "<FILE_PATH>",
"scale": <FLOAT>,
"angle": <FLOAT>,
"translation": [
<FLOAT>,
<FLOAT>
],
"brightness": <FLOAT>
}
},
```
##### Pause
A `Pause` is handy for after an `Image` to hold the `Image` on the AniMe for a period.
```json
{
"Pause": {
"secs": <INT>,
"nanos": <INT>
}
},
```
##### Options for objects
**<FILE_PATH>**
Must be full path: `"/usr/share/asusd/anime/asus/gaming/Controller.gif"` or `/home/luke/Downloads/random.gif`.
**<FLOAT>**
A number from 0.0-1.0.
- `brightness`: If it is brightness it is combined with the system daemon global brightness
- `scale`: 1.0 is the original size with lower number shrinking, larger growing
- `angle`: Rotation angle in radians
- `translation`: Shift the image X -/+, and y -/+
**<TIME>**
Time is the length of time to run the gif for:
```json
"time": {
"Time": {
"secs": 5,
"nanos": 0
}
},
```
A cycle is how many gif loops to run:
```json
"time": {
"Cycles": 2
},
```
`Infinite` means that this gif will never end:
```json
"time": "Infinite",
```
**<INT>**
A plain non-float integer.
## asusctl
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. I can be used in any place a terminal app can be used.
This program will query `asusd` for the `Support` level of the laptop and show or hide options according to this support level.
Most commands are self-explanatory.
### CLI Usage and help
Commands are given by:
```
asusctl <option> <command> <command-options>
```
Help is available through:
```
asusctl --help
asusctl <command> --help
```
Some commands may have subcommands:
```
asusctl <command> <subcommand> --help
```
### Keybinds
To switch to next/previous Aura modes you will need to bind both the aura keys (if available) to one of:
**Next**
```
asusctl led-mode -n
```
**Previous**
```
asusctl led-mode -p
```
To switch Fan/Thermal profiles you need to bind the Fn+F5 key to `asusctl profile -n`.
## User NOTIFICATIONS via dbus
If you have a notifications handler set up, or are using KDE or Gnome then you
can enable the user service to get basic notifications when something changes.
```
systemctl --user enable asus-notify.service
systemctl --user start asus-notify.service
```
# License & Trademarks
Mozilla Public License 2 (MPL-2.0)
---
ASUS and ROG Trademark is either a US registered trademark or trademark of ASUSTeK Computer Inc. in the United States and/or other countries.
Reference to any ASUS products, services, processes, or other information and/or use of ASUS Trademarks does not constitute or imply endorsement, sponsorship, or recommendation thereof by ASUS.
The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops.
---

172
README.md
View File

@@ -1,4 +1,4 @@
# ASUS NB Ctrl
# `asusctl` for ASUS ROG
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/donate/?hosted_button_id=4V2DEPS7K6APC) - [Asus Linux Website](https://asus-linux.org/)
@@ -10,21 +10,18 @@ but can also be used with non-asus laptops with reduced features.
1. To provide an interface for rootless control of some system functions most users wish to control such as fan speeds, keyboard LEDs, graphics modes.
2. Enable third-party apps to use the above with dbus methods
3. To make the above as easy as possible for new users
4. Respect the users resources: be small, light, and fast
Point 3 means that the list of supported distros is very narrow - fedora is explicitly
supported, while Ubuntu and openSUSE are level-2 support. All other distros are *not*
supported (while asusd might still run fine on them). For best support use fedora 32+ Workstation.
Point 4? asusd currently uses a tiny fraction of cpu time, and less than 1Mb of ram, the way
a system-level daemon should.
**NOTICE:**
1. The following is *not* required for 5.11 kernel versions, as this version includes all the required patches.
2. 2021 hardware has a new keyboard prod_id and the patch is included in 5.12+
'hid-asus-rog` DKMS module from [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/).
The module enables the following in kernel:
- Initialising the keyboard
- All hotkeys (FN+Key combos)
Various patches are required for keyboard support. See [this post](https://asus-linux.org/blog/updates-2021-05-06/) for details on status and which kernels will have which patches.
## Discord
@@ -60,99 +57,9 @@ will probably suffer another rename once it becomes generic enough to do so.
- [X] Toggle bios setting for boot/POST sound
- [X] Toggle bios setting for "dedicated gfx" mode on supported laptops (g-sync)
# FUNCTIONS
## Graphics switching
`asusd` can switch graphics modes between:
- `integrated`, uses the iGPU only and force-disables the dGPU
- `hybrid`, enables Nvidia prime-offload mode
- `nvidia`, uses the Nvidia gpu only
- `vfio`, binds the Nvidia gpu to vfio for VM pass-through
**Rebootless note:** You must edit `/etc/default/grub` to remove `nvidia-drm.modeset=1`
from the line `GRUB_CMDLINE_LINUX=` and then recreate your grub config. In fedora
you can do this with `sudo grub2-mkconfig -o /etc/grub2.cfg` - other distro may be
similar but with a different config location.
This can be disabled in the config with `"manage_gfx": false,`. Additionally there
is an extra setting for laptops capable of g-sync dedicated gfx mode to enable the
graphics switching to switch on dedicated gfx for "nvidia" mode.
This switcher conflicts with other gpu switchers like optimus-manager, suse-prime
or ubuntu-prime, system76-power, and bbswitch. If you have issues with `asusd`
always defaulting to `integrated` mode on boot then you will need to check for
stray configs blocking nvidia modules from loading in:
- `/etc/modprobe.d/`
- `/usr/lib/modprope.d/`
**VFIO NOTE:** The vfio modules *must not* be compiled into the kernel, they need
to be separate modules. If you don't plan to use vfio mode then you can ignore this
otherwise you may need a custom built kernel.
To enable vfio switching you need to edit `/etc/asusd/asusd.conf` and change `"gfx_vfio_enable": false,` to true.
### Power management udev rule
If you have installed the Nvidia driver manually you will require the
`data/90-asusd-nvidia-pm.rules` udev rule to be installed in `/etc/udev/rules.d/`.
### fedora and openSUSE
You *may* need a file `/etc/dracut.conf.d/90-nvidia-dracut-G05.conf` installed
to stop dracut including the nvidia modules in the ramdisk if you manually
installed the nvidia drivers.
```
# filename /etc/dracut.conf.d/90-nvidia-dracut-G05.conf
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
# the ramdisk on updates, and to ensure the power-management udev rules run
# on module load
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
```
and run `dracut -f` after creating it.
## KEYBOARD BACKLIGHT MODES
Models GA401, GA502, GU502 support LED brightness change only (no RGB).
If you model isn't getting the correct led modes, you can edit the file
`/etc/asusd/asusd-ledmodes.toml`, the LED Mode numbers are as follows:
- Static
- Breathe
- Strobe
- Rainbow
- Star
- Rain
- Highlight
- Laser
- Ripple
- Pulse
- Comet
- Flash
use `cat /sys/class/dmi/id/product_name` to get details about your laptop. You
must restart the `asusd.service` after editing.
# Keybinds
To switch to next/previous Aura modes you will need to bind both the aura keys (if available) to one of:
**Next**
```
asusctl led-mode -n
```
**Previous**
```
asusctl led-mode -p
```
To switch Fan/Thermal profiles you need to bind the Fn+F5 key to `asusctl profile -n`.
# BUILDING
Requirements are rust >= 1.40 installed from rustup.io if the distro provided version is too old, and `make`.
Requirements are rust >= 1.47 installed from rustup.io if the distro provided version is too old, and `make`.
**Ubuntu*:** `apt install libclang-dev libudev-dev`
@@ -160,9 +67,7 @@ Requirements are rust >= 1.40 installed from rustup.io if the distro provided ve
## Installing
Packaging and auto-builds are available [here](https://build.opensuse.org/package/show/home:luke_nukem:asus/asus-nb-ctrl)
Download repositories are available [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/)
Download repositories are available [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/) for the latest versions of Fedora, Ubuntu, and openSUSE.
---
@@ -188,61 +93,22 @@ can be added on request). You will need to install the alternative service from
Run `sudo make uninstall` in the source repo, and remove `/etc/asusd/`.
# USAGE
Commands are given by:
```
asusctl <option> <command> <command-options>
```
Help is available through:
```
asusctl --help
asusctl <command> --help
```
Some commands may have subcommands:
```
asusctl <command> <subcommand> --help
```
## User NOTIFICATIONS via dbus
If you have a notifications handler set up, or are using KDE or Gnome then you
can enable the user service to get basic notifications when something changes.
```
systemctl --user enable asus-notify.service
systemctl --user start asus-notify.service
```
# OTHER
## AniMe input
You will want to look at what MeuMeu has done with [https://github.com/Meumeu/ZephyrusBling/](https://github.com/Meumeu/ZephyrusBling/)
## Supporting more laptops
Please file a support request.
## Notes:
- If charge limit or fan modes are not working, then you may require a kernel newer than 5.6.10.
- AniMe device check is performed on start, if your device has one it will be detected.
- GA14/GA401 and GA15/GA502/GU502, You will need kernel [patches](https://lab.retarded.farm/zappel/asus-rog-zephyrus-g14/-/tree/master/kernel_patches), these are on their way to the kernel upstream.
- On fedora manually installed Nvidia driver requires a dracut config as follows:
```
# filename/etc/dracut.conf.d/90-nvidia-dracut-G05.conf
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
# the ramdisk on updates, and to ensure the power-management udev rules run
# on module load
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
```
# License
# License & Trademarks
Mozilla Public License 2 (MPL-2.0)
---
ASUS and ROG Trademark is either a US registered trademark or trademark of ASUSTeK Computer Inc. in the United States and/or other countries.
Reference to any ASUS products, services, processes, or other information and/or use of ASUS Trademarks does not constitute or imply endorsement, sponsorship, or recommendation thereof by ASUS.
The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops.
---

View File

@@ -10,6 +10,7 @@ edition = "2018"
# serialisation
serde_json = "^1.0"
rog_dbus = { path = "../rog-dbus" }
rog_profiles = { path = "../rog-profiles" }
rog_types = { path = "../rog-types" }
daemon = { path = "../daemon" }

View File

@@ -1,6 +1,6 @@
use notify_rust::{Hint, Notification, NotificationHandle};
use rog_dbus::{DbusProxies, Signals};
use rog_types::profile::Profile;
use rog_profiles::profiles::{FanLevel, Profile};
use std::error::Error;
use std::thread::sleep;
use std::time::Duration;
@@ -36,73 +36,59 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
err_count = 0;
if let Ok(mut lock) = signals.gfx_vendor.lock() {
if let Some(vendor) = lock.take() {
if let Some(notif) = last_gfx_notif.take() {
notif.close();
}
let x = do_notif(&format!(
"Graphics mode changed to {}",
<&str>::from(vendor)
))?;
last_gfx_notif = Some(x);
if let Ok(vendor) = signals.gfx_vendor.try_recv() {
if let Some(notif) = last_gfx_notif.take() {
notif.close();
}
let x = do_notif(&format!(
"Graphics mode changed to {}",
<&str>::from(vendor)
))?;
last_gfx_notif = Some(x);
}
if let Ok(mut lock) = signals.charge.lock() {
if let Some(limit) = lock.take() {
if let Some(notif) = last_chrg_notif.take() {
notif.close();
}
let x = do_notif(&format!("Battery charge limit changed to {}", limit))?;
last_chrg_notif = Some(x);
if let Ok(limit) = signals.charge.try_recv() {
if let Some(notif) = last_chrg_notif.take() {
notif.close();
}
let x = do_notif(&format!("Battery charge limit changed to {}", limit))?;
last_chrg_notif = Some(x);
}
if let Ok(mut lock) = signals.profile.lock() {
if let Some(profile) = lock.take() {
if let Some(notif) = last_profile_notif.take() {
notif.close();
}
if let Ok(profile) = serde_json::from_str(&profile) {
let profile: Profile = profile;
if let Ok(name) = proxies.profile().active_profile_name() {
let x = do_thermal_notif(&profile, &name)?;
last_profile_notif = Some(x);
}
}
if let Ok(profile) = signals.profile.try_recv() {
if let Some(notif) = last_profile_notif.take() {
notif.close();
}
let x = do_thermal_notif(&profile)?;
last_profile_notif = Some(x);
}
if let Ok(mut lock) = signals.led_mode.lock() {
if let Some(ledmode) = lock.take() {
if let Some(notif) = last_led_notif.take() {
notif.close();
}
let x = do_notif(&format!(
"Keyboard LED mode changed to {}",
ledmode.mode_name()
))?;
last_led_notif = Some(x);
if let Ok(ledmode) = signals.led_mode.try_recv() {
if let Some(notif) = last_led_notif.take() {
notif.close();
}
let x = do_notif(&format!(
"Keyboard LED mode changed to {}",
ledmode.mode_name()
))?;
last_led_notif = Some(x);
}
}
}
fn do_thermal_notif(profile: &Profile, label: &str) -> Result<NotificationHandle, Box<dyn Error>> {
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
let fan = profile.fan_preset;
let turbo = if profile.turbo { "enabled" } else { "disabled" };
let icon = match fan {
0 => "asus_notif_yellow",
1 => "asus_notif_red",
2 => "asus_notif_green",
_ => "asus_notif_red",
FanLevel::Normal => "asus_notif_yellow",
FanLevel::Boost => "asus_notif_red",
FanLevel::Silent => "asus_notif_green",
};
let x = Notification::new()
.summary("ASUS ROG")
.body(&format!(
"Thermal profile changed to {}, turbo {}",
label.to_uppercase(),
profile.name.to_uppercase(),
turbo
))
.hint(Hint::Resident(true))

View File

@@ -1,18 +1,18 @@
[package]
name = "asusctl"
version = "3.4.0"
version = "3.5.0"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# serialisation
serde_json = "^1.0"
rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_dbus = { path = "../rog-dbus" }
rog_profiles = { path = "../rog-profiles" }
rog_types = { path = "../rog-types" }
daemon = { path = "../daemon" }
rog_fan_curve = { version = "^0.1", features = ["serde"] }
gumdrop = "^0.8"
yansi-term = "^0.1"

View File

@@ -1,10 +1,10 @@
use std::{env, error::Error, path::Path, process::exit};
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
use rog_dbus::AuraDbusClient;
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = AuraDbusClient::new().unwrap();
let (client, _) = RogDbusClient::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 3 {

View File

@@ -1,7 +1,7 @@
use std::{thread::sleep, time::Duration};
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
use rog_dbus::AuraDbusClient;
use rog_dbus::RogDbusClient;
// In usable data:
// Top row start at 1, ends at 32
@@ -9,7 +9,7 @@ use rog_dbus::AuraDbusClient;
// 74w x 36h diagonal used by the windows app
fn main() {
let (client, _) = AuraDbusClient::new().unwrap();
let (client, _) = RogDbusClient::new().unwrap();
for step in (2..50).rev() {
let mut matrix = AnimeDiagonal::new(None);

View File

@@ -1,10 +1,10 @@
use std::{env, path::Path, thread::sleep};
use rog_anime::{ActionData, AnimeAction, Sequences};
use rog_dbus::AuraDbusClient;
use rog_dbus::RogDbusClient;
fn main() {
let (client, _) = AuraDbusClient::new().unwrap();
let (client, _) = RogDbusClient::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 3 {

View File

@@ -1,5 +1,5 @@
use rog_anime::{AnimeDataBuffer, AnimeGrid};
use rog_dbus::AuraDbusClient;
use rog_dbus::RogDbusClient;
// In usable data:
// Top row start at 1, ends at 32
@@ -7,7 +7,7 @@ use rog_dbus::AuraDbusClient;
// 74w x 36h diagonal used by the windows app
fn main() {
let (client, _) = AuraDbusClient::new().unwrap();
let (client, _) = RogDbusClient::new().unwrap();
let mut matrix = AnimeGrid::new(None);
let tmp = matrix.get_mut();

View File

@@ -1,11 +1,11 @@
use rog_anime::AnimeDataBuffer;
use rog_dbus::AuraDbusClient;
use rog_dbus::RogDbusClient;
// In usable data:
// Top row start at 1, ends at 32
fn main() {
let (client, _) = AuraDbusClient::new().unwrap();
let (client, _) = RogDbusClient::new().unwrap();
let mut matrix = AnimeDataBuffer::new();
matrix.get_mut()[1] = 100; // start = 1
for n in matrix.get_mut()[2..32].iter_mut() {

View File

@@ -3,10 +3,10 @@ use std::{env, error::Error, path::Path, process::exit};
use rog_anime::{
AnimeDataBuffer, {AnimeImage, Vec2},
};
use rog_dbus::AuraDbusClient;
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = AuraDbusClient::new().unwrap();
let (client, _) = RogDbusClient::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 7 {

View File

@@ -5,10 +5,10 @@ use std::{
use rog_anime::{
AnimeDataBuffer, {AnimeImage, Vec2},
};
use rog_dbus::AuraDbusClient;
use rog_dbus::RogDbusClient;
fn main() -> Result<(), Box<dyn Error>> {
let (client, _) = AuraDbusClient::new().unwrap();
let (client, _) = RogDbusClient::new().unwrap();
let args: Vec<String> = env::args().into_iter().collect();
if args.len() != 7 {

View File

@@ -1,5 +1,5 @@
use rog_dbus::AuraDbusClient;
use rog_types::aura_perkey::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_dbus::RogDbusClient;
use std::collections::LinkedList;
#[derive(Debug, Clone)]
@@ -52,7 +52,7 @@ impl Ball {
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let mut colours = KeyColourArray::new();

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
use gumdrop::Options;
use rog_types::error::AuraError;
use rog_aura::error::Error;
use std::str::FromStr;
#[derive(Copy, Clone, Debug)]
@@ -8,7 +8,7 @@ pub enum AnimeStatusValue {
Off,
}
impl FromStr for AnimeStatusValue {
type Err = AuraError;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
@@ -17,7 +17,7 @@ impl FromStr for AnimeStatusValue {
"off" => Ok(AnimeStatusValue::Off),
_ => {
print!("Invalid argument, must be one of: on, off");
Err(AuraError::ParseAnime)
Err(Error::ParseAnime)
}
}
}

View File

@@ -1,8 +1,5 @@
use gumdrop::Options;
use rog_types::{
aura_modes::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed},
error::AuraError,
};
use rog_aura::{error::Error, AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed};
use std::str::FromStr;
#[derive(Options)]
@@ -19,7 +16,7 @@ impl LedBrightness {
}
}
impl FromStr for LedBrightness {
type Err = AuraError;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
@@ -30,7 +27,7 @@ impl FromStr for LedBrightness {
"high" => Ok(LedBrightness { level: Some(0x03) }),
_ => {
print!("Invalid argument, must be one of: off, low, med, high");
Err(AuraError::ParseBrightness)
Err(Error::ParseBrightness)
}
}
}

View File

@@ -1,15 +1,17 @@
mod anime_cli;
mod aura_cli;
mod profiles_cli;
use crate::aura_cli::{LedBrightness, SetAuraBuiltin};
use anime_cli::{AnimeActions, AnimeCommand};
use gumdrop::{Opt, Options};
use profiles_cli::ProfileCommand;
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2, ANIME_DATA_LEN};
use rog_dbus::AuraDbusClient;
use rog_aura::{self, AuraEffect};
use rog_dbus::RogDbusClient;
use rog_profiles::profiles::Profile;
use rog_types::{
aura_modes::{self, AuraEffect, AuraModeNum},
gfx_vendors::GfxVendors,
profile::{FanLevel, ProfileCommand, ProfileEvent},
supported::{
FanCpuSupportedFunctions, LedSupportedFunctions, RogBiosSupportedFunctions,
SupportedFunctions,
@@ -29,11 +31,6 @@ struct CliStart {
show_supported: bool,
#[options(meta = "", help = "<off, low, med, high>")]
kbd_bright: Option<LedBrightness>,
#[options(
meta = "",
help = "<silent, normal, boost>, set fan mode independent of profile"
)]
fan_mode: Option<FanLevel>,
#[options(meta = "", help = "<20-100>")]
chg_limit: Option<u8>,
#[options(command)]
@@ -62,6 +59,16 @@ struct LedModeCommand {
next_mode: bool,
#[options(help = "switch to previous aura mode")]
prev_mode: bool,
#[options(
meta = "",
help = "set the keyboard LED to enabled while the device is awake"
)]
awake_enable: Option<bool>,
#[options(
meta = "",
help = "set the keyboard LED suspend animation to enabled while the device is suspended"
)]
sleep_enable: Option<bool>,
#[options(command)]
command: Option<SetAuraBuiltin>,
}
@@ -122,10 +129,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
let (dbus, _) = AuraDbusClient::new()?;
let (dbus, _) = RogDbusClient::new()?;
let supported_tmp = dbus.proxies().supported().get_supported_functions()?;
let supported = serde_json::from_str::<SupportedFunctions>(&supported_tmp)?;
let supported = dbus.proxies().supported().get_supported_functions()?;
if parsed.help {
print_supported_help(&supported, &parsed);
@@ -137,7 +143,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" asusctl v{}", env!("CARGO_PKG_VERSION"));
println!(" rog-dbus v{}", rog_dbus::VERSION);
println!("rog-types v{}", rog_types::VERSION);
println!(" daemon v{}", daemon::VERSION);
return Ok(());
}
@@ -193,10 +198,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
Some(CliCommand::Bios(cmd)) => handle_bios_option(&dbus, &supported.rog_bios_ctrl, &cmd)?,
None => {
if (!parsed.show_supported
&& parsed.kbd_bright.is_none()
&& parsed.fan_mode.is_none()
&& parsed.chg_limit.is_none())
if (!parsed.show_supported && parsed.kbd_bright.is_none() && parsed.chg_limit.is_none())
|| parsed.help
{
println!("{}", CliStart::usage());
@@ -215,21 +217,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Some(level) => dbus
.proxies()
.led()
.set_led_brightness(<aura_modes::LedBrightness>::from(level))?,
.set_led_brightness(<rog_aura::LedBrightness>::from(level))?,
}
}
if parsed.show_supported {
let dat = dbus.proxies().supported().get_supported_functions()?;
println!("Supported laptop functions:\n{}", dat);
println!("Supported laptop functions:\n{:?}", dat);
}
if let Some(fan_level) = parsed.fan_mode {
dbus.proxies().profile().write_fan_mode(fan_level.into())?;
}
if let Some(chg_limit) = parsed.chg_limit {
dbus.proxies().charge().write_limit(chg_limit)?;
}
Ok(())
}
@@ -259,7 +259,7 @@ fn print_supported_help(supported: &SupportedFunctions, parsed: &CliStart) {
{
return false;
}
if line.contains("led-mode") && supported.keyboard_led.stock_led_modes.is_none() {
if line.contains("led-mode") && !supported.keyboard_led.stock_led_modes.is_empty() {
return false;
}
if line.contains("bios")
@@ -285,7 +285,7 @@ fn print_supported_help(supported: &SupportedFunctions, parsed: &CliStart) {
}
fn do_gfx(
dbus: &AuraDbusClient,
dbus: &RogDbusClient,
supported: &RogBiosSupportedFunctions,
command: GraphicsCommand,
) -> Result<(), Box<dyn std::error::Error>> {
@@ -331,11 +331,16 @@ fn do_gfx(
}
fn handle_led_mode(
dbus: &AuraDbusClient,
dbus: &RogDbusClient,
supported: &LedSupportedFunctions,
mode: &LedModeCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if mode.command.is_none() && !mode.prev_mode && !mode.next_mode {
if mode.command.is_none()
&& !mode.prev_mode
&& !mode.next_mode
&& mode.sleep_enable.is_none()
&& mode.awake_enable.is_none()
{
if !mode.help {
println!("Missing arg or command\n");
}
@@ -347,9 +352,11 @@ fn handle_led_mode(
.lines()
.map(|s| s.to_string())
.collect();
for command in commands.iter().filter(|mode| {
if let Some(modes) = supported.stock_led_modes.as_ref() {
return modes.contains(&<AuraModeNum>::from(mode.as_str()));
for command in commands.iter().filter(|command| {
for mode in &supported.stock_led_modes {
if command.contains(<&str>::from(mode)) {
return true;
}
}
if supported.multizone_led_mode {
return true;
@@ -389,27 +396,37 @@ fn handle_led_mode(
.set_led_mode(&<AuraEffect>::from(mode))?,
}
}
if let Some(enable) = mode.awake_enable {
dbus.proxies().led().set_awake_enabled(enable)?;
}
if let Some(enable) = mode.sleep_enable {
dbus.proxies().led().set_sleep_enabled(enable)?;
}
Ok(())
}
fn handle_profile(
dbus: &AuraDbusClient,
dbus: &RogDbusClient,
supported: &FanCpuSupportedFunctions,
cmd: &ProfileCommand,
) -> Result<(), Box<dyn std::error::Error>> {
if !cmd.next
&& !cmd.create
&& !cmd.create // TODO
&& !cmd.list
&& cmd.profile.is_none()
&& !cmd.active_name
&& !cmd.active_data
&& !cmd.profiles_data
&& cmd.remove.is_none()
&& cmd.curve.is_none()
&& cmd.max_percentage.is_none()
&& cmd.curve.is_none() // TODO
&& cmd.fan_preset.is_none() // TODO
&& cmd.turbo.is_none() // TODO
&& cmd.max_percentage.is_none() // TODO
&& cmd.min_percentage.is_none()
&& cmd.fan_preset.is_none()
&& cmd.profile.is_none()
&& cmd.turbo.is_none()
// TODO
{
if !cmd.help {
println!("Missing arg or command\n");
@@ -428,6 +445,9 @@ fn handle_profile(
if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst);
}
println!("Note: turbo, frequency, fan preset and fan curve options will apply to");
println!(" to the currently active profile unless a profile name is specified");
std::process::exit(1);
}
@@ -444,14 +464,12 @@ fn handle_profile(
if cmd.active_name {
println!(
"Active profile: {:?}",
dbus.proxies().profile().active_profile_name()?
dbus.proxies().profile().active_name()?
);
}
if cmd.active_data {
println!("Active profile:");
for s in dbus.proxies().profile().active_profile_data()?.lines() {
println!("{}", s);
}
println!("{:?}", dbus.proxies().profile().active_data()?);
}
if cmd.profiles_data {
println!("Profiles:");
@@ -460,17 +478,48 @@ fn handle_profile(
}
}
if cmd.profile.is_some() {
dbus.proxies()
.profile()
.write_command(&ProfileEvent::Cli(cmd.clone()))?
let mut set_profile = false;
let mut profile;
if cmd.create {
profile = Profile::default();
set_profile = true;
} else {
profile = dbus.proxies().profile().active_data()?;
}
if let Some(turbo) = cmd.turbo {
set_profile = true;
profile.turbo = turbo;
}
if let Some(min) = cmd.min_percentage {
set_profile = true;
profile.min_percentage = min;
}
if let Some(max) = cmd.max_percentage {
set_profile = true;
profile.max_percentage = max;
}
if let Some(preset) = cmd.fan_preset {
set_profile = true;
profile.fan_preset = preset;
}
if let Some(ref curve) = cmd.curve {
set_profile = true;
profile.fan_curve = curve.as_config_string();
}
if let Some(ref name) = cmd.profile {
set_profile = true;
profile.name = name.clone();
}
if set_profile {
dbus.proxies().profile().new_or_modify(&profile)?;
}
Ok(())
}
fn handle_bios_option(
dbus: &AuraDbusClient,
dbus: &RogDbusClient,
supported: &RogBiosSupportedFunctions,
cmd: &BiosCommand,
) -> Result<(), Box<dyn std::error::Error>> {
@@ -489,8 +538,8 @@ fn handle_bios_option(
.collect();
for line in usage.iter().filter(|line| {
!(line.contains("sound") && !supported.post_sound_toggle)
|| !(line.contains("GPU") && !supported.dedicated_gfx_toggle)
!line.contains("sound") && !supported.post_sound_toggle
|| !line.contains("GPU") && !supported.dedicated_gfx_toggle
}) {
println!("{}", line);
}

View File

@@ -0,0 +1,53 @@
use gumdrop::Options;
use rog_fan_curve::{Curve, Fan};
use rog_profiles::profiles::FanLevel;
#[derive(Debug, Clone, Options)]
pub struct ProfileCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(help = "toggle to next profile in list")]
pub next: bool,
#[options(help = "create the profile if it doesn't exist")]
pub create: bool,
#[options(meta = "", help = "remove a profile by name")]
pub remove: Option<String>,
#[options(help = "list available profiles")]
pub list: bool,
#[options(help = "get active profile name")]
pub active_name: bool,
#[options(help = "get active profile data")]
pub active_data: bool,
#[options(help = "get all profile data")]
pub profiles_data: bool,
// Options for profile
#[options(meta = "", help = "enable or disable cpu turbo")]
pub turbo: Option<bool>,
#[options(meta = "", help = "set min cpu scaling (intel)")]
pub min_percentage: Option<u8>,
#[options(meta = "", help = "set max cpu scaling (intel)")]
pub max_percentage: Option<u8>,
#[options(meta = "", help = "<silent, normal, boost>")]
pub fan_preset: Option<FanLevel>,
#[options(
meta = "",
parse(try_from_str = "parse_fan_curve"),
help = "set fan curve"
)]
pub curve: Option<Curve>,
#[options(free)]
pub profile: Option<String>,
}
fn parse_fan_curve(data: &str) -> Result<Curve, String> {
let curve = Curve::from_config_str(data)?;
if let Err(err) = curve.check_safety(Fan::Cpu) {
return Err(format!("Unsafe curve {:?}", err));
}
if let Err(err) = curve.check_safety(Fan::Gpu) {
return Err(format!("Unsafe curve {:?}", err));
}
Ok(curve)
}

View File

@@ -1,6 +1,6 @@
[package]
name = "daemon-user"
version = "1.0.0"
version = "1.1.1"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
description = "Usermode daemon for user settings, anime, per-key lighting"
@@ -27,4 +27,4 @@ dirs = "3.0.1"
zbus = "^1.9.1"
zvariant = "^2.6"
zvariant_derive = "^2.6"
zvariant_derive = "^2.6"

View File

@@ -1,5 +1,5 @@
use rog_anime::{ActionData, AnimTime, AnimeAction, Sequences, Vec2};
use rog_dbus::AuraDbusClient;
use rog_dbus::RogDbusClient;
//use crate::dbus::DbusEvents;
use serde_derive::{Deserialize, Serialize};
use std::time::Duration;
@@ -15,7 +15,7 @@ use zbus::dbus_interface;
use zvariant::ObjectPath;
use zvariant_derive::Type;
use crate::{error::Error, user_config::UserConfig};
use crate::{error::Error, user_config::UserAnimeConfig};
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
pub enum TimeType {
@@ -28,14 +28,14 @@ pub enum TimeType {
/// and a zbus server behind `Arc<Mutex<T>>`
pub struct CtrlAnimeInner<'a> {
sequences: Sequences,
client: AuraDbusClient<'a>,
client: RogDbusClient<'a>,
do_early_return: &'a AtomicBool,
}
impl<'a> CtrlAnimeInner<'static> {
pub fn new(
sequences: Sequences,
client: AuraDbusClient<'static>,
client: RogDbusClient<'static>,
do_early_return: &'static AtomicBool,
) -> Result<Self, Error> {
Ok(Self {
@@ -111,8 +111,8 @@ impl<'a> CtrlAnimeInner<'static> {
}
pub struct CtrlAnime<'a> {
config: Arc<Mutex<UserConfig>>,
client: AuraDbusClient<'a>,
config: Arc<Mutex<UserAnimeConfig>>,
client: RogDbusClient<'a>,
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
/// Must be the same Atomic as in CtrlAnimeInner
inner_early_return: &'a AtomicBool,
@@ -120,15 +120,15 @@ pub struct CtrlAnime<'a> {
impl<'a> CtrlAnime<'static> {
pub fn new(
config: Arc<Mutex<UserConfig>>,
config: Arc<Mutex<UserAnimeConfig>>,
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
client: AuraDbusClient<'static>,
client: RogDbusClient<'static>,
inner_early_return: &'static AtomicBool,
) -> Result<Self, Error> {
Ok(CtrlAnime {
config,
inner,
client,
inner,
inner_early_return,
})
}
@@ -147,7 +147,6 @@ impl<'a> CtrlAnime<'static> {
}
}
// The pattern for a zbus method is:
// - Get config lock if required
// - Set inner_early_return to stop the inner run loop temporarily
@@ -181,7 +180,10 @@ impl CtrlAnime<'static> {
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller.sequences.insert(index as usize, &action)?;
controller
.sequences
.insert(index as usize, &action)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
}
config.anime.push(action);
config.write()?;
@@ -228,7 +230,10 @@ impl CtrlAnime<'static> {
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller.sequences.insert(index as usize, &action)?;
controller
.sequences
.insert(index as usize, &action)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
}
config.anime.push(action);
config.write()?;
@@ -266,7 +271,10 @@ impl CtrlAnime<'static> {
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller.sequences.insert(index as usize, &action)?;
controller
.sequences
.insert(index as usize, &action)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
}
config.anime.push(action);
config.write()?;
@@ -288,7 +296,10 @@ impl CtrlAnime<'static> {
self.inner_early_return.store(true, Ordering::SeqCst);
if let Ok(mut controller) = self.inner.lock() {
controller.sequences.insert(index as usize, &action)?;
controller
.sequences
.insert(index as usize, &action)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
}
config.anime.push(action);
config.write()?;

View File

@@ -1,5 +1,4 @@
use rog_dbus::AuraDbusClient;
use rog_types::supported::SupportedFunctions;
use rog_dbus::RogDbusClient;
use rog_user::{
ctrl_anime::{CtrlAnime, CtrlAnimeInner},
user_config::*,
@@ -16,17 +15,21 @@ use std::sync::atomic::AtomicBool;
static ANIME_INNER_EARLY_RETURN: AtomicBool = AtomicBool::new(false);
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" rog-dbus version {}", rog_dbus::VERSION);
println!(" user daemon v{}", rog_user::VERSION);
println!(" rog-anime v{}", rog_anime::VERSION);
println!(" rog-dbus v{}", rog_dbus::VERSION);
println!(" rog-types v{}", rog_types::VERSION);
let (client, _) = AuraDbusClient::new().unwrap();
let (client, _) = RogDbusClient::new().unwrap();
let supported = client.proxies().supported().get_supported_functions()?;
let supported = serde_json::from_str::<SupportedFunctions>(&&supported).unwrap();
let mut config = UserConfig::new();
config.load_config()?;
let anime = config.create_anime()?;
let config = Arc::new(Mutex::new(config));
let anime_config = UserAnimeConfig::load_config(config.active_anime)?;
let anime = anime_config.create_anime()?;
let anime_config = Arc::new(Mutex::new(anime_config));
// Create server
let connection = Connection::new_session()?;
@@ -43,9 +46,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
&ANIME_INNER_EARLY_RETURN,
)?));
// Need new client object for dbus control part
let (client, _) = AuraDbusClient::new().unwrap();
let anime_control =
CtrlAnime::new(config, inner.clone(), client, &ANIME_INNER_EARLY_RETURN)?;
let (client, _) = RogDbusClient::new().unwrap();
let anime_control = CtrlAnime::new(
anime_config,
inner.clone(),
client,
&ANIME_INNER_EARLY_RETURN,
)?;
anime_control.add_to_server(&mut server);
// Thread using inner
let _anime_thread = thread::Builder::new()

View File

@@ -6,4 +6,6 @@ pub mod ctrl_anime;
pub mod zbus_anime;
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
pub static VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -9,14 +9,91 @@ use serde_derive::{Deserialize, Serialize};
use crate::error::Error;
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct UserConfig {
#[derive(Debug, Deserialize, Serialize)]
pub struct UserAnimeConfig {
pub name: String,
pub anime: Vec<AnimeAction>,
}
impl UserConfig {
pub fn new() -> Self {
let x = Self {
impl UserAnimeConfig {
pub fn create_anime(&self) -> Result<Sequences, Error> {
let mut seq = Sequences::new();
for (idx, action) in self.anime.iter().enumerate() {
seq.insert(idx, action)?;
}
Ok(seq)
}
pub fn write(&self) -> Result<(), Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push("rog");
if !path.exists() {
create_dir(path.clone())?;
}
let name = self.name.clone();
path.push(name + ".cfg");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let json = serde_json::to_string_pretty(&self).unwrap();
file.write_all(json.as_bytes())?;
Ok(())
}
pub fn load_config(name: String) -> Result<UserAnimeConfig, Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
return Err(Error::XdgVars);
};
path.push("rog");
if !path.exists() {
create_dir(path.clone())?;
}
path.push(name.clone() + ".cfg");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)?;
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
let default = UserAnimeConfig {
name,
..Default::default()
};
let json = serde_json::to_string_pretty(&default).unwrap();
file.write_all(json.as_bytes())?;
return Ok(default);
} else if let Ok(data) = serde_json::from_str::<UserAnimeConfig>(&buf) {
return Ok(data);
}
}
Err(Error::ConfigLoadFail)
}
}
impl Default for UserAnimeConfig {
fn default() -> Self {
Self {
name: "default".to_string(),
anime: vec![
AnimeAction::AsusAnimation {
file: "/usr/share/asusd/anime/asus/rog/Sunset.gif".into(),
@@ -48,9 +125,21 @@ impl UserConfig {
time: AnimTime::Cycles(2),
},
],
};
println!("{}", serde_json::to_string_pretty(&x).unwrap());
x
}
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct UserConfig {
/// Name of active anime config file in the user config directory
pub active_anime: String,
}
impl UserConfig {
pub fn new() -> Self {
Self {
active_anime: "anime-default".to_string(),
}
}
pub fn load_config(&mut self) -> Result<(), Error> {
@@ -80,7 +169,7 @@ impl UserConfig {
let json = serde_json::to_string_pretty(&self).unwrap();
file.write_all(json.as_bytes())?;
} else if let Ok(data) = serde_json::from_str::<UserConfig>(&buf) {
self.anime = data.anime;
self.active_anime = data.active_anime;
return Ok(());
}
}
@@ -101,21 +190,14 @@ impl UserConfig {
path.push("rog-user.cfg");
let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(&path)?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let json = serde_json::to_string_pretty(&self).unwrap();
dbg!(&json);
file.write_all(json.as_bytes())?;
Ok(())
}
pub fn create_anime(&self) -> Result<Sequences, Error> {
let mut seq = Sequences::new();
for (idx, action) in self.anime.iter().enumerate() {
seq.insert(idx, action)?;
}
Ok(seq)
}
}

View File

@@ -18,6 +18,7 @@
//! * [`zbus::fdo::PropertiesProxy`]
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
#![allow(clippy::too_many_arguments)]
use zbus::dbus_proxy;

View File

@@ -1,6 +1,6 @@
[package]
name = "daemon"
version = "3.4.0"
version = "3.6.0"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -19,7 +19,9 @@ path = "src/daemon.rs"
[dependencies]
rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_types = { path = "../rog-types" }
rog_profiles = { path = "../rog-profiles" }
rog_dbus = { path = "../rog-dbus" }
rusb = "^0.8"
udev = "^0.6"
@@ -30,6 +32,7 @@ env_logger = "^0.8"
zbus = "^1.9.1"
zvariant = "^2.6"
zvariant_derive = { version = "^2.6" }
logind-zbus = "^0.7.1"
# serialisation
@@ -41,5 +44,3 @@ toml = "^0.5"
# Device control
sysfs-class = "^0.1.2" # used for backlight control and baord ID
rog_fan_curve = { version = "0.1", features = ["serde"] }
# cpu power management
intel-pstate = "^0.2"

View File

@@ -1,5 +1,6 @@
use log::{error, info, warn};
use rog_types::{gfx_vendors::GfxVendors, profile::Profile};
use rog_profiles::profiles::{FanLevel, Profile};
use rog_types::gfx_vendors::GfxVendors;
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs::{File, OpenOptions};
@@ -14,6 +15,9 @@ pub static AURA_CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
#[derive(Deserialize, Serialize)]
pub struct Config {
pub gfx_mode: GfxVendors,
/// Only for informational purposes.
#[serde(skip)]
pub gfx_tmp_mode: Option<GfxVendors>,
pub gfx_managed: bool,
pub gfx_vfio_enable: bool,
pub active_profile: String,
@@ -27,12 +31,43 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
let mut pwr = BTreeMap::new();
pwr.insert("normal".into(), Profile::new(0, 100, true, 0, None));
pwr.insert("boost".into(), Profile::new(0, 100, true, 1, None));
pwr.insert("silent".into(), Profile::new(0, 100, true, 2, None));
pwr.insert(
"normal".into(),
Profile::new(
"normal".into(),
0,
100,
true,
FanLevel::Normal,
"".to_string(),
),
);
pwr.insert(
"boost".into(),
Profile::new(
"boost".into(),
0,
100,
true,
FanLevel::Boost,
"".to_string(),
),
);
pwr.insert(
"silent".into(),
Profile::new(
"silent".into(),
0,
100,
true,
FanLevel::Silent,
"".to_string(),
),
);
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: true,
gfx_vfio_enable: false,
active_profile: "normal".into(),
@@ -52,12 +87,7 @@ impl Config {
.write(true)
.create(true)
.open(&CONFIG_PATH)
.unwrap_or_else(|_| {
panic!(
"The file {} or directory /etc/asusd/ is missing",
CONFIG_PATH
)
}); // okay to cause panic here
.unwrap_or_else(|_| panic!("The directory /etc/asusd/ is missing")); // okay to cause panic here
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
@@ -65,6 +95,16 @@ impl Config {
} else {
if let Ok(data) = serde_json::from_str(&buf) {
return data;
} else if let Ok(data) = serde_json::from_str::<ConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV341>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV324>(&buf) {
let config = data.into_current();
config.write();
@@ -75,21 +115,6 @@ impl Config {
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV301>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV222>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV212>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
}
warn!("Could not deserialise {}", CONFIG_PATH);
panic!("Please remove {} then restart asusd", CONFIG_PATH);

237
daemon/src/config_anime.rs Normal file
View File

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

View File

@@ -1,6 +1,6 @@
use crate::laptops::LaptopLedData;
use log::{error, info, warn};
use rog_types::aura_modes::{AuraEffect, AuraModeNum, AuraMultiZone, AuraZone, LedBrightness};
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, LedBrightness};
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs::{File, OpenOptions};
@@ -23,6 +23,29 @@ impl AuraConfigV320 {
current_mode: self.current_mode,
builtins: self.builtins,
multizone: self.multizone,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraConfigV352 {
pub brightness: LedBrightness,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
}
impl AuraConfigV352 {
pub(crate) fn into_current(self) -> AuraConfig {
AuraConfig {
brightness: self.brightness,
current_mode: self.current_mode,
builtins: self.builtins,
multizone: self.multizone,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
@@ -33,6 +56,8 @@ pub struct AuraConfig {
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
pub awake_enabled: bool,
pub sleep_anim_enabled: bool,
}
impl Default for AuraConfig {
@@ -42,6 +67,8 @@ impl Default for AuraConfig {
current_mode: AuraModeNum::Static,
builtins: BTreeMap::new(),
multizone: None,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
@@ -72,6 +99,11 @@ impl AuraConfig {
config.write();
info!("Updated AuraConfig version");
return config;
} else if let Ok(data) = serde_json::from_str::<AuraConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated AuraConfig version");
return config;
}
warn!("Could not deserialise {}", AURA_CONFIG_PATH);
panic!("Please remove {} then restart asusd", AURA_CONFIG_PATH);
@@ -146,3 +178,90 @@ impl AuraConfig {
None
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraMultiZone {
static_: [AuraEffect; 4],
breathe: [AuraEffect; 4],
}
impl AuraMultiZone {
pub fn set(&mut self, effect: AuraEffect) {
if effect.mode == AuraModeNum::Static {
match effect.zone {
AuraZone::None => {}
AuraZone::One => self.static_[0] = effect,
AuraZone::Two => self.static_[1] = effect,
AuraZone::Three => self.static_[2] = effect,
AuraZone::Four => self.static_[3] = effect,
}
} else if effect.mode == AuraModeNum::Breathe {
match effect.zone {
AuraZone::None => {}
AuraZone::One => self.breathe[0] = effect,
AuraZone::Two => self.breathe[1] = effect,
AuraZone::Three => self.breathe[2] = effect,
AuraZone::Four => self.breathe[3] = effect,
}
}
}
pub fn static_(&self) -> &[AuraEffect; 4] {
&self.static_
}
pub fn breathe(&self) -> &[AuraEffect; 4] {
&self.breathe
}
}
impl Default for AuraMultiZone {
fn default() -> Self {
Self {
static_: [
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::One,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Two,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Three,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Four,
..Default::default()
},
],
breathe: [
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::One,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Two,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Three,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Four,
..Default::default()
},
],
}
}
}

View File

@@ -1,103 +1,13 @@
use rog_types::{aura_modes::AuraEffect, gfx_vendors::GfxVendors, profile::Profile};
use rog_fan_curve::Curve;
use rog_profiles::profiles::Profile;
use rog_types::gfx_vendors::GfxVendors;
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use crate::config::Config;
/// for parsing old v2.1.2 config
#[allow(dead_code)]
#[derive(Deserialize)]
pub(crate) struct ConfigV212 {
gfx_managed: bool,
bat_charge_limit: u8,
active_profile: String,
toggle_profiles: Vec<String>,
power_profiles: BTreeMap<String, Profile>,
power_profile: u8,
kbd_led_brightness: u8,
kbd_backlight_mode: u8,
kbd_backlight_modes: Vec<AuraEffect>,
}
impl ConfigV212 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_managed: self.gfx_managed,
active_profile: self.active_profile,
gfx_vfio_enable: false,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.power_profile,
bat_charge_limit: self.bat_charge_limit,
power_profiles: self.power_profiles,
}
}
}
/// for parsing old v2.2.2 config
#[allow(dead_code)]
#[derive(Deserialize)]
pub(crate) struct ConfigV222 {
gfx_managed: bool,
bat_charge_limit: u8,
active_profile: String,
toggle_profiles: Vec<String>,
power_profiles: BTreeMap<String, Profile>,
power_profile: u8,
kbd_led_brightness: u8,
kbd_backlight_mode: u8,
kbd_backlight_modes: Vec<AuraEffect>,
}
impl ConfigV222 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.power_profile,
bat_charge_limit: self.bat_charge_limit,
power_profiles: self.power_profiles,
}
}
}
/// for parsing old v3.0.1 config
#[derive(Deserialize, Serialize)]
pub(crate) struct ConfigV301 {
pub gfx_managed: bool,
pub gfx_nv_mode_is_dedicated: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
// TODO: remove power_profile
#[serde(skip)]
pub curr_fan_mode: u8,
pub bat_charge_limit: u8,
pub kbd_led_brightness: u8,
pub kbd_backlight_mode: u8,
pub kbd_backlight_modes: Vec<AuraEffect>,
pub power_profiles: BTreeMap<String, Profile>,
}
impl ConfigV301 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.curr_fan_mode,
bat_charge_limit: self.bat_charge_limit,
power_profiles: self.power_profiles,
}
}
}
/// for parsing old v3.1.7 config
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
pub(crate) struct ConfigV317 {
pub gfx_mode: GfxVendors,
pub gfx_managed: bool,
@@ -110,20 +20,21 @@ pub(crate) struct ConfigV317 {
pub kbd_backlight_mode: u8,
#[serde(skip)]
pub kbd_backlight_modes: Option<bool>,
pub power_profiles: BTreeMap<String, Profile>,
pub power_profiles: BTreeMap<String, ProfileV317>,
}
impl ConfigV317 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.curr_fan_mode,
bat_charge_limit: self.bat_charge_limit,
power_profiles: self.power_profiles,
power_profiles: ProfileV317::transform_map(self.power_profiles),
}
}
}
@@ -137,20 +48,113 @@ pub struct ConfigV324 {
#[serde(skip)]
pub curr_fan_mode: u8,
pub bat_charge_limit: u8,
pub power_profiles: BTreeMap<String, Profile>,
pub power_profiles: BTreeMap<String, ProfileV317>,
}
impl ConfigV324 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.curr_fan_mode,
bat_charge_limit: self.bat_charge_limit,
power_profiles: self.power_profiles,
power_profiles: ProfileV317::transform_map(self.power_profiles),
}
}
}
#[derive(Deserialize, Serialize)]
pub struct ConfigV341 {
pub gfx_mode: GfxVendors,
pub gfx_managed: bool,
pub gfx_vfio_enable: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
#[serde(skip)]
pub curr_fan_mode: u8,
pub bat_charge_limit: u8,
pub power_profiles: BTreeMap<String, ProfileV317>,
}
impl ConfigV341 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.curr_fan_mode,
bat_charge_limit: self.bat_charge_limit,
power_profiles: ProfileV317::transform_map(self.power_profiles),
}
}
}
#[derive(Deserialize, Serialize)]
pub struct ConfigV352 {
pub gfx_mode: GfxVendors,
pub gfx_last_mode: GfxVendors,
pub gfx_managed: bool,
pub gfx_vfio_enable: bool,
pub gfx_save_compute_vfio: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
#[serde(skip)]
pub curr_fan_mode: u8,
pub bat_charge_limit: u8,
pub power_profiles: BTreeMap<String, ProfileV317>,
}
impl ConfigV352 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
active_profile: self.active_profile,
toggle_profiles: self.toggle_profiles,
curr_fan_mode: self.curr_fan_mode,
bat_charge_limit: self.bat_charge_limit,
power_profiles: ProfileV317::transform_map(self.power_profiles),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ProfileV317 {
pub min_percentage: u8,
pub max_percentage: u8,
pub turbo: bool,
pub fan_preset: u8,
pub fan_curve: Option<Curve>,
}
impl ProfileV317 {
fn into_current(self, name: String) -> Profile {
Profile {
name,
min_percentage: self.min_percentage,
max_percentage: self.max_percentage,
turbo: self.turbo,
fan_preset: self.fan_preset.into(),
fan_curve: self
.fan_curve
.map_or_else(|| "".to_string(), |c| c.as_config_string()),
}
}
fn transform_map(map: BTreeMap<String, ProfileV317>) -> BTreeMap<String, Profile> {
let mut new_map = BTreeMap::new();
map.iter().for_each(|(k, v)| {
new_map.insert(k.to_string(), v.clone().into_current(k.to_string()));
});
new_map
}
}

View File

@@ -1,69 +1,56 @@
use log::{error, info, warn};
use logind_zbus::ManagerProxy;
use rog_anime::{
usb::{
pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID,
VENDOR_ID,
},
AnimeDataBuffer, AnimePacketType,
ActionData, AnimTime, AnimeDataBuffer, AnimePacketType, AnimePowerStates, ANIME_DATA_LEN,
};
use rog_types::supported::AnimeSupportedFunctions;
use rusb::{Device, DeviceHandle};
use std::error::Error;
use std::time::Duration;
use zbus::dbus_interface;
use std::{
error::Error,
sync::{Arc, Mutex},
thread::sleep,
time::Instant,
};
use std::{
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use zbus::{dbus_interface, Connection};
use zvariant::ObjectPath;
use crate::GetSupported;
use crate::{
config_anime::{AnimeConfig, AnimeConfigCached},
error::RogError,
GetSupported,
};
impl GetSupported for CtrlAnimeDisplay {
impl GetSupported for CtrlAnime {
type A = AnimeSupportedFunctions;
fn get_supported() -> Self::A {
AnimeSupportedFunctions(CtrlAnimeDisplay::get_device(VENDOR_ID, PROD_ID).is_ok())
AnimeSupportedFunctions(CtrlAnime::get_device(VENDOR_ID, PROD_ID).is_ok())
}
}
pub struct CtrlAnimeDisplay {
pub struct CtrlAnime {
handle: DeviceHandle<rusb::GlobalContext>,
cache: AnimeConfigCached,
config: AnimeConfig,
// set to force thread to exit
thread_exit: Arc<AtomicBool>,
// Set to false when the thread exits
thread_running: Arc<AtomicBool>,
}
impl crate::ZbusAdd for CtrlAnimeDisplay {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnimeDisplay {
/// Writes a data stream of length
fn write(&self, input: AnimeDataBuffer) {
self.write_data_buffer(input);
}
fn set_on_off(&self, status: bool) {
self.write_bytes(&pkt_for_set_on(status));
}
fn set_boot_on_off(&self, on: bool) {
self.write_bytes(&pkt_for_set_boot(on));
self.write_bytes(&pkt_for_apply());
}
}
impl CtrlAnimeDisplay {
impl CtrlAnime {
#[inline]
pub fn new() -> Result<CtrlAnimeDisplay, Box<dyn Error>> {
pub fn new(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
// We don't expect this ID to ever change
let device = CtrlAnimeDisplay::get_device(0x0b05, 0x193b)?;
let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
let mut device = device.open()?;
device.reset()?;
@@ -79,7 +66,16 @@ impl CtrlAnimeDisplay {
})?;
info!("Device has an AniMe Matrix display");
let ctrl = CtrlAnimeDisplay { handle: device };
let mut cache = AnimeConfigCached::default();
cache.init_from_config(&config)?;
let ctrl = CtrlAnime {
handle: device,
cache,
config,
thread_exit: Arc::new(AtomicBool::new(false)),
thread_running: Arc::new(AtomicBool::new(false)),
};
ctrl.do_initialization();
Ok(ctrl)
@@ -95,6 +91,117 @@ impl CtrlAnimeDisplay {
Err(rusb::Error::NoDevice)
}
/// Start an action thread. This is classed as a singleton and there should be only
/// one running - so the thread uses atomics to signal run/exit.
///
/// Because this also writes to the usb device, other write tries (display only) *must*
/// get the mutex lock and set the thread_exit atomic.
fn run_thread(inner: Arc<Mutex<CtrlAnime>>, actions: Vec<ActionData>, mut once: bool) {
if actions.is_empty() {
warn!("AniMe system actions was empty");
return;
}
// Loop rules:
// - Lock the mutex **only when required**. That is, the lock must be held for the shortest duration possible.
// - An AtomicBool used for thread exit should be checked in every loop, including nested
// The only reason for this outer thread is to prevent blocking while waiting for the
// next spawned thread to exit
std::thread::Builder::new()
.name("AniMe system thread start".into())
.spawn(move || {
info!("AniMe system thread started");
// Getting copies of these Atomics is done *in* the thread to ensure
// we don't block other threads/main
let thread_exit;
let thread_running;
// First two loops are to ensure we *do* aquire a lock on the mutex
// The reason the loop is required is because the USB writes can block
// for up to 10ms. We can't fail to get the atomics.
loop {
if let Ok(lock) = inner.try_lock() {
thread_exit = lock.thread_exit.clone();
thread_running = lock.thread_running.clone();
// Make any running loop exit first
thread_exit.store(true, Ordering::SeqCst);
break;
}
}
loop {
// wait for other threads to set not running so we know they exited
if !thread_running.load(Ordering::SeqCst) {
thread_exit.store(false, Ordering::SeqCst);
info!("AniMe forced a thread to exit");
break;
}
}
'main: loop {
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
for action in actions.iter() {
match action {
ActionData::Animation(frames) => {
let mut count = 0;
let start = Instant::now();
'animation: loop {
for frame in frames.frames() {
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(frame.frame().clone());
}
if let AnimTime::Time(time) = frames.duration() {
if Instant::now().duration_since(start) > time {
break 'animation;
}
}
sleep(frame.delay());
// Need to check for early exit condition here or it might run
// until end of gif or time
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
}
if let AnimTime::Cycles(times) = frames.duration() {
count += 1;
if count >= times {
break 'animation;
}
}
}
}
ActionData::Image(image) => {
once = false;
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(image.as_ref().clone())
}
}
ActionData::Pause(duration) => sleep(*duration),
ActionData::AudioEq => {}
ActionData::SystemInfo => {}
ActionData::TimeDate => {}
ActionData::Matrix => {}
}
}
if once || actions.is_empty() {
break 'main;
}
}
// Clear the display on exit
if let Ok(lock) = inner.try_lock() {
let data = AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec());
lock.write_data_buffer(data);
}
// Loop ended, set the atmonics
thread_exit.store(false, Ordering::SeqCst);
thread_running.store(false, Ordering::SeqCst);
info!("AniMe system thread exited");
})
.map(|err| info!("AniMe system thread: {:?}", err))
.ok();
}
fn write_bytes(&self, message: &[u8]) {
match self.handle.write_control(
0x21, // request_type
@@ -112,7 +219,16 @@ impl CtrlAnimeDisplay {
}
}
fn write_data_buffer(&self, buffer: AnimeDataBuffer) {
/// Write only a data packet. This will modify the leds brightness using the
/// global brightness set in config.
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) {
for led in buffer.get_mut()[7..].iter_mut() {
let mut bright = *led as f32 * self.config.brightness;
if bright > 254.0 {
bright = 254.0;
}
*led = bright as u8;
}
let data = AnimePacketType::from(buffer);
for row in data.iter() {
self.write_bytes(row);
@@ -126,3 +242,226 @@ impl CtrlAnimeDisplay {
self.write_bytes(&pkts[1]);
}
}
pub struct CtrlAnimeTask<'a> {
inner: Arc<Mutex<CtrlAnime>>,
_c: Connection,
manager: ManagerProxy<'a>,
}
impl<'a> CtrlAnimeTask<'a> {
pub fn new(inner: Arc<Mutex<CtrlAnime>>) -> Self {
let connection = Connection::new_system().unwrap();
let manager = ManagerProxy::new(&connection).unwrap();
let c1 = inner.clone();
// Run this action when the system starts shutting down
manager
.connect_prepare_for_shutdown(move |shutdown| {
if shutdown {
'outer: loop {
if let Ok(lock) = c1.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(c1.clone(), lock.cache.shutdown.clone(), false);
break 'outer;
}
}
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
let c1 = inner.clone();
// Run this action when the system wakes up from sleep
manager
.connect_prepare_for_sleep(move |sleep| {
if !sleep {
// wait a fraction for things to wake up properly
std::thread::sleep(Duration::from_millis(100));
'outer: loop {
if let Ok(lock) = c1.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(c1.clone(), lock.cache.wake.clone(), true);
break 'outer;
}
}
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
Self {
inner,
_c: connection,
manager,
}
}
}
impl<'a> crate::CtrlTask for CtrlAnimeTask<'a> {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(mut lock) = self.inner.try_lock() {
// Refresh the config and cache incase the user has edited it
let config = AnimeConfig::load();
lock.cache
.init_from_config(&config)
.map_err(|err| {
warn!("CtrlAnimeTask: do_task {}", err);
err
})
.ok();
}
// Check for signals on each task iteration, this will run the callbacks
// if any signal is recieved
self.manager.next_signal()?;
Ok(())
}
}
pub struct CtrlAnimeReloader(pub Arc<Mutex<CtrlAnime>>);
impl crate::Reloadable for CtrlAnimeReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_on(lock.config.awake_enabled));
lock.write_bytes(&pkt_for_apply());
lock.write_bytes(&pkt_for_set_boot(lock.config.boot_anim_enabled));
lock.write_bytes(&pkt_for_apply());
let action = lock.cache.boot.clone();
CtrlAnime::run_thread(self.0.clone(), action, true);
}
Ok(())
}
}
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
/// The struct with the main dbus methods requires this trait
impl crate::ZbusAdd for CtrlAnimeZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
// None of these calls can be guarnateed to succeed unless we loop until okay
// If the try_lock *does* succeed then any other thread trying to lock will not grab it
// until we finish.
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnimeZbus {
/// Writes a data stream of length. Will force system thread to exit until it is restarted
fn write(&self, input: AnimeDataBuffer) {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
lock.write_data_buffer(input);
break 'outer;
}
}
}
fn set_brightness(&self, bright: f32) {
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
let mut bright = bright;
if bright < 0.0 {
bright = 0.0
} else if bright > 254.0 {
bright = 254.0;
}
lock.config.brightness = bright;
lock.config.write();
break 'outer;
}
}
}
fn set_on_off(&self, status: bool) {
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_on(status));
lock.config.awake_enabled = status;
lock.config.write();
let states = AnimePowerStates {
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
break 'outer;
}
}
}
fn set_boot_on_off(&self, on: bool) {
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_boot(on));
lock.write_bytes(&pkt_for_apply());
lock.config.boot_anim_enabled = on;
lock.config.write();
let states = AnimePowerStates {
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
break 'outer;
}
}
}
/// The main loop is the base system set action if the user isn't running
/// the user daemon
fn run_main_loop(&self, start: bool) {
if start {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false);
break 'outer;
}
}
}
}
#[dbus_interface(property)]
fn awake_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.awake_enabled;
}
true
}
#[dbus_interface(property)]
fn boot_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.boot_anim_enabled;
}
true
}
#[dbus_interface(signal)]
fn notify_power_states(&self, data: &AnimePowerStates) -> zbus::Result<()>;
}

View File

@@ -1,394 +0,0 @@
use crate::error::RogError;
use crate::{config::Config, GetSupported};
use log::{info, warn};
use rog_types::{profile::{FanLevel, Profile, ProfileEvent}, supported::FanCpuSupportedFunctions};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::{dbus_interface, fdo::Error};
use zvariant::ObjectPath;
static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy";
static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode";
static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost";
pub struct CtrlFanAndCpu {
pub path: &'static str,
config: Arc<Mutex<Config>>,
}
impl GetSupported for CtrlFanAndCpu {
type A = FanCpuSupportedFunctions;
fn get_supported() -> Self::A {
FanCpuSupportedFunctions {
stock_fan_modes: CtrlFanAndCpu::get_fan_path().is_ok(),
min_max_freq: intel_pstate::PState::new().is_ok(),
fan_curve_set: rog_fan_curve::Board::from_board_name().is_some(),
}
}
}
pub struct DbusFanAndCpu {
inner: Arc<Mutex<CtrlFanAndCpu>>,
}
impl DbusFanAndCpu {
pub fn new(inner: Arc<Mutex<CtrlFanAndCpu>>) -> Self {
Self { inner }
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl DbusFanAndCpu {
/// Set profile details
fn set_profile(&self, profile: String) {
if let Ok(event) = serde_json::from_str(&profile) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
cfg.read();
ctrl.handle_profile_event(&event, &mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
if let Ok(json) = serde_json::to_string(profile) {
self.notify_profile(&json)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
}
}
/// Fetch the active profile name
fn next_profile(&mut self) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
cfg.read();
ctrl.do_next_profile(&mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
if let Ok(json) = serde_json::to_string(profile) {
self.notify_profile(&json)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
}
/// Fetch the active profile name
fn active_profile_name(&mut self) -> zbus::fdo::Result<String> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
return Ok(cfg.active_profile.clone());
}
}
Err(Error::Failed(
"Failed to get active profile name".to_string(),
))
}
// TODO: Profile can't implement Type because of Curve
/// Fetch the active profile details
fn profile(&mut self) -> zbus::fdo::Result<String> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
if let Ok(json) = serde_json::to_string_pretty(profile) {
return Ok(json);
}
}
}
}
Err(Error::Failed(
"Failed to get active profile details".to_string(),
))
}
/// Fetch all profile data
fn profiles(&mut self) -> zbus::fdo::Result<String> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
if let Ok(json) = serde_json::to_string_pretty(&cfg.power_profiles) {
return Ok(json);
}
}
}
Err(Error::Failed(
"Failed to get all profile details".to_string(),
))
}
fn profile_names(&self) -> zbus::fdo::Result<Vec<String>> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
let profile_names = cfg.power_profiles.keys().cloned().collect::<Vec<String>>();
return Ok(profile_names);
}
}
Err(Error::Failed("Failed to get all profile names".to_string()))
}
fn remove(&self, profile: &str) -> zbus::fdo::Result<()> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
if !cfg.power_profiles.contains_key(profile) {
return Err(Error::Failed("Invalid profile specified".to_string()));
}
if cfg.power_profiles.keys().len() == 1 {
return Err(Error::Failed("Cannot delete the last profile".to_string()));
}
if cfg.active_profile == *profile {
return Err(Error::Failed(
"Cannot delete the active profile".to_string(),
));
}
cfg.power_profiles.remove(profile);
cfg.write();
return Ok(());
}
}
Err(Error::Failed("Failed to lock configuration".to_string()))
}
#[dbus_interface(signal)]
fn notify_profile(&self, profile: &str) -> zbus::Result<()> {}
}
impl crate::ZbusAdd for DbusFanAndCpu {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Profile"),
self,
)
.map_err(|err| {
warn!("DbusFanAndCpu: add_to_server {}", err);
err
})
.ok();
}
}
impl crate::Reloadable for CtrlFanAndCpu {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(mut config) = self.config.clone().try_lock() {
let profile = config.active_profile.clone();
self.set(&profile, &mut config)?;
// info!(
// "Reloaded fan mode: {:?}",
// FanLevel::from(config.power_profile)
// );
}
Ok(())
}
}
impl CtrlFanAndCpu {
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
let path = CtrlFanAndCpu::get_fan_path()?;
info!("Device has thermal throttle control");
Ok(CtrlFanAndCpu { path, config })
}
fn get_fan_path() -> Result<&'static str, RogError> {
if Path::new(FAN_TYPE_1_PATH).exists() {
Ok(FAN_TYPE_1_PATH)
} else if Path::new(FAN_TYPE_2_PATH).exists() {
Ok(FAN_TYPE_2_PATH)
} else {
Err(RogError::MissingFunction(
"Fan mode not available, you may require a v5.8.10 series kernel or newer".into(),
))
}
}
/// Toggle to next profile in list
pub(super) fn do_next_profile(&mut self, config: &mut Config) -> Result<(), RogError> {
config.read();
let mut i = config
.toggle_profiles
.iter()
.position(|x| x == &config.active_profile)
.map(|i| i + 1)
.unwrap_or(0);
if i >= config.toggle_profiles.len() {
i = 0;
}
let new_profile = config
.toggle_profiles
.get(i)
.unwrap_or(&config.active_profile)
.clone();
self.set(&new_profile, config)?;
info!("Profile was changed: {}", &new_profile);
Ok(())
}
fn set_fan_mode(&mut self, preset: u8, config: &mut Config) -> Result<(), RogError> {
let mode = config.active_profile.clone();
let mut fan_ctrl = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
config.read();
let mut mode_config = config
.power_profiles
.get_mut(&mode)
.ok_or_else(|| RogError::MissingProfile(mode.clone()))?;
config.curr_fan_mode = preset;
mode_config.fan_preset = preset;
config.write();
fan_ctrl
.write_all(format!("{}\n", preset).as_bytes())
.map_err(|err| RogError::Write(self.path.into(), err))?;
info!("Fan mode set to: {:?}", FanLevel::from(preset));
Ok(())
}
fn handle_profile_event(
&mut self,
event: &ProfileEvent,
config: &mut Config,
) -> Result<(), RogError> {
match event {
ProfileEvent::Toggle => self.do_next_profile(config)?,
ProfileEvent::ChangeMode(mode) => {
self.set_fan_mode(*mode, config)?;
let mode = config.active_profile.clone();
self.set_pstate_for_fan_mode(&mode, config)?;
self.set_fan_curve_for_fan_mode(&mode, config)?;
}
ProfileEvent::Cli(command) => {
let profile_key = match command.profile.as_ref() {
Some(k) => k.clone(),
None => config.active_profile.clone(),
};
let mut profile = if command.create {
config
.power_profiles
.entry(profile_key.clone())
.or_insert_with(Profile::default)
} else {
config
.power_profiles
.get_mut(&profile_key)
.ok_or_else(|| RogError::MissingProfile(profile_key.clone()))?
};
if command.turbo.is_some() {
profile.turbo = command.turbo.unwrap();
}
if let Some(min_perc) = command.min_percentage {
profile.min_percentage = min_perc;
}
if let Some(max_perc) = command.max_percentage {
profile.max_percentage = max_perc;
}
if let Some(ref preset) = command.fan_preset {
profile.fan_preset = preset.into();
}
if let Some(ref curve) = command.curve {
profile.fan_curve = Some(curve.clone());
}
self.set(&profile_key, config)?;
}
}
Ok(())
}
fn set(&mut self, profile: &str, config: &mut Config) -> Result<(), RogError> {
let mode_config = config
.power_profiles
.get(profile)
.ok_or_else(|| RogError::MissingProfile(profile.into()))?;
let mut fan_ctrl = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| RogError::Path(self.path.into(), err))?;
config.curr_fan_mode = mode_config.fan_preset;
fan_ctrl
.write_all(format!("{}\n", mode_config.fan_preset).as_bytes())
.map_err(|err| RogError::Write(self.path.into(), err))?;
self.set_pstate_for_fan_mode(profile, config)?;
self.set_fan_curve_for_fan_mode(profile, config)?;
config.active_profile = profile.into();
config.write();
Ok(())
}
fn set_pstate_for_fan_mode(&self, mode: &str, config: &mut Config) -> Result<(), RogError> {
info!("Setting pstate");
let mode_config = config
.power_profiles
.get(mode)
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
// Set CPU pstate
if let Ok(pstate) = intel_pstate::PState::new() {
pstate.set_min_perf_pct(mode_config.min_percentage)?;
pstate.set_max_perf_pct(mode_config.max_percentage)?;
pstate.set_no_turbo(!mode_config.turbo)?;
info!(
"Intel CPU Power: min: {}%, max: {}%, turbo: {}",
mode_config.min_percentage, mode_config.max_percentage, mode_config.turbo
);
} else {
info!("Setting pstate for AMD CPU");
// must be AMD CPU
let mut file = OpenOptions::new()
.write(true)
.open(AMD_BOOST_PATH)
.map_err(|err| RogError::Path(self.path.into(), err))?;
let boost = if mode_config.turbo { "1" } else { "0" }; // opposite of Intel
file.write_all(boost.as_bytes())
.map_err(|err| RogError::Write(AMD_BOOST_PATH.into(), err))?;
info!("AMD CPU Turbo: {}", boost);
}
Ok(())
}
fn set_fan_curve_for_fan_mode(&self, mode: &str, config: &Config) -> Result<(), RogError> {
let mode_config = &config
.power_profiles
.get(mode)
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
if let Some(ref curve) = mode_config.fan_curve {
use rog_fan_curve::{Board, Fan};
if let Some(board) = Board::from_board_name() {
curve.apply(board, Fan::Cpu)?;
curve.apply(board, Fan::Gpu)?;
} else {
warn!("Fan curve unsupported on this board.")
}
}
Ok(())
}
}

View File

@@ -1,3 +1,4 @@
use ::zbus::Connection;
use ctrl_gfx::error::GfxError;
use ctrl_gfx::*;
use ctrl_rog_bios::CtrlRogBios;
@@ -14,8 +15,6 @@ use std::{str::FromStr, sync::mpsc};
use std::{sync::Arc, sync::Mutex};
use sysfs_class::{PciDevice, SysClass};
use system::{GraphicsDevice, PciBus};
use zbus::{dbus_interface, Connection};
use zvariant::ObjectPath;
use crate::*;
@@ -32,62 +31,6 @@ pub struct CtrlGraphics {
thread_kill: Arc<Mutex<Option<mpsc::Sender<bool>>>>,
}
trait Dbus {
fn vendor(&self) -> zbus::fdo::Result<GfxVendors>;
fn power(&self) -> zbus::fdo::Result<GfxPower>;
fn set_vendor(&mut self, vendor: GfxVendors) -> zbus::fdo::Result<GfxRequiredUserAction>;
fn notify_gfx(&self, vendor: &GfxVendors) -> zbus::Result<()>;
fn notify_action(&self, action: &GfxRequiredUserAction) -> zbus::Result<()>;
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl Dbus for CtrlGraphics {
fn vendor(&self) -> zbus::fdo::Result<GfxVendors> {
self.get_gfx_mode().map_err(|err| {
error!("GFX: {}", err);
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
})
}
fn power(&self) -> zbus::fdo::Result<GfxPower> {
Self::get_runtime_status().map_err(|err| {
error!("GFX: {}", err);
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
})
}
fn set_vendor(&mut self, vendor: GfxVendors) -> zbus::fdo::Result<GfxRequiredUserAction> {
info!("GFX: Switching gfx mode to {}", <&str>::from(vendor));
let msg = self.set_gfx_config(vendor).map_err(|err| {
error!("GFX: {}", err);
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
})?;
self.notify_gfx(&vendor)
.unwrap_or_else(|err| warn!("GFX: {}", err));
self.notify_action(&msg)
.unwrap_or_else(|err| warn!("GFX: {}", err));
Ok(msg)
}
#[dbus_interface(signal)]
fn notify_gfx(&self, vendor: &GfxVendors) -> zbus::Result<()> {}
#[dbus_interface(signal)]
fn notify_action(&self, action: &GfxRequiredUserAction) -> zbus::Result<()> {}
}
impl ZbusAdd for CtrlGraphics {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Gfx"), self)
.map_err(|err| {
warn!("GFX: CtrlGraphics: add_to_server {}", err);
err
})
.ok();
}
}
impl Reloadable for CtrlGraphics {
fn reload(&mut self) -> Result<(), RogError> {
self.auto_power()?;
@@ -176,13 +119,16 @@ impl CtrlGraphics {
/// Associated method to get which vendor mode is set
pub fn get_gfx_mode(&self) -> Result<GfxVendors, RogError> {
if let Ok(config) = self.config.lock() {
if let Some(mode) = config.gfx_tmp_mode {
return Ok(mode);
}
return Ok(config.gfx_mode);
}
// TODO: Error here
Ok(GfxVendors::Hybrid)
}
fn get_runtime_status() -> Result<GfxPower, RogError> {
pub(super) fn get_runtime_status() -> Result<GfxPower, RogError> {
let path = Path::new("/sys/bus/pci/devices/0000:01:00.0/power/runtime_status");
if path.exists() {
let buf = std::fs::read_to_string(path).map_err(|err| {
@@ -281,10 +227,14 @@ impl CtrlGraphics {
fn write_modprobe_conf(vendor: GfxVendors, devices: &[GraphicsDevice]) -> Result<(), RogError> {
info!("GFX: Writing {}", MODPROBE_PATH);
let content = match vendor {
GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Compute => MODPROBE_BASE.to_vec(),
GfxVendors::Nvidia | GfxVendors::Hybrid => {
let mut base = MODPROBE_BASE.to_vec();
base.append(&mut MODPROBE_DRM_MODESET.to_vec());
base
}
GfxVendors::Vfio => Self::get_vfio_conf(devices),
// GfxVendors::Compute => {}
GfxVendors::Integrated => MODPROBE_INTEGRATED.to_vec(),
GfxVendors::Compute => MODPROBE_BASE.to_vec(),
};
let mut file = std::fs::OpenOptions::new()
@@ -418,9 +368,13 @@ impl CtrlGraphics {
fn logout_required(&self, vendor: GfxVendors) -> GfxRequiredUserAction {
if let Ok(config) = self.config.lock() {
let current = config.gfx_mode;
if matches!(current, GfxVendors::Integrated | GfxVendors::Vfio)
&& matches!(vendor, GfxVendors::Integrated | GfxVendors::Vfio)
{
if matches!(
current,
GfxVendors::Integrated | GfxVendors::Vfio | GfxVendors::Compute
) && matches!(
vendor,
GfxVendors::Integrated | GfxVendors::Vfio | GfxVendors::Compute
) {
return GfxRequiredUserAction::None;
}
}
@@ -559,7 +513,11 @@ impl CtrlGraphics {
Self::do_display_manager_action("stop")?;
Self::wait_display_manager_state("inactive")?;
let vfio_enable = if let Ok(config) = config.lock() {
let vfio_enable = if let Ok(mut config) = config.try_lock() {
// Since we have a lock, reset tmp to none. This thread should only ever run
// for Integrated, Hybrid, or Nvidia. Tmp is also only for informational
config.gfx_tmp_mode = None;
//
config.gfx_vfio_enable
} else {
false
@@ -630,7 +588,7 @@ impl CtrlGraphics {
}
}
let vfio_enable = if let Ok(config) = self.config.lock() {
let vfio_enable = if let Ok(config) = self.config.try_lock() {
config.gfx_vfio_enable
} else {
false
@@ -655,6 +613,11 @@ impl CtrlGraphics {
let bus = self.bus.clone();
Self::do_vendor_tasks(vendor, vfio_enable, &devices, &bus)?;
info!("GFX: Graphics mode changed to {}", <&str>::from(vendor));
if matches!(vendor, GfxVendors::Vfio | GfxVendors::Compute) {
if let Ok(mut config) = self.config.try_lock() {
config.gfx_tmp_mode = Some(vendor);
};
}
}
// TODO: undo if failed? Save last mode, catch errors...
Ok(action_required)
@@ -666,7 +629,7 @@ impl CtrlGraphics {
let devices = self.nvidia.clone();
let bus = self.bus.clone();
let vfio_enable = if let Ok(config) = self.config.lock() {
let vfio_enable = if let Ok(config) = self.config.try_lock() {
config.gfx_vfio_enable
} else {
false

View File

@@ -1,9 +1,11 @@
pub mod error;
pub mod gfx;
pub mod controller;
pub mod system;
pub mod zbus_gfx;
const NVIDIA_DRIVERS: [&str; 4] = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"];
const VFIO_DRIVERS: [&str; 5] = [
@@ -26,6 +28,9 @@ static MODPROBE_BASE: &[u8] = br#"# Automatically generated by asusd
blacklist nouveau
alias nouveau off
options nvidia NVreg_DynamicPowerManagement=0x02
"#;
static MODPROBE_DRM_MODESET: &[u8] = br#"
options nvidia-drm modeset=1
"#;

View File

@@ -0,0 +1,56 @@
use ::zbus::dbus_interface;
use log::{error, info, warn};
use rog_types::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors};
use zvariant::ObjectPath;
use crate::ZbusAdd;
use super::controller::CtrlGraphics;
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlGraphics {
fn vendor(&self) -> zbus::fdo::Result<GfxVendors> {
self.get_gfx_mode().map_err(|err| {
error!("GFX: {}", err);
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
})
}
fn power(&self) -> zbus::fdo::Result<GfxPower> {
Self::get_runtime_status().map_err(|err| {
error!("GFX: {}", err);
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
})
}
fn set_vendor(&mut self, vendor: GfxVendors) -> zbus::fdo::Result<GfxRequiredUserAction> {
info!("GFX: Switching gfx mode to {}", <&str>::from(vendor));
let msg = self.set_gfx_config(vendor).map_err(|err| {
error!("GFX: {}", err);
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
})?;
self.notify_gfx(&vendor)
.unwrap_or_else(|err| warn!("GFX: {}", err));
self.notify_action(&msg)
.unwrap_or_else(|err| warn!("GFX: {}", err));
Ok(msg)
}
#[dbus_interface(signal)]
fn notify_gfx(&self, vendor: &GfxVendors) -> zbus::Result<()> {}
#[dbus_interface(signal)]
fn notify_action(&self, action: &GfxRequiredUserAction) -> zbus::Result<()> {}
}
impl ZbusAdd for CtrlGraphics {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Gfx"), self)
.map_err(|err| {
warn!("GFX: CtrlGraphics: add_to_server {}", err);
err
})
.ok();
}
}

View File

@@ -1,7 +1,4 @@
// Only these two packets must be 17 bytes
static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
use crate::{
@@ -9,19 +6,24 @@ use crate::{
error::RogError,
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
};
use log::{error, info, warn};
use rog_types::{LED_MSG_LEN, aura_modes::{AuraEffect, AuraModeNum, LedBrightness}, supported::LedSupportedFunctions};
use log::{info, warn};
use rog_aura::{
usb::{
LED_APPLY, LED_AWAKE_OFF_SLEEP_OFF, LED_AWAKE_OFF_SLEEP_ON, LED_AWAKE_ON_SLEEP_OFF,
LED_AWAKE_ON_SLEEP_ON, LED_SET,
},
AuraEffect, LedBrightness, LED_MSG_LEN,
};
use rog_types::supported::LedSupportedFunctions;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::dbus_interface;
use zvariant::ObjectPath;
use crate::GetSupported;
impl GetSupported for CtrlKbdBacklight {
impl GetSupported for CtrlKbdLed {
type A = LedSupportedFunctions;
fn get_supported() -> Self::A {
@@ -29,14 +31,10 @@ impl GetSupported for CtrlKbdBacklight {
let multizone_led_mode = false;
let per_key_led_mode = false;
let laptop = LaptopLedData::get_data();
let stock_led_modes = if laptop.standard.is_empty() {
None
} else {
Some(laptop.standard)
};
let stock_led_modes = laptop.standard;
LedSupportedFunctions {
brightness_set: CtrlKbdBacklight::get_kbd_bright_path().is_some(),
brightness_set: CtrlKbdLed::get_kbd_bright_path().is_some(),
stock_led_modes,
multizone_led_mode,
per_key_led_mode,
@@ -44,198 +42,72 @@ impl GetSupported for CtrlKbdBacklight {
}
}
pub struct CtrlKbdBacklight {
led_node: Option<String>,
pub struct CtrlKbdLed {
pub led_node: Option<String>,
pub bright_node: String,
supported_modes: LaptopLedData,
flip_effect_write: bool,
config: AuraConfig,
pub supported_modes: LaptopLedData,
pub flip_effect_write: bool,
pub config: AuraConfig,
}
pub struct DbusKbdBacklight {
inner: Arc<Mutex<CtrlKbdBacklight>>,
}
pub struct CtrlKbdLedTask(pub Arc<Mutex<CtrlKbdLed>>);
impl DbusKbdBacklight {
pub fn new(inner: Arc<Mutex<CtrlKbdBacklight>>) -> Self {
Self { inner }
}
}
impl crate::ZbusAdd for DbusKbdBacklight {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Led"), self)
.map_err(|err| {
error!("DbusKbdBacklight: add_to_server {}", err);
})
.ok();
}
}
/// The main interface for changing, reading, or notfying signals
///
/// LED commands are split between Brightness, Modes, Per-Key
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl DbusKbdBacklight {
fn set_brightness(&mut self, brightness: LedBrightness) {
if let Ok(ctrl) = self.inner.try_lock() {
ctrl.set_brightness(brightness)
.map_err(|err| warn!("{}", err))
.ok();
}
}
fn set_led_mode(&mut self, effect: AuraEffect) {
if let Ok(mut ctrl) = self.inner.try_lock() {
let mode_name = effect.mode_name();
match ctrl.do_command(effect) {
Ok(_) => {
self.notify_led(&mode_name).ok();
}
Err(err) => {
warn!("{}", err);
impl crate::CtrlTask for CtrlKbdLedTask {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(mut lock) = self.0.try_lock() {
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(());
}
return Err(RogError::ParseLed);
}
}
fn next_led_mode(&self) {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.toggle_mode(false)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
self.notify_led(&json)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
fn prev_led_mode(&self) {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.toggle_mode(true)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
self.notify_led(&json)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
/// Return the current mode data
#[dbus_interface(property)]
fn led_mode(&self) -> String {
if let Ok(ctrl) = self.inner.try_lock() {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
return json;
}
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not deserialise".to_string()
}
/// Return a list of available modes
#[dbus_interface(property)]
fn led_modes(&self) -> String {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
return json;
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not serialise".to_string()
}
/// Return the current LED brightness
#[dbus_interface(property)]
fn led_brightness(&self) -> i8 {
if let Ok(ctrl) = self.inner.try_lock() {
return ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1);
}
warn!("SetKeyBacklight could not serialise");
-1
}
#[dbus_interface(signal)]
fn notify_led(&self, data: &str) -> zbus::Result<()>;
}
impl crate::Reloadable for CtrlKbdBacklight {
fn reload(&mut self) -> Result<(), RogError> {
// set current mode (if any)
if self.supported_modes.standard.len() > 1 {
let current_mode = self.config.current_mode;
if self.supported_modes.standard.contains(&(current_mode)) {
let mode = self
.config
.builtins
.get(&current_mode)
.ok_or(RogError::NotSupported)?
.to_owned();
self.write_mode(&mode)?;
info!("Reloaded last used mode");
} else {
warn!(
"An unsupported mode was set: {}, reset to first mode available",
<&str>::from(&self.config.current_mode)
);
self.config.builtins.remove(&current_mode);
self.config.current_mode = AuraModeNum::Static;
// TODO: do a recursive call with a boxed dyn future later
let mode = self
.config
.builtins
.get(&current_mode)
.ok_or(RogError::NotSupported)?
.to_owned();
self.write_mode(&mode)?;
info!("Reloaded last used mode");
}
}
// Reload brightness
let bright = self.config.brightness;
self.set_brightness(bright)?;
info!("Reloaded last used brightness");
Ok(())
}
}
impl crate::CtrlTask for CtrlKbdBacklight {
fn do_task(&mut self) -> Result<(), RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(&self.bright_node)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
}
_ => RogError::Path((&self.bright_node).into(), err),
})?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)
.map_err(|err| RogError::Read("buffer".into(), err))?;
if let Some(num) = char::from(buf[0]).to_digit(10) {
if self.config.brightness != num.into() {
self.config.read();
self.config.brightness = num.into();
self.config.write();
pub struct CtrlKbdLedReloader(pub Arc<Mutex<CtrlKbdLed>>);
impl crate::Reloadable for CtrlKbdLedReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(mut ctrl) = self.0.try_lock() {
let current = ctrl.config.current_mode;
if let Some(mode) = ctrl.config.builtins.get(&current).cloned() {
ctrl.do_command(mode).ok();
}
return Ok(());
ctrl.set_states_enabled(ctrl.config.awake_enabled, ctrl.config.sleep_anim_enabled)
.map_err(|err| warn!("{}", err))
.ok();
}
Err(RogError::ParseLed)
Ok(())
}
}
impl CtrlKbdBacklight {
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
@@ -265,7 +137,7 @@ impl CtrlKbdBacklight {
));
}
let ctrl = CtrlKbdBacklight {
let ctrl = CtrlKbdLed {
led_node,
bright_node: bright_node.unwrap(), // If was none then we already returned above
supported_modes,
@@ -282,7 +154,7 @@ impl CtrlKbdBacklight {
None
}
pub fn get_brightness(&self) -> Result<u8, RogError> {
pub(super) fn get_brightness(&self) -> Result<u8, RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(&self.bright_node)
@@ -298,7 +170,7 @@ impl CtrlKbdBacklight {
Ok(buf[0])
}
pub fn set_brightness(&self, brightness: LedBrightness) -> Result<(), RogError> {
pub(super) fn set_brightness(&self, brightness: LedBrightness) -> Result<(), RogError> {
let path = Path::new(&self.bright_node);
let mut file =
OpenOptions::new()
@@ -315,6 +187,26 @@ impl CtrlKbdBacklight {
Ok(())
}
/// Set if awake/on LED active, and/or sleep animation active
pub(super) fn set_states_enabled(&self, awake: bool, sleep: bool) -> Result<(), RogError> {
let bytes = if awake && sleep {
LED_AWAKE_ON_SLEEP_ON
} else if awake && !sleep {
LED_AWAKE_ON_SLEEP_OFF
} else if !awake && sleep {
LED_AWAKE_OFF_SLEEP_ON
} else if !awake && !sleep {
LED_AWAKE_OFF_SLEEP_OFF
} else {
LED_AWAKE_ON_SLEEP_ON
};
self.write_bytes(&bytes)?;
self.write_bytes(&LED_SET)?;
// Changes won't persist unless apply is set
self.write_bytes(&LED_APPLY)?;
Ok(())
}
fn find_led_node(id_product: &str) -> Result<String, RogError> {
let mut enumerator = udev::Enumerator::new().map_err(|err| {
warn!("{}", err);
@@ -373,7 +265,7 @@ impl CtrlKbdBacklight {
/// Write an effect block
#[inline]
fn write_effect(&mut self, effect: &[Vec<u8>]) -> Result<(), RogError> {
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)?;
@@ -401,7 +293,7 @@ impl CtrlKbdBacklight {
}
#[inline]
fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
pub(super) fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
let current = self.config.current_mode;
if let Some(idx) = self
.supported_modes

View File

@@ -0,0 +1,2 @@
pub mod controller;
pub mod zbus;

View File

@@ -0,0 +1,165 @@
use log::{error, warn};
use rog_aura::{AuraEffect, LedBrightness, LedPowerStates};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use super::controller::CtrlKbdLedZbus;
impl crate::ZbusAdd for CtrlKbdLedZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Led"), self)
.map_err(|err| {
error!("DbusKbdLed: add_to_server {}", err);
})
.ok();
}
}
/// The main interface for changing, reading, or notfying signals
///
/// LED commands are split between Brightness, Modes, Per-Key
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlKbdLedZbus {
/// Set the keyboard brightness level (0-3)
fn set_brightness(&mut self, brightness: LedBrightness) {
if let Ok(ctrl) = self.0.try_lock() {
ctrl.set_brightness(brightness)
.map_err(|err| warn!("{}", err))
.ok();
}
}
/// Set the keyboard LED to enabled while the device is awake
fn set_awake_enabled(&mut self, enabled: bool) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.set_states_enabled(enabled, ctrl.config.sleep_anim_enabled)
.map_err(|err| warn!("{}", err))
.ok();
ctrl.config.awake_enabled = enabled;
ctrl.config.write();
let states = LedPowerStates {
enabled: ctrl.config.awake_enabled,
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
}
}
/// Set the keyboard LED suspend animation to enabled while the device is suspended
fn set_sleep_enabled(&mut self, enabled: bool) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.set_states_enabled(ctrl.config.awake_enabled, enabled)
.map_err(|err| warn!("{}", err))
.ok();
ctrl.config.sleep_anim_enabled = enabled;
ctrl.config.write();
let states = LedPowerStates {
enabled: ctrl.config.awake_enabled,
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
}
}
fn set_led_mode(&mut self, effect: AuraEffect) {
if let Ok(mut ctrl) = self.0.try_lock() {
match ctrl.do_command(effect) {
Ok(_) => {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
Err(err) => {
warn!("{}", err);
}
}
}
}
fn next_led_mode(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.toggle_mode(false)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
fn prev_led_mode(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.toggle_mode(true)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
#[dbus_interface(property)]
fn awake_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.awake_enabled;
}
true
}
#[dbus_interface(property)]
fn sleep_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.sleep_anim_enabled;
}
true
}
/// Return the current mode data
#[dbus_interface(property)]
fn led_mode(&self) -> String {
if let Ok(ctrl) = self.0.try_lock() {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
return json;
}
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not deserialise".to_string()
}
/// Return a list of available modes
#[dbus_interface(property)]
fn led_modes(&self) -> String {
if let Ok(ctrl) = self.0.try_lock() {
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
return json;
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not serialise".to_string()
}
/// Return the current LED brightness
#[dbus_interface(property)]
fn led_brightness(&self) -> i8 {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1);
}
warn!("SetKeyBacklight could not serialise");
-1
}
#[dbus_interface(signal)]
fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>;
#[dbus_interface(signal)]
fn notify_power_states(&self, data: &LedPowerStates) -> zbus::Result<()>;
}

View File

@@ -0,0 +1,100 @@
use crate::error::RogError;
use crate::{config::Config, GetSupported};
use log::info;
use rog_profiles::profiles::Profile;
use rog_types::supported::FanCpuSupportedFunctions;
use std::sync::Arc;
use std::sync::Mutex;
pub struct CtrlFanAndCpu {
pub config: Arc<Mutex<Config>>,
}
impl GetSupported for CtrlFanAndCpu {
type A = FanCpuSupportedFunctions;
fn get_supported() -> Self::A {
FanCpuSupportedFunctions {
stock_fan_modes: Profile::get_fan_path().is_ok(),
min_max_freq: Profile::get_intel_supported(),
fan_curve_set: rog_fan_curve::Board::from_board_name().is_some(),
}
}
}
impl crate::Reloadable for CtrlFanAndCpu {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(mut cfg) = self.config.clone().try_lock() {
let active = cfg.active_profile.clone();
if let Some(existing) = cfg.power_profiles.get_mut(&active) {
existing.set_system_all()?;
cfg.write();
}
}
Ok(())
}
}
impl CtrlFanAndCpu {
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
Profile::get_fan_path()?;
info!("Device has thermal throttle control");
Ok(CtrlFanAndCpu { config })
}
/// Toggle to next profile in list
pub(super) fn do_next_profile(&mut self, config: &mut Config) -> Result<(), RogError> {
config.read();
let mut i = config
.toggle_profiles
.iter()
.position(|x| x == &config.active_profile)
.map(|i| i + 1)
.unwrap_or(0);
if i >= config.toggle_profiles.len() {
i = 0;
}
let new_profile = config
.toggle_profiles
.get(i)
.unwrap_or(&config.active_profile)
.clone();
self.set_active(&new_profile)?;
info!("Profile was changed: {}", &new_profile);
Ok(())
}
pub(super) fn set_active(&mut self, profile: &str) -> Result<(), RogError> {
if let Ok(mut cfg) = self.config.clone().try_lock() {
cfg.read();
if let Some(existing) = cfg.power_profiles.get_mut(profile) {
existing.set_system_all()?;
cfg.active_profile = existing.name.clone();
cfg.write();
}
}
Ok(())
}
pub(super) fn new_or_modify(&mut self, profile: &Profile) -> Result<(), RogError> {
if let Ok(mut cfg) = self.config.clone().try_lock() {
cfg.read();
if let Some(existing) = cfg.power_profiles.get_mut(&profile.name) {
*existing = profile.clone();
existing.set_system_all()?;
} else {
cfg.power_profiles
.insert(profile.name.clone(), profile.clone());
}
cfg.active_profile = profile.name.clone();
cfg.write();
}
Ok(())
}
}

View File

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

View File

@@ -0,0 +1,170 @@
use log::warn;
use rog_profiles::profiles::Profile;
use std::sync::Arc;
use std::sync::Mutex;
use zbus::{dbus_interface, fdo::Error};
use zvariant::ObjectPath;
use super::controller::CtrlFanAndCpu;
pub struct FanAndCpuZbus {
inner: Arc<Mutex<CtrlFanAndCpu>>,
}
impl FanAndCpuZbus {
pub fn new(inner: Arc<Mutex<CtrlFanAndCpu>>) -> Self {
Self { inner }
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl FanAndCpuZbus {
/// Create new profile and make active
fn set_profile(&self, profile: String) {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.set_active(&profile)
.unwrap_or_else(|err| warn!("{}", err));
// Do notification
if let Ok(cfg) = ctrl.config.clone().try_lock() {
// Do notify
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
self.notify_profile(&profile)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
/// New or modify profile details and make active, will create if it does not exist
fn new_or_modify(&self, profile: Profile) {
if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.new_or_modify(&profile)
.unwrap_or_else(|err| warn!("{}", err));
// Do notification
if let Ok(cfg) = ctrl.config.clone().try_lock() {
// Do notify
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
self.notify_profile(&profile)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
/// Fetch the active profile name
fn next_profile(&mut self) {
if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
cfg.read();
ctrl.do_next_profile(&mut cfg)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
self.notify_profile(&profile)
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
}
/// Fetch the active profile name
fn active_name(&mut self) -> zbus::fdo::Result<String> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
return Ok(cfg.active_profile.clone());
}
}
Err(Error::Failed(
"Failed to get active profile name".to_string(),
))
}
// TODO: Profile can't implement Type because of Curve
/// Fetch the active profile details
fn active_data(&mut self) -> zbus::fdo::Result<Profile> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
return Ok(profile.clone());
}
}
}
Err(Error::Failed(
"Failed to get active profile details".to_string(),
))
}
/// Fetch all profile data
fn profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
return Ok(cfg.power_profiles.values().cloned().collect());
}
}
Err(Error::Failed(
"Failed to get all profile details".to_string(),
))
}
fn profile_names(&self) -> zbus::fdo::Result<Vec<String>> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
let profile_names = cfg.power_profiles.keys().cloned().collect::<Vec<String>>();
return Ok(profile_names);
}
}
Err(Error::Failed("Failed to get all profile names".to_string()))
}
fn remove(&self, profile: &str) -> zbus::fdo::Result<()> {
if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() {
cfg.read();
if !cfg.power_profiles.contains_key(profile) {
return Err(Error::Failed("Invalid profile specified".to_string()));
}
if cfg.power_profiles.keys().len() == 1 {
return Err(Error::Failed("Cannot delete the last profile".to_string()));
}
if cfg.active_profile == *profile {
return Err(Error::Failed(
"Cannot delete the active profile".to_string(),
));
}
cfg.power_profiles.remove(profile);
cfg.write();
return Ok(());
}
}
Err(Error::Failed("Failed to lock configuration".to_string()))
}
#[dbus_interface(signal)]
fn notify_profile(&self, profile: &Profile) -> zbus::Result<()> {}
}
impl crate::ZbusAdd for FanAndCpuZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Profile"),
self,
)
.map_err(|err| {
warn!("DbusFanAndCpu: add_to_server {}", err);
err
})
.ok();
}
}

View File

@@ -309,11 +309,9 @@ impl CtrlRogBios {
let mut buf = Vec::new();
// remove modules
for line in std::io::BufReader::new(file).lines() {
if let Ok(l) = line {
if !modules.contains(&l.as_ref()) {
buf.append(&mut l.as_bytes().to_vec());
}
for line in std::io::BufReader::new(file).lines().flatten() {
if !modules.contains(&line.as_str()) {
buf.append(&mut line.as_bytes().to_vec());
}
}

View File

@@ -2,10 +2,11 @@ use log::warn;
use serde_derive::{Deserialize, Serialize};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use zvariant_derive::Type;
use crate::{
ctrl_anime::CtrlAnimeDisplay, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCpu,
ctrl_leds::CtrlKbdBacklight, ctrl_rog_bios::CtrlRogBios, GetSupported,
ctrl_anime::CtrlAnime, ctrl_charge::CtrlCharge, ctrl_leds::controller::CtrlKbdLed,
ctrl_profiles::controller::CtrlFanAndCpu, ctrl_rog_bios::CtrlRogBios, GetSupported,
};
use rog_types::supported::{
@@ -13,7 +14,7 @@ use rog_types::supported::{
LedSupportedFunctions, RogBiosSupportedFunctions,
};
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Type)]
pub struct SupportedFunctions {
pub anime_ctrl: AnimeSupportedFunctions,
pub charge_ctrl: ChargeSupportedFunctions,
@@ -24,8 +25,8 @@ pub struct SupportedFunctions {
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl SupportedFunctions {
fn supported_functions(&self) -> String {
serde_json::to_string_pretty(self).unwrap()
fn supported_functions(&self) -> &SupportedFunctions {
self
}
}
@@ -49,8 +50,8 @@ impl GetSupported for SupportedFunctions {
fn get_supported() -> Self::A {
SupportedFunctions {
keyboard_led: CtrlKbdBacklight::get_supported(),
anime_ctrl: CtrlAnimeDisplay::get_supported(),
anime_ctrl: CtrlAnime::get_supported(),
keyboard_led: CtrlKbdLed::get_supported(),
charge_ctrl: CtrlCharge::get_supported(),
fan_cpu_ctrl: CtrlFanAndCpu::get_supported(),
rog_bios_ctrl: CtrlRogBios::get_supported(),

View File

@@ -1,11 +1,13 @@
use daemon::ctrl_leds::{CtrlKbdBacklight, DbusKbdBacklight};
use daemon::ctrl_leds::controller::{
CtrlKbdLed, CtrlKbdLedReloader, CtrlKbdLedTask, CtrlKbdLedZbus,
};
use daemon::{
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
};
use daemon::{config_aura::AuraConfig, ctrl_charge::CtrlCharge};
use daemon::{ctrl_anime::CtrlAnimeDisplay, ctrl_gfx::gfx::CtrlGraphics};
use daemon::{config_anime::AnimeConfig, config_aura::AuraConfig, ctrl_charge::CtrlCharge};
use daemon::{ctrl_anime::*, ctrl_gfx::controller::CtrlGraphics};
use daemon::{
ctrl_fan_cpu::{CtrlFanAndCpu, DbusFanAndCpu},
ctrl_profiles::{controller::CtrlFanAndCpu, zbus::FanAndCpuZbus},
laptops::LaptopLedData,
};
@@ -33,35 +35,35 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
.filter(None, LevelFilter::Info)
.init();
info!(" daemon v{}", daemon::VERSION);
info!(" rog-dbus v{}", rog_dbus::VERSION);
info!("rog-types v{}", rog_types::VERSION);
info!(" daemon v{}", daemon::VERSION);
info!(" rog-anime v{}", rog_anime::VERSION);
info!(" rog-aura v{}", rog_aura::VERSION);
info!(" rog-dbus v{}", rog_dbus::VERSION);
info!("rog-profiles v{}", rog_profiles::VERSION);
info!(" rog-types v{}", rog_types::VERSION);
start_daemon()?;
Ok(())
}
// Timing is such that:
// - interrupt write is minimum 1ms (sometimes lower)
// - read interrupt must timeout, minimum of 1ms
// - for a single usb packet, 2ms total.
// - to maintain constant times of 1ms, per-key colours should use
// the effect endpoint so that the complete colour block is written
// as fast as 1ms per row of the matrix inside it. (10ms total time)
/// The actual main loop for the daemon
fn start_daemon() -> Result<(), Box<dyn Error>> {
let supported = SupportedFunctions::get_supported();
print_board_info();
println!("{}", serde_json::to_string_pretty(&supported).unwrap());
let config = Config::load();
let enable_gfx_switching = config.gfx_managed;
let config = Arc::new(Mutex::new(config));
// Collect tasks for task thread
let mut tasks: Vec<Box<dyn CtrlTask + Send>> = Vec::new();
// Start zbus server
let connection = Connection::new_system()?;
fdo::DBusProxy::new(&connection)?
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
let mut object_server = zbus::ObjectServer::new(&connection);
let config = Config::load();
let enable_gfx_switching = config.gfx_managed;
let config = Arc::new(Mutex::new(config));
supported.add_to_server(&mut object_server);
match CtrlRogBios::new(config.clone()) {
@@ -90,23 +92,66 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
}
}
match CtrlAnimeDisplay::new() {
match CtrlFanAndCpu::new(config.clone()) {
Ok(mut ctrl) => {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
FanAndCpuZbus::new(tmp).add_to_server(&mut object_server);
}
Err(err) => {
error!("Profile control: {}", err);
}
}
match CtrlAnime::new(AnimeConfig::load()) {
Ok(ctrl) => {
ctrl.add_to_server(&mut object_server);
let inner = Arc::new(Mutex::new(ctrl));
let mut reload = CtrlAnimeReloader(inner.clone());
reload
.reload()
.unwrap_or_else(|err| warn!("AniMe: {}", err));
let zbus = CtrlAnimeZbus(inner.clone());
zbus.add_to_server(&mut object_server);
tasks.push(Box::new(CtrlAnimeTask::new(inner)));
}
Err(err) => {
error!("AniMe control: {}", err);
}
}
let laptop = LaptopLedData::get_data();
let aura_config = AuraConfig::load(&laptop);
match CtrlKbdLed::new(laptop, aura_config) {
Ok(ctrl) => {
let inner = Arc::new(Mutex::new(ctrl));
let mut reload = CtrlKbdLedReloader(inner.clone());
reload
.reload()
.unwrap_or_else(|err| warn!("Keyboard LED control: {}", err));
CtrlKbdLedZbus::new(inner.clone()).add_to_server(&mut object_server);
let task = CtrlKbdLedTask(inner);
tasks.push(Box::new(task));
}
Err(err) => {
error!("Keyboard control: {}", err);
}
}
// Graphics switching requires some checks on boot specifically for g-sync capable laptops
if enable_gfx_switching {
match CtrlGraphics::new(config.clone()) {
Ok(mut ctrl) => {
// Need to check if a laptop has the dedicated gfx switch
if CtrlRogBios::has_dedicated_gfx_toggle() {
if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
if let Ok(vendor) = ctrl.get_gfx_mode() {
if ded == 1 && vendor != GfxVendors::Nvidia {
if let Ok(config) = config.lock() {
if ded == 1 {
warn!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
let devices = ctrl.devices();
let bus = ctrl.bus();
@@ -118,6 +163,14 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
)?;
} else if ded == 0 {
info!("Dedicated GFX toggle is off");
let devices = ctrl.devices();
let bus = ctrl.bus();
CtrlGraphics::do_vendor_tasks(
config.gfx_mode,
false,
&devices,
&bus,
)?;
}
}
}
@@ -132,48 +185,24 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
}
}
// Collect tasks for task thread
let mut tasks: Vec<Arc<Mutex<dyn CtrlTask + Send>>> = Vec::new();
if let Ok(mut ctrl) = CtrlFanAndCpu::new(config).map_err(|err| {
error!("Profile control: {}", err);
}) {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
DbusFanAndCpu::new(tmp).add_to_server(&mut object_server);
};
let laptop = LaptopLedData::get_data();
let aura_config = AuraConfig::load(&laptop);
if let Ok(ctrl) = CtrlKbdBacklight::new(laptop, aura_config).map_err(|err| {
error!("Keyboard control: {}", err);
err
}) {
let tmp = Arc::new(Mutex::new(ctrl));
DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server);
tasks.push(tmp);
}
// TODO: implement messaging between threads to check fails
// These tasks generally read a sys path or file to check for a
// change
// Run tasks
let handle = std::thread::Builder::new()
.name("asusd watch".to_string())
.spawn(move || loop {
std::thread::sleep(std::time::Duration::from_millis(100));
for ctrl in tasks.iter() {
if let Ok(mut lock) = ctrl.try_lock() {
lock.do_task()
.map_err(|err| {
warn!("do_task error: {}", err);
})
.ok();
}
ctrl.do_task()
.map_err(|err| {
warn!("do_task error: {}", err);
})
.ok();
}
});
// Run zbus server
object_server
.with(
&ObjectPath::from_str_unchecked("/org/asuslinux/Charge"),
@@ -187,6 +216,7 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
})
.ok();
// Loop to check errors and iterate zbus server
loop {
if let Err(err) = &handle {
error!("{}", err);

View File

@@ -1,5 +1,5 @@
use intel_pstate::PStateError;
use rog_fan_curve::CurveError;
use rog_profiles::error::ProfileError;
use rog_types::error::GraphicsError;
use std::convert::From;
use std::fmt;
@@ -18,13 +18,13 @@ pub enum RogError {
Write(String, std::io::Error),
NotSupported,
NotFound(String),
IntelPstate(PStateError),
FanCurve(CurveError),
DoTask(String),
MissingFunction(String),
MissingLedBrightNode(String, std::io::Error),
ReloadFail(String),
GfxSwitching(GfxError),
Profiles(ProfileError),
Initramfs(String),
Modprobe(String),
Command(String, std::io::Error),
@@ -46,13 +46,13 @@ impl fmt::Display for RogError {
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
RogError::NotSupported => write!(f, "Not supported"),
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
RogError::IntelPstate(err) => write!(f, "Intel pstate error: {}", err),
RogError::FanCurve(err) => write!(f, "Custom fan-curve error: {}", err),
RogError::DoTask(deets) => write!(f, "Task error: {}", deets),
RogError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
RogError::MissingLedBrightNode(path, error) => write!(f, "Led node at {} is missing, please check you have the required patch or dkms module installed: {}", path, error),
RogError::ReloadFail(deets) => write!(f, "Task error: {}", deets),
RogError::GfxSwitching(deets) => write!(f, "Graphics switching error: {}", deets),
RogError::Profiles(deets) => write!(f, "Profile error: {}", deets),
RogError::Initramfs(detail) => write!(f, "Initiramfs error: {}", detail),
RogError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
RogError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
@@ -64,12 +64,6 @@ impl fmt::Display for RogError {
impl std::error::Error for RogError {}
impl From<PStateError> for RogError {
fn from(err: PStateError) -> Self {
RogError::IntelPstate(err)
}
}
impl From<CurveError> for RogError {
fn from(err: CurveError) -> Self {
RogError::FanCurve(err)
@@ -85,6 +79,12 @@ impl From<GraphicsError> for RogError {
}
}
impl From<ProfileError> for RogError {
fn from(err: ProfileError) -> Self {
RogError::Profiles(err)
}
}
impl From<zbus::Error> for RogError {
fn from(err: zbus::Error) -> Self {
RogError::Zbus(err)
@@ -96,3 +96,10 @@ impl From<std::io::Error> for RogError {
RogError::Io(err)
}
}
impl From<RogError> for zbus::fdo::Error {
#[inline]
fn from(err: RogError) -> Self {
zbus::fdo::Error::Failed(format!("{}", err))
}
}

View File

@@ -1,5 +1,5 @@
use log::{info, warn};
use rog_types::aura_modes::AuraModeNum;
use rog_aura::AuraModeNum;
use serde_derive::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Read;

View File

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

BIN
data/anime/asus/rog/ROG city.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -87,4 +87,11 @@ prod_family = "ROG Zephyrus G14"
board_names = ["GA401Q"]
standard = ["Static", "Breathe", "Pulse"]
multizone = false
per_key = false
per_key = false
[[led_data]]
prod_family = "ROG Zephyrus"
board_names = ["GX550L"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = false
per_key = true

View File

@@ -10,4 +10,6 @@ Restart=on-failure
Restart=always
RestartSec=1
Type=dbus
BusName=org.asuslinux.Daemon
BusName=org.asuslinux.Daemon
SELinuxContext=system_u:system_r:unconfined_t:s0
#SELinuxContext=system_u:object_r:modules_object_t:s0

89
design-patterns.md Normal file
View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "rog_anime"
version = "1.0.2"
version = "1.0.4"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -14,7 +14,7 @@ exclude = ["data"]
[features]
default = ["dbus"]
dbus = ["zbus", "zvariant", "zvariant_derive"]
dbus = ["zvariant", "zvariant_derive"]
[dependencies]
png_pong = "^0.8.0"
@@ -26,6 +26,5 @@ serde_derive = "^1.0"
glam = { version = "0.14.0", features = ["serde"] }
zbus = { version = "^1.9.1", optional = true }
zvariant = { version = "^2.6", optional = true }
zvariant_derive = { version = "^2.6", optional = true }

View File

@@ -14,6 +14,14 @@ pub const ANIME_DATA_LEN: usize = PANE_LEN * 2;
const USB_PREFIX1: [u8; 7] = [0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02];
const USB_PREFIX2: [u8; 7] = [0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02];
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize)]
pub struct AnimePowerStates {
pub enabled: bool,
pub boot_anim_enabled: bool,
}
/// The minimal serializable data that can be transferred over wire types.
/// Other data structures in `rog_anime` will convert to this.
#[cfg_attr(feature = "dbus", derive(Type))]

View File

@@ -3,9 +3,6 @@ use png_pong::decode::Error as PngError;
use std::error::Error;
use std::fmt;
#[cfg(feature = "dbus")]
use zbus::fdo;
#[derive(Debug)]
pub enum AnimeError {
NoFrames,
@@ -15,8 +12,6 @@ pub enum AnimeError {
Format,
/// The input was incorrect size, expected size is `IncorrectSize(width, height)`
IncorrectSize(u32, u32),
#[cfg(feature = "dbus")]
Zbus(fdo::Error)
}
impl fmt::Display for AnimeError {
@@ -33,8 +28,6 @@ impl fmt::Display for AnimeError {
"The input image size is incorrect, expected {}x{}",
width, height
),
#[cfg(feature = "dbus")]
AnimeError::Zbus(e) => write!(f, "ZBUS error: {}", e),
}
}
}
@@ -61,11 +54,3 @@ impl From<DecodingError> for AnimeError {
AnimeError::Gif(err)
}
}
#[cfg(feature = "dbus")]
impl From<AnimeError> for fdo::Error {
#[inline]
fn from(err: AnimeError) -> Self {
fdo::Error::Failed(format!("{}", err))
}
}

View File

@@ -180,36 +180,34 @@ impl AnimeImage {
let du = led_from_px * Vec3::new(-0.5, 0.5, 0.0);
let dv = led_from_px * Vec3::new(0.5, 0.5, 0.0);
for led in self.led_pos.iter_mut() {
if let Some(led) = led {
let mut sum = 0.0;
let mut alpha = 0.0;
let mut count = 0;
for led in self.led_pos.iter_mut().flatten() {
let mut sum = 0.0;
let mut alpha = 0.0;
let mut count = 0;
let pos = Vec3::new(led.x(), led.y(), 1.0);
let x0 = led_from_px.mul_vec3(pos + Vec3::new(0.0, -0.5, 0.0));
let pos = Vec3::new(led.x(), led.y(), 1.0);
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() {
let sample = x0 + *u * du + *v * dv;
const GROUP: [f32; 4] = [0.0, 0.5, 1.0, 1.5];
for u in GROUP.iter() {
for v in GROUP.iter() {
let sample = x0 + *u * du + *v * dv;
let x = sample.x as i32;
let y = sample.y as i32;
if x > width - 1 || y > height - 1 || x < 0 || y < 0 {
continue;
}
let p = self.img_pixels[(x + (y * width)) as usize];
sum += p.color as f32;
alpha += p.alpha;
count += 1;
let x = sample.x as i32;
let y = sample.y as i32;
if x > width - 1 || y > height - 1 || x < 0 || y < 0 {
continue;
}
let p = self.img_pixels[(x + (y * width)) as usize];
sum += p.color as f32;
alpha += p.alpha;
count += 1;
}
alpha /= count as f32;
sum /= count as f32;
led.set_bright((sum * self.bright * alpha) as u8);
}
alpha /= count as f32;
sum /= count as f32;
led.set_bright((sum * self.bright * alpha) as u8);
}
}

View File

@@ -30,4 +30,6 @@ pub use sequencer::*;
pub mod error;
/// Provides const methods to create the USB HID control packets
pub mod usb;
pub mod usb;
pub static VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -6,7 +6,7 @@ use std::{
use glam::Vec2;
use serde_derive::{Deserialize, Serialize};
use crate::{error::AnimeError, AnimeDataBuffer, AnimeGif, AnimeImage, AnimTime};
use crate::{error::AnimeError, AnimTime, AnimeDataBuffer, AnimeGif, AnimeImage};
/// All the possible AniMe actions that can be used. This enum is intended to be
/// a helper for loading up `ActionData`.
@@ -40,7 +40,7 @@ pub enum AnimeAction {
/// 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, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum ActionData {
/// Full gif sequence. Immutable.
Animation(AnimeGif),
@@ -58,6 +58,50 @@ pub enum ActionData {
Matrix,
}
impl ActionData {
pub fn from_anime_action(action: &AnimeAction) -> Result<ActionData, AnimeError> {
let a = match action {
AnimeAction::AsusAnimation {
file,
time: duration,
brightness,
} => ActionData::Animation(AnimeGif::create_diagonal_gif(
&file,
*duration,
*brightness,
)?),
AnimeAction::ImageAnimation {
file,
scale,
angle,
translation,
time: duration,
brightness,
} => ActionData::Animation(AnimeGif::create_png_gif(
&file,
*scale,
*angle,
*translation,
*duration,
*brightness,
)?),
AnimeAction::Image {
file,
scale,
angle,
translation,
brightness,
} => {
let image = AnimeImage::from_png(&file, *scale, *angle, *translation, *brightness)?;
let data = <AnimeDataBuffer>::from(&image);
ActionData::Image(Box::new(data))
}
AnimeAction::Pause(duration) => ActionData::Pause(*duration),
};
Ok(a)
}
}
/// An optimised precomputed set of actions that the user can cycle through
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct Sequences(Vec<ActionData>);

View File

@@ -8,7 +8,7 @@
//! Step 1 need to applied only on fresh system boot.
const INIT_STR: [u8; 15] = [
0x5e, b'A', b'S', b'U', b'S', b' ', b'T', b'E', b'C', b'H', b'.', b'I', b'N', b'C', b'.',
0x5e, b'A', b'S', b'U', b'S', b' ', b'T', b'e', b'c', b'h', b'.', b'I', b'n', b'c', b'.',
];
const PACKET_SIZE: usize = 640;
const DEV_PAGE: u8 = 0x5e;
@@ -24,7 +24,7 @@ pub const fn pkts_for_init() -> [[u8; PACKET_SIZE]; 2] {
let mut count = 0;
while count < INIT_STR.len() {
packets[0][count] = INIT_STR[count];
count +=1;
count += 1;
}
//
packets[1][0] = DEV_PAGE; // write it to be sure?

24
rog-aura/Cargo.toml Normal file
View File

@@ -0,0 +1,24 @@
[package]
name = "rog_aura"
version = "1.1.0"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
repository = "https://gitlab.com/asus-linux/asusctl"
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"
exclude = ["data"]
[features]
default = ["dbus"]
dbus = ["zvariant", "zvariant_derive"]
[dependencies]
serde = "^1.0"
serde_derive = "^1.0"
zvariant = { version = "^2.6", optional = true }
zvariant_derive = { version = "^2.6", optional = true }

373
rog-aura/LICENSE Normal file
View File

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

0
rog-aura/README.md Normal file
View File

View File

@@ -1,16 +1,25 @@
// static LED_INIT1: [u8; 2] = [0x5d, 0xb9];
// static LED_INIT2: &str = "]ASUS Tech.Inc."; // ] == 0x5d
// static LED_INIT3: [u8; 6] = [0x5d, 0x05, 0x20, 0x31, 0, 0x08];
// static LED_INIT4: &str = "^ASUS Tech.Inc."; // ^ == 0x5e
// static LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
pub const LED_INIT1: [u8; 2] = [0x5d, 0xb9];
pub const LED_INIT2: &str = "]ASUS Tech.Inc."; // ] == 0x5d
pub const LED_INIT3: [u8; 6] = [0x5d, 0x05, 0x20, 0x31, 0, 0x08];
pub const LED_INIT4: &str = "^ASUS Tech.Inc."; // ^ == 0x5e
pub const LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
use crate::error::AuraError;
use crate::LED_MSG_LEN;
use serde_derive::{Deserialize, Serialize};
use std::str::FromStr;
#[cfg(feature = "dbus")]
use zvariant_derive::Type;
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize, Type)]
use crate::{error::Error, LED_MSG_LEN};
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize)]
pub struct LedPowerStates {
pub enabled: bool,
pub sleep_anim_enabled: bool,
}
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum LedBrightness {
Off,
Low,
@@ -36,7 +45,8 @@ impl From<u32> for LedBrightness {
}
}
#[derive(Debug, Clone, PartialEq, Copy, Deserialize, Serialize, Type)]
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Clone, PartialEq, Copy, Deserialize, Serialize)]
pub struct Colour(pub u8, pub u8, pub u8);
impl Default for Colour {
@@ -46,20 +56,21 @@ impl Default for Colour {
}
impl FromStr for Colour {
type Err = AuraError;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() < 6 {
return Err(AuraError::ParseColour);
return Err(Error::ParseColour);
}
let r = u8::from_str_radix(&s[0..2], 16).or(Err(AuraError::ParseColour))?;
let g = u8::from_str_radix(&s[2..4], 16).or(Err(AuraError::ParseColour))?;
let b = u8::from_str_radix(&s[4..6], 16).or(Err(AuraError::ParseColour))?;
let r = u8::from_str_radix(&s[0..2], 16).or(Err(Error::ParseColour))?;
let g = u8::from_str_radix(&s[2..4], 16).or(Err(Error::ParseColour))?;
let b = u8::from_str_radix(&s[4..6], 16).or(Err(Error::ParseColour))?;
Ok(Colour(r, g, b))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize, Type)]
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum Speed {
Low = 0xe1,
Med = 0xeb,
@@ -71,7 +82,7 @@ impl Default for Speed {
}
}
impl FromStr for Speed {
type Err = AuraError;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
@@ -79,7 +90,7 @@ impl FromStr for Speed {
"low" => Ok(Speed::Low),
"med" => Ok(Speed::Med),
"high" => Ok(Speed::High),
_ => Err(AuraError::ParseSpeed),
_ => Err(Error::ParseSpeed),
}
}
}
@@ -87,7 +98,8 @@ impl FromStr for Speed {
/// Used for Rainbow mode.
///
/// Enum corresponds to the required integer value
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize, Type)]
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum Direction {
Right,
Left,
@@ -100,7 +112,7 @@ impl Default for Direction {
}
}
impl FromStr for Direction {
type Err = AuraError;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
@@ -109,21 +121,14 @@ impl FromStr for Direction {
"up" => Ok(Direction::Up),
"down" => Ok(Direction::Down),
"left" => Ok(Direction::Left),
_ => Err(AuraError::ParseDirection),
_ => Err(Error::ParseDirection),
}
}
}
/// Writes out the correct byte string for brightness
pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
[
0x5A, 0xBA, 0xC5, 0xC4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]
}
#[derive(
Debug, Type, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Deserialize, Serialize,
)]
/// 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)]
pub enum AuraModeNum {
Static = 0,
Breathe = 1,
@@ -197,95 +202,9 @@ impl From<u8> for AuraModeNum {
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraMultiZone {
static_: [AuraEffect; 4],
breathe: [AuraEffect; 4],
}
impl AuraMultiZone {
pub fn set(&mut self, effect: AuraEffect) {
if effect.mode == AuraModeNum::Static {
match effect.zone {
AuraZone::None => {}
AuraZone::One => self.static_[0] = effect,
AuraZone::Two => self.static_[1] = effect,
AuraZone::Three => self.static_[2] = effect,
AuraZone::Four => self.static_[3] = effect,
}
} else if effect.mode == AuraModeNum::Breathe {
match effect.zone {
AuraZone::None => {}
AuraZone::One => self.breathe[0] = effect,
AuraZone::Two => self.breathe[1] = effect,
AuraZone::Three => self.breathe[2] = effect,
AuraZone::Four => self.breathe[3] = effect,
}
}
}
pub fn static_(&self) -> &[AuraEffect; 4] {
&self.static_
}
pub fn breathe(&self) -> &[AuraEffect; 4] {
&self.breathe
}
}
impl Default for AuraMultiZone {
fn default() -> Self {
Self {
static_: [
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::One,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Two,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Three,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Four,
..Default::default()
},
],
breathe: [
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::One,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Two,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Three,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Four,
..Default::default()
},
],
}
}
}
/// Base effects have no zoning, while multizone is 1-4
#[derive(Debug, Type, Copy, Clone, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum AuraZone {
None,
One,
@@ -294,8 +213,12 @@ pub enum AuraZone {
Four,
}
/// Default factory modes structure
#[derive(Debug, Type, Clone, Deserialize, Serialize)]
/// Default factory modes structure. This easily converts to an USB HID packet with:
/// ```rust
/// let bytes: [u8; LED_MSG_LEN] = mode.into();
/// ```
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuraEffect {
/// The effect type
pub mode: AuraModeNum,
@@ -351,11 +274,11 @@ impl Default for AuraEffect {
/// Parses `AuraEffect` in to packet data for writing to the USB interface
///
/// Byte structure:
/// Byte structure where colour is RGB, one byte per R, G, B:
/// ```ignore
/// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12|
/// |---|---|---|---|---|---|---|---|---|---|---|---|---|
/// |5d |b3 |00 |03 |ff |00 |00 |00 |00 |00 |00 |ff |00 |
/// | 0 | 1 | 2 | 3 | 4, 5, 6 | 7 | 8 | 9 | 10, 11, 12|
/// |---|---|-----|-----|---------|------|----------|---|-----------|
/// |5d |b3 |Zone |Mode |Colour 1 |Speed |Direction |00 |Colour 2 |
/// ```
impl From<&AuraEffect> for [u8; LED_MSG_LEN] {
fn from(aura: &AuraEffect) -> Self {
@@ -372,7 +295,6 @@ impl From<&AuraEffect> for [u8; LED_MSG_LEN] {
msg[10] = aura.colour2.0;
msg[11] = aura.colour2.1;
msg[12] = aura.colour2.2;
msg
}
}

26
rog-aura/src/error.rs Normal file
View File

@@ -0,0 +1,26 @@
use std::error;
use std::fmt;
#[derive(Debug)]
pub enum Error {
ParseColour,
ParseSpeed,
ParseDirection,
ParseBrightness,
ParseAnime,
}
impl fmt::Display for Error {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::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"),
}
}
}
impl error::Error for Error {}

18
rog-aura/src/lib.rs Normal file
View File

@@ -0,0 +1,18 @@
/// A container of images/grids/gifs/pauses which can be iterated over to generate
/// cool effects
mod sequencer;
pub use sequencer::*;
mod builtin_modes;
pub use builtin_modes::*;
mod per_key_rgb;
pub use per_key_rgb::*;
pub mod usb;
pub mod error;
pub const LED_MSG_LEN: usize = 17;
pub static VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -33,7 +33,8 @@ impl KeyColourArray {
KeyColourArray(set)
}
/// Initialise and clear the keyboard for custom effects
/// Initialise and clear the keyboard for custom effects, this must be done for
/// every time mode switches from builtin to custom
#[inline]
pub const fn get_init_msg() -> [u8; 64] {
let mut init = [0u8; 64];

69
rog-aura/src/sequencer.rs Normal file
View File

@@ -0,0 +1,69 @@
use serde_derive::{Deserialize, Serialize};
use crate::error::Error;
/// 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, Deserialize, Serialize)]
pub enum ActionData {
Static,
}
/// An optimised precomputed set of actions that the user can cycle through
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct Sequences(Vec<ActionData>);
impl Sequences {
#[inline]
pub fn new() -> Self {
Self(Vec::new())
}
/// Use a base `AnimeAction` to generate the precomputed data and insert in to
/// the run buffer
#[inline]
pub fn insert(&mut self, _index: usize) -> Result<(), Error> {
Ok(())
}
/// Remove an item at this position from the run buffer. If the `index` supplied
/// is not in range then `None` is returned, otherwise the `ActionData` at that location
/// is yeeted and returned.
#[inline]
pub fn remove_item(&mut self, index: usize) -> Option<ActionData> {
if index < self.0.len() {
return Some(self.0.remove(index));
}
None
}
pub fn iter(&self) -> ActionIterator {
ActionIterator {
actions: &self,
next_idx: 0,
}
}
}
/// Iteractor helper for iterating over all the actions in `Sequences`
pub struct ActionIterator<'a> {
actions: &'a Sequences,
next_idx: usize,
}
impl<'a> Iterator for ActionIterator<'a> {
type Item = &'a ActionData;
#[inline]
fn next(&mut self) -> Option<&'a ActionData> {
if self.next_idx == self.actions.0.len() {
self.next_idx = 0;
return None;
}
let current = self.next_idx;
self.next_idx += 1;
Some(&self.actions.0[current])
}
}

32
rog-aura/src/usb.rs Normal file
View File

@@ -0,0 +1,32 @@
pub const LED_INIT1: [u8; 2] = [0x5d, 0xb9];
pub const LED_INIT2: &str = "]ASUS Tech.Inc."; // ] == 0x5d
pub const LED_INIT3: [u8; 6] = [0x5d, 0x05, 0x20, 0x31, 0, 0x08];
pub const LED_INIT4: &str = "^ASUS Tech.Inc."; // ^ == 0x5e
pub const LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
// Only these two packets must be 17 bytes
pub const LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
pub const LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
/// Writes out the correct byte string for brightness
pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
[
0x5A, 0xBA, 0xC5, 0xC4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]
}
pub const LED_AWAKE_ON_SLEEP_OFF: [u8; 17] = [
0x5d, 0xbd, 0x01, 0xcf, 0x17, 0x0b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
pub const LED_AWAKE_ON_SLEEP_ON: [u8; 17] = [
0x5d, 0xbd, 0x01, 0xff, 0x1f, 0x0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
pub const LED_AWAKE_OFF_SLEEP_OFF: [u8; 17] = [
0x5d, 0xbd, 0x01, 0xc3, 0x13, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
pub const LED_AWAKE_OFF_SLEEP_ON: [u8; 17] = [
0x5d, 0xbd, 0x01, 0xf3, 0x1b, 0x0d, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];

View File

@@ -1,6 +1,6 @@
[package]
name = "rog_dbus"
version = "3.2.0"
version = "3.4.0"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -10,10 +10,10 @@ description = "dbus interface methods for asusctl"
edition = "2018"
[dependencies]
serde_json = "^1.0"
rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_profiles = { path = "../rog-profiles" }
rog_types = { path = "../rog-types" }
rog_fan_curve = { version = "^0.1", features = ["serde"] }
zbus = "^1.8"
zbus_macros = "^1.8"
zbus = "^1.9"
zbus_macros = "^1.9"
zvariant = "^2.5"

View File

@@ -1,7 +1,6 @@
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
pub static DBUS_PATH: &str = "/org/asuslinux/Daemon";
pub static DBUS_IFACE: &str = "org.asuslinux.Daemon";
pub const LED_MSG_LEN: usize = 17;
pub mod zbus_anime;
pub mod zbus_charge;
@@ -11,11 +10,11 @@ pub mod zbus_profile;
pub mod zbus_rogbios;
pub mod zbus_supported;
use rog_types::{
aura_modes::AuraEffect,
gfx_vendors::{GfxRequiredUserAction, GfxVendors},
};
use std::sync::{Arc, Mutex};
use rog_anime::AnimePowerStates;
use rog_aura::{AuraEffect, LedPowerStates};
use rog_profiles::profiles::Profile;
use rog_types::gfx_vendors::{GfxRequiredUserAction, GfxVendors};
use std::sync::mpsc::{channel, Receiver};
use zbus::{Connection, Result, SignalReceiver};
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -48,9 +47,9 @@ impl<'a> DbusProxies<'a> {
))
}
pub fn setup_recv(&'a self, conn: Connection) -> SignalReceiver {
pub fn setup_recv(&'a self, conn: Connection) -> SignalReceiver<'a, 'a> {
let mut recv = SignalReceiver::new(conn);
//recv.receive_for(&self.proxy_anime);
recv.receive_for(self.anime.proxy());
recv.receive_for(self.led.proxy());
recv.receive_for(self.charge.proxy());
recv.receive_for(self.gfx.proxy());
@@ -89,68 +88,91 @@ impl<'a> DbusProxies<'a> {
// Signals separated out
pub struct Signals {
pub gfx_vendor: Arc<Mutex<Option<GfxVendors>>>,
pub gfx_action: Arc<Mutex<Option<GfxRequiredUserAction>>>,
pub profile: Arc<Mutex<Option<String>>>,
pub led_mode: Arc<Mutex<Option<AuraEffect>>>,
pub charge: Arc<Mutex<Option<u8>>>,
pub gfx_vendor: Receiver<GfxVendors>,
pub gfx_action: Receiver<GfxRequiredUserAction>,
pub profile: Receiver<Profile>,
pub led_mode: Receiver<AuraEffect>,
pub led_power_state: Receiver<LedPowerStates>,
pub anime_power_state: Receiver<AnimePowerStates>,
pub charge: Receiver<u8>,
}
impl Signals {
#[inline]
pub fn new(proxies: &DbusProxies) -> Result<Self> {
//
let charge_signal = Arc::new(Mutex::new(None));
proxies
.charge
.connect_notify_charge(charge_signal.clone())?;
//
let ledmode_signal = Arc::new(Mutex::new(None));
proxies.led.connect_notify_led(ledmode_signal.clone())?;
let gfx_action_signal = Arc::new(Mutex::new(None));
proxies
.gfx
.connect_notify_action(gfx_action_signal.clone())?;
let gfx_vendor_signal = Arc::new(Mutex::new(None));
proxies.gfx.connect_notify_gfx(gfx_vendor_signal.clone())?;
let profile_signal = Arc::new(Mutex::new(None));
proxies
.profile
.connect_notify_profile(profile_signal.clone())?;
Ok(Signals {
gfx_vendor: gfx_vendor_signal,
gfx_action: gfx_action_signal,
profile: profile_signal,
led_mode: ledmode_signal,
charge: charge_signal,
gfx_vendor: {
let (tx, rx) = channel();
proxies.gfx.connect_notify_gfx(tx)?;
rx
},
gfx_action: {
let (tx, rx) = channel();
proxies.gfx.connect_notify_action(tx)?;
rx
},
profile: {
let (tx, rx) = channel();
proxies.profile.connect_notify_profile(tx)?;
rx
},
charge: {
let (tx, rx) = channel();
proxies.charge.connect_notify_charge(tx)?;
rx
},
led_mode: {
let (tx, rx) = channel();
proxies.led.connect_notify_led(tx)?;
rx
},
led_power_state: {
let (tx, rx) = channel();
proxies.led.connect_notify_power_states(tx)?;
rx
},
anime_power_state: {
let (tx, rx) = channel();
proxies.anime.connect_notify_power_states(tx)?;
rx
},
})
}
}
/// This is the main way to communicate with the DBUS interface
pub struct AuraDbusClient<'a> {
pub struct RogDbusClient<'a> {
proxies: DbusProxies<'a>,
signals: Signals,
}
impl<'a> AuraDbusClient<'a> {
impl<'a> RogDbusClient<'a> {
#[inline]
pub fn new() -> Result<(Self, Connection)> {
let (proxies, conn) = DbusProxies::new()?;
let signals = Signals::new(&proxies)?;
Ok((AuraDbusClient { proxies, signals }, conn))
Ok((RogDbusClient { proxies, signals }, conn))
}
pub fn proxies(&self) -> &DbusProxies {
&self.proxies
}
pub fn signals(&self) -> &Signals {
&self.signals
}
pub fn setup_recv(&'a self, conn: Connection) -> SignalReceiver<'a, 'a> {
let mut recv = SignalReceiver::new(conn);
recv.receive_for(self.proxies.anime.proxy());
recv.receive_for(self.proxies.led.proxy());
recv.receive_for(self.proxies.charge.proxy());
recv.receive_for(self.proxies.gfx.proxy());
recv.receive_for(self.proxies.profile.proxy());
recv
}
/*
* GFX
*/
@@ -158,10 +180,8 @@ impl<'a> AuraDbusClient<'a> {
loop {
if let Ok(res) = self.proxies.gfx.proxy().next_signal() {
if res.is_none() {
if let Ok(lock) = self.signals.gfx_action.lock() {
if let Some(stuff) = lock.as_ref() {
return Ok(*stuff);
}
if let Ok(stuff) = self.signals.gfx_action.try_recv() {
return Ok(stuff);
}
// return Ok("Failed for unknown reason".to_owned());
}

View File

@@ -19,7 +19,9 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use rog_anime::AnimeDataBuffer;
use std::sync::mpsc::Sender;
use rog_anime::{AnimeDataBuffer, AnimePowerStates};
use zbus::{dbus_proxy, Connection, Result};
#[dbus_proxy(
@@ -35,6 +37,15 @@ trait Daemon {
/// WriteDirect method
fn write(&self, input: &[u8]) -> zbus::Result<()>;
#[dbus_proxy(property)]
fn awake_enabled(&self) -> zbus::Result<bool>;
#[dbus_proxy(property)]
fn boot_enabled(&self) -> zbus::Result<bool>;
#[dbus_proxy(signal)]
fn notify_power_states(&self, data: AnimePowerStates) -> zbus::Result<()>;
}
pub struct AnimeProxy<'a>(DaemonProxy<'a>);
@@ -63,4 +74,26 @@ impl<'a> AnimeProxy<'a> {
pub fn write(&self, input: AnimeDataBuffer) -> Result<()> {
self.0.write(input.get())
}
#[inline]
pub fn awake_enabled(&self) -> Result<bool> {
self.0.awake_enabled()
}
#[inline]
pub fn boot_enabled(&self) -> Result<bool> {
self.0.boot_enabled()
}
#[inline]
pub fn connect_notify_power_states(
&self,
send: Sender<AnimePowerStates>,
) -> zbus::fdo::Result<()> {
self.0.connect_notify_power_states(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
}

View File

@@ -19,7 +19,7 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender;
use zbus::{dbus_proxy, Connection, Result};
@@ -62,11 +62,10 @@ impl<'a> ChargeProxy<'a> {
}
#[inline]
pub fn connect_notify_charge(&self, charge: Arc<Mutex<Option<u8>>>) -> zbus::fdo::Result<()> {
pub fn connect_notify_charge(&self, send: Sender<u8>) -> zbus::fdo::Result<()> {
self.0.connect_notify_charge(move |data| {
if let Ok(mut lock) = charge.lock() {
*lock = Some(data);
}
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}

View File

@@ -19,7 +19,7 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender;
use rog_types::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors};
use zbus::{dbus_proxy, Connection, Result};
@@ -77,25 +77,20 @@ impl<'a> GfxProxy<'a> {
#[inline]
pub fn connect_notify_action(
&self,
action: Arc<Mutex<Option<GfxRequiredUserAction>>>,
send: Sender<GfxRequiredUserAction>,
) -> zbus::fdo::Result<()> {
self.0.connect_notify_action(move |data| {
if let Ok(mut lock) = action.lock() {
*lock = Some(data);
}
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
#[inline]
pub fn connect_notify_gfx(
&self,
vendor: Arc<Mutex<Option<GfxVendors>>>,
) -> zbus::fdo::Result<()> {
pub fn connect_notify_gfx(&self, send: Sender<GfxVendors>) -> zbus::fdo::Result<()> {
self.0.connect_notify_gfx(move |data| {
if let Ok(mut lock) = vendor.lock() {
*lock = Some(data);
}
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}

View File

@@ -19,14 +19,11 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender;
use zbus::{dbus_proxy, Connection, Result};
use rog_types::{
aura_modes::{AuraEffect, LedBrightness},
aura_perkey::KeyColourArray,
};
use rog_aura::{AuraEffect, KeyColourArray, LedBrightness, LedPowerStates};
const BLOCKING_TIME: u64 = 40; // 100ms = 10 FPS, max 50ms = 20 FPS, 40ms = 25 FPS
@@ -47,10 +44,18 @@ trait Daemon {
/// SetLedMode method
fn set_led_mode(&self, effect: &AuraEffect) -> zbus::Result<()>;
/// NotifyLed signal
/// SetAwakeEnabled method
fn set_awake_enabled(&self, enabled: bool) -> zbus::Result<()>;
/// SetSleepEnabled method
fn set_sleep_enabled(&self, enabled: bool) -> zbus::Result<()>;
/// NotifyLed signal
#[dbus_proxy(signal)]
fn notify_led(&self, data: &str) -> zbus::Result<()>;
fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>;
#[dbus_proxy(signal)]
fn notify_power_states(&self, data: LedPowerStates) -> zbus::Result<()>;
/// LedBrightness property
#[dbus_proxy(property)]
@@ -63,6 +68,12 @@ trait Daemon {
/// LedModes property
#[dbus_proxy(property)]
fn led_modes(&self) -> zbus::Result<String>;
#[dbus_proxy(property)]
fn awake_enabled(&self) -> zbus::Result<bool>;
#[dbus_proxy(property)]
fn sleep_enabled(&self) -> zbus::Result<bool>;
}
pub struct LedProxy<'a>(DaemonProxy<'a>);
@@ -88,6 +99,20 @@ impl<'a> LedProxy<'a> {
Ok(())
}
/// Set the keyboard LED to enabled while the device is awake
#[inline]
pub fn set_awake_enabled(&self, enabled: bool) -> Result<()> {
self.0.set_awake_enabled(enabled)?;
Ok(())
}
/// Set the keyboard LED suspend animation to enabled while the device is suspended
#[inline]
pub fn set_sleep_enabled(&self, enabled: bool) -> Result<()> {
self.0.set_sleep_enabled(enabled)?;
Ok(())
}
#[inline]
pub fn next_led_mode(&self) -> Result<()> {
self.0.next_led_mode()
@@ -103,6 +128,16 @@ impl<'a> LedProxy<'a> {
self.0.set_led_mode(mode)
}
#[inline]
pub fn awake_enabled(&self) -> Result<bool> {
self.0.awake_enabled()
}
#[inline]
pub fn sleep_enabled(&self) -> Result<bool> {
self.0.sleep_enabled()
}
/// Write a single colour block.
///
/// Intentionally blocks for 10ms after sending to allow the block to
@@ -136,13 +171,22 @@ impl<'a> LedProxy<'a> {
}
#[inline]
pub fn connect_notify_led(&self, led: Arc<Mutex<Option<AuraEffect>>>) -> zbus::fdo::Result<()> {
pub fn connect_notify_led(&self, send: Sender<AuraEffect>) -> zbus::fdo::Result<()> {
self.0.connect_notify_led(move |data| {
if let Ok(mut lock) = led.lock() {
if let Ok(dat) = serde_json::from_str(&data) {
*lock = Some(dat);
}
}
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
#[inline]
pub fn connect_notify_power_states(
&self,
send: Sender<LedPowerStates>,
) -> zbus::fdo::Result<()> {
self.0.connect_notify_power_states(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}

View File

@@ -19,9 +19,9 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender;
use rog_types::profile::ProfileEvent;
use rog_profiles::profiles::Profile;
use zbus::{dbus_proxy, Connection, Result};
#[dbus_proxy(
@@ -29,14 +29,14 @@ use zbus::{dbus_proxy, Connection, Result};
default_path = "/org/asuslinux/Profile"
)]
trait Daemon {
/// ActiveProfileName method
fn active_profile_name(&self) -> zbus::Result<String>;
/// NextProfile method
fn next_profile(&self) -> zbus::Result<()>;
/// Profile method
fn profile(&self) -> zbus::Result<String>;
/// Profile, get the active profile
fn active_name(&self) -> zbus::Result<String>;
/// Get the active `Profile` data
fn active_data(&self) -> zbus::Result<Profile>;
/// Profiles method
fn profiles(&self) -> zbus::Result<String>;
@@ -48,11 +48,11 @@ trait Daemon {
fn remove(&self, profile: &str) -> zbus::Result<()>;
/// SetProfile method
fn set_profile(&self, profile: &str) -> zbus::Result<()>;
fn new_or_modify(&self, profile: &Profile) -> zbus::Result<()>;
/// NotifyProfile signal
#[dbus_proxy(signal)]
fn notify_profile(&self, profile: &str) -> zbus::Result<()>;
fn notify_profile(&self, profile: Profile) -> zbus::Result<()>;
}
pub struct ProfileProxy<'a>(DaemonProxy<'a>);
@@ -68,13 +68,13 @@ impl<'a> ProfileProxy<'a> {
}
#[inline]
pub fn active_profile_name(&self) -> Result<String> {
self.0.active_profile_name()
pub fn active_name(&self) -> Result<String> {
self.0.active_name()
}
#[inline]
pub fn active_profile_data(&self) -> Result<String> {
self.0.profile()
pub fn active_data(&self) -> Result<Profile> {
self.0.active_data()
}
#[inline]
@@ -87,17 +87,6 @@ impl<'a> ProfileProxy<'a> {
self.0.next_profile()
}
#[inline]
pub fn write_fan_mode(&self, level: u8) -> Result<()> {
self.0
.set_profile(&serde_json::to_string(&ProfileEvent::ChangeMode(level)).unwrap())
}
#[inline]
pub fn write_command(&self, cmd: &ProfileEvent) -> Result<()> {
self.0.set_profile(&serde_json::to_string(cmd).unwrap())
}
#[inline]
pub fn profile_names(&self) -> Result<Vec<String>> {
self.0.profile_names()
@@ -109,14 +98,15 @@ impl<'a> ProfileProxy<'a> {
}
#[inline]
pub fn connect_notify_profile(
&self,
charge: Arc<Mutex<Option<String>>>,
) -> zbus::fdo::Result<()> {
pub fn new_or_modify(&self, profile: &Profile) -> Result<()> {
self.0.new_or_modify(profile)
}
#[inline]
pub fn connect_notify_profile(&self, send: Sender<Profile>) -> zbus::fdo::Result<()> {
self.0.connect_notify_profile(move |data| {
if let Ok(mut lock) = charge.lock() {
*lock = Some(data.to_owned());
}
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}

View File

@@ -19,6 +19,7 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use rog_types::supported::SupportedFunctions;
use zbus::{dbus_proxy, Connection, Result};
#[dbus_proxy(
@@ -27,7 +28,7 @@ use zbus::{dbus_proxy, Connection, Result};
)]
trait Daemon {
/// SupportedFunctions method
fn supported_functions(&self) -> zbus::Result<String>;
fn supported_functions(&self) -> zbus::Result<SupportedFunctions>;
}
pub struct SupportProxy<'a>(DaemonProxy<'a>);
@@ -43,7 +44,7 @@ impl<'a> SupportProxy<'a> {
}
#[inline]
pub fn get_supported_functions(&self) -> Result<String> {
pub fn get_supported_functions(&self) -> Result<SupportedFunctions> {
self.0.supported_functions()
}
}

18
rog-profiles/Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "rog_profiles"
version = "0.1.0"
authors = ["Luke D. Jones <luke@ljones.dev>"]
edition = "2018"
[features]
default = ["dbus"]
dbus = ["zvariant", "zvariant_derive"]
[dependencies]
rog_fan_curve = { version = "^0.1", features = ["serde"] }
serde = "^1.0"
serde_derive = "^1.0"
intel-pstate = "^0.2"
zvariant = { version = "^2.6", optional = true }
zvariant_derive = { version = "^2.6", optional = true }

54
rog-profiles/src/error.rs Normal file
View File

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

8
rog-profiles/src/lib.rs Normal file
View File

@@ -0,0 +1,8 @@
pub mod error;
pub mod profiles;
static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy";
static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode";
static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost";
pub static VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -0,0 +1,185 @@
use rog_fan_curve::{Curve, Fan};
use serde_derive::{Deserialize, Serialize};
use std::io::Write;
use std::{fs::OpenOptions, path::Path, str::FromStr};
#[cfg(feature = "dbus")]
use zvariant_derive::Type;
use crate::{error::ProfileError, AMD_BOOST_PATH, FAN_TYPE_1_PATH, FAN_TYPE_2_PATH};
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Profile {
pub name: String,
pub min_percentage: u8,
pub max_percentage: u8,
pub turbo: bool,
pub fan_preset: FanLevel,
pub fan_curve: String,
}
impl Default for Profile {
fn default() -> Self {
Profile {
name: "new".into(),
min_percentage: 0,
max_percentage: 100,
turbo: false,
fan_preset: FanLevel::Normal,
fan_curve: "".to_string(),
}
}
}
impl Profile {
pub fn new(
name: String,
min_percentage: u8,
max_percentage: u8,
turbo: bool,
fan_preset: FanLevel,
fan_curve: String,
) -> Self {
Profile {
name,
min_percentage,
max_percentage,
turbo,
fan_preset,
fan_curve,
}
}
pub fn get_intel_supported() -> bool {
intel_pstate::PState::new().is_ok()
}
pub fn get_fan_path() -> Result<&'static str, ProfileError> {
if Path::new(FAN_TYPE_1_PATH).exists() {
Ok(FAN_TYPE_1_PATH)
} else if Path::new(FAN_TYPE_2_PATH).exists() {
Ok(FAN_TYPE_2_PATH)
} else {
Err(ProfileError::NotSupported)
}
}
pub fn set_system_pstate(&self) -> Result<(), ProfileError> {
// Set CPU pstate
if let Ok(pstate) = intel_pstate::PState::new() {
pstate.set_min_perf_pct(self.min_percentage)?;
pstate.set_max_perf_pct(self.max_percentage)?;
pstate.set_no_turbo(!self.turbo)?;
} else {
// must be AMD CPU
let mut file = OpenOptions::new()
.write(true)
.open(AMD_BOOST_PATH)
.map_err(|err| ProfileError::Path(AMD_BOOST_PATH.into(), err))?;
let boost = if self.turbo { "1" } else { "0" }; // opposite of Intel
file.write_all(boost.as_bytes())
.map_err(|err| ProfileError::Write(AMD_BOOST_PATH.into(), err))?;
}
Ok(())
}
pub fn set_system_fan_mode(&self) -> Result<(), ProfileError> {
let path = Profile::get_fan_path()?;
let mut fan_ctrl = OpenOptions::new()
.write(true)
.open(path)
.map_err(|err| ProfileError::Path(path.into(), err))?;
fan_ctrl
.write_all(format!("{}\n", <u8>::from(self.fan_preset)).as_bytes())
.map_err(|err| ProfileError::Write(path.into(), err))?;
Ok(())
}
pub fn set_system_fan_curve(&self) -> Result<(), ProfileError> {
if !self.fan_curve.is_empty() {
if let Ok(curve) = Profile::parse_fan_curve(&self.fan_curve) {
use rog_fan_curve::Board;
if let Some(board) = Board::from_board_name() {
curve.apply(board, Fan::Cpu)?;
curve.apply(board, Fan::Gpu)?;
}
}
}
Ok(())
}
pub fn set_system_all(&self) -> Result<(), ProfileError> {
self.set_system_pstate()?;
if !self.fan_curve.is_empty() {
self.set_system_fan_mode()?;
} else {
self.set_system_fan_curve()?;
}
Ok(())
}
fn parse_fan_curve(data: &str) -> Result<Curve, String> {
let curve = Curve::from_config_str(data)?;
if let Err(err) = curve.check_safety(Fan::Cpu) {
return Err(format!("Unsafe curve {:?}", err));
}
if let Err(err) = curve.check_safety(Fan::Gpu) {
return Err(format!("Unsafe curve {:?}", err));
}
Ok(curve)
}
}
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum FanLevel {
Normal,
Boost,
Silent,
}
impl FromStr for FanLevel {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"normal" => Ok(FanLevel::Normal),
"boost" => Ok(FanLevel::Boost),
"silent" => Ok(FanLevel::Silent),
_ => Err("Invalid fan level"),
}
}
}
impl From<u8> for FanLevel {
fn from(n: u8) -> Self {
match n {
0 => FanLevel::Normal,
1 => FanLevel::Boost,
2 => FanLevel::Silent,
_ => FanLevel::Normal,
}
}
}
impl From<FanLevel> for u8 {
fn from(n: FanLevel) -> Self {
match n {
FanLevel::Normal => 0,
FanLevel::Boost => 1,
FanLevel::Silent => 2,
}
}
}
impl From<&FanLevel> for u8 {
fn from(n: &FanLevel) -> Self {
match n {
FanLevel::Normal => 0,
FanLevel::Boost => 1,
FanLevel::Silent => 2,
}
}
}

View File

@@ -10,8 +10,7 @@ description = "A small library of effect types and conversions for ROG Aura"
edition = "2018"
[dependencies]
gumdrop = "^0.8"
rog_fan_curve = { version = "^0.1", features = ["serde"] }
rog_aura = { path = "../rog-aura" }
serde = "^1.0"
serde_derive = "^1.0"
zvariant = "^2.6"

View File

@@ -1,30 +1,6 @@
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum AuraError {
ParseColour,
ParseSpeed,
ParseDirection,
ParseBrightness,
ParseAnime,
}
impl fmt::Display for AuraError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AuraError::ParseColour => write!(f, "Could not parse colour"),
AuraError::ParseSpeed => write!(f, "Could not parse speed"),
AuraError::ParseDirection => write!(f, "Could not parse direction"),
AuraError::ParseBrightness => write!(f, "Could not parse brightness"),
AuraError::ParseAnime => write!(f, "Could not parse anime"),
}
}
}
impl Error for AuraError {}
#[derive(Debug)]
pub enum GraphicsError {
ParseVendor,
@@ -42,27 +18,3 @@ impl fmt::Display for GraphicsError {
}
impl Error for GraphicsError {}
#[derive(Debug)]
pub enum AnimeError {
InvalidBitmap,
Io(std::io::Error),
}
impl fmt::Display for AnimeError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AnimeError::InvalidBitmap => write!(f, "Bitmap is invalid"),
AnimeError::Io(e) => write!(f, "Could not open: {}", e),
}
}
}
impl Error for AnimeError {}
impl From<std::io::Error> for AnimeError {
fn from(err: std::io::Error) -> Self {
AnimeError::Io(err)
}
}

View File

@@ -5,14 +5,6 @@
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
pub static DBUS_PATH: &str = "/org/asuslinux/Daemon";
pub static DBUS_IFACE: &str = "org.asuslinux.Daemon";
pub const LED_MSG_LEN: usize = 17;
pub mod aura_modes;
pub mod profile;
/// Enables you to create fancy RGB effects
pub mod aura_perkey;
pub mod gfx_vendors;

View File

@@ -1,153 +0,0 @@
use gumdrop::Options;
use rog_fan_curve::{Curve, Fan};
use serde_derive::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Profile {
pub min_percentage: u8,
pub max_percentage: u8,
pub turbo: bool,
pub fan_preset: u8,
pub fan_curve: Option<Curve>,
}
#[deprecated]
pub type CpuSettings = Profile;
impl Default for Profile {
fn default() -> Self {
Profile {
min_percentage: 0,
max_percentage: 100,
turbo: false,
fan_preset: 0,
fan_curve: None,
}
}
}
impl Profile {
pub fn new(
min_percentage: u8,
max_percentage: u8,
turbo: bool,
fan_preset: u8,
fan_curve: Option<Curve>,
) -> Self {
Profile {
min_percentage,
max_percentage,
turbo,
fan_preset,
fan_curve,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ProfileEvent {
Cli(ProfileCommand),
ChangeMode(u8),
Toggle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FanLevel {
Normal,
Boost,
Silent,
}
impl FromStr for FanLevel {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"normal" => Ok(FanLevel::Normal),
"boost" => Ok(FanLevel::Boost),
"silent" => Ok(FanLevel::Silent),
_ => Err("Invalid fan level"),
}
}
}
impl From<u8> for FanLevel {
fn from(n: u8) -> Self {
match n {
0 => FanLevel::Normal,
1 => FanLevel::Boost,
2 => FanLevel::Silent,
_ => FanLevel::Normal,
}
}
}
impl From<FanLevel> for u8 {
fn from(n: FanLevel) -> Self {
match n {
FanLevel::Normal => 0,
FanLevel::Boost => 1,
FanLevel::Silent => 2,
}
}
}
impl From<&FanLevel> for u8 {
fn from(n: &FanLevel) -> Self {
match n {
FanLevel::Normal => 0,
FanLevel::Boost => 1,
FanLevel::Silent => 2,
}
}
}
fn parse_fan_curve(data: &str) -> Result<Curve, String> {
let curve = Curve::from_config_str(data)?;
if let Err(err) = curve.check_safety(Fan::Cpu) {
return Err(format!("Unsafe curve {:?}", err));
}
if let Err(err) = curve.check_safety(Fan::Gpu) {
return Err(format!("Unsafe curve {:?}", err));
}
Ok(curve)
}
#[derive(Debug, Clone, Options, Serialize, Deserialize)]
pub struct ProfileCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(help = "toggle to next profile in list")]
pub next: bool,
#[options(help = "create the profile if it doesn't exist")]
pub create: bool,
#[options(meta = "", help = "remove a profile by name")]
pub remove: Option<String>,
#[options(help = "list available profiles")]
pub list: bool,
#[options(help = "get active profile name")]
pub active_name: bool,
#[options(help = "get active profile data")]
pub active_data: bool,
#[options(help = "get all profile data")]
pub profiles_data: bool,
#[options(meta = "", help = "enable or disable cpu turbo")]
pub turbo: Option<bool>,
#[options(meta = "", help = "set min cpu scaling (intel)")]
pub min_percentage: Option<u8>,
#[options(meta = "", help = "set max cpu scaling (intel)")]
pub max_percentage: Option<u8>,
#[options(meta = "", help = "<silent, normal, boost>")]
pub fan_preset: Option<FanLevel>,
#[options(
meta = "",
parse(try_from_str = "parse_fan_curve"),
help = "set fan curve"
)]
pub curve: Option<Curve>,
#[options(free)]
pub profile: Option<String>,
}

View File

@@ -1,8 +1,8 @@
use rog_aura::AuraModeNum;
use serde_derive::{Deserialize, Serialize};
use zvariant_derive::Type;
use crate::aura_modes::AuraModeNum;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct SupportedFunctions {
pub anime_ctrl: AnimeSupportedFunctions,
pub charge_ctrl: ChargeSupportedFunctions,
@@ -11,30 +11,30 @@ pub struct SupportedFunctions {
pub rog_bios_ctrl: RogBiosSupportedFunctions,
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct AnimeSupportedFunctions(pub bool);
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct ChargeSupportedFunctions {
pub charge_level_set: bool,
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct FanCpuSupportedFunctions {
pub stock_fan_modes: bool,
pub min_max_freq: bool,
pub fan_curve_set: bool,
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct LedSupportedFunctions {
pub brightness_set: bool,
pub stock_led_modes: Option<Vec<AuraModeNum>>,
pub stock_led_modes: Vec<AuraModeNum>,
pub multizone_led_mode: bool,
pub per_key_led_mode: bool,
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct RogBiosSupportedFunctions {
pub post_sound_toggle: bool,
pub dedicated_gfx_toggle: bool,