asusctl

repository·main·Indexed 19 days ago

https://github.com/opengamingcollective/asusctl

A collection of tools and libraries for ASUS ROG gaming laptops, including asusd for hardware control, rog-anime for AniMe matrix displays, and rog-aura for RGB keyboard management. Features include the asus-shutdown utility for deferred firmware attribute application, the rog-control-center GUI, and the anime CLI for managing display animations, images, and GIFs.

Tokens
58.2K
Snippets
198
Records
269
Agent score
66%

What's inside asusctl

  1. Overview of rog-aura

    main

    rog-aura is a helper crate designed for interacting with RGB keyboards on ASUS ROG gaming laptops (e.g., Zephyrus, Strix, TUF). It handles the conversion from high-level APIs to the USB packets required by the hardware.

    Key capabilities include:

    • Detecting USB or I2C keyboard connections.
    • Setting basic lighting modes and zones.
    • Advanced/direct addressing for single zones, multi-zones, or per-key lighting.
    • Physical layout mapping for visual representations.
  2. Overview of asusd-user

    main
    The asusd-user crate provides the asusd-user binary and a helper library designed to run in userland. Its primary purpose is to offer users and third-party applications an interface for controlling hardware features, specifically for creating AniMe Matrix sequences. It aims to provide a balance between simplicity and a high degree of control for user-level interactions.
  3. Implement the Controller pattern for the daemon

    main

    The daemon uses a controller pattern to manage hardware functions. Controllers are typically implemented as standalone structs. To integrate a controller with the daemon's lifecycle, you implement specific traits. Because the daemon owns these trait objects, you must wrap the controller in an Arc<Mutex<T>> to allow shared access across different components (tasks, Zbus, reloaders).

    Core Traits

    • Reloadable: For controllers that need to reload configuration (typically on startup).
    • ZbusAdd: For controllers that use zbus derives and need to run on the Zbus server.
    • CtrlTask: For controllers that need to run periodic or event-driven tasks in the main loop.
    • GetSupported: To check if the required hardware or functions are supported.
  4. Understand the aura_support.ron database structure

    main

    The aura_support.ron file acts as a database mapping laptop models to their supported RGB features.

    Each entry contains:

    • board_name: The identifier found via cat /sys/devices/virtual/dmi/id/board_name (note: the last character variant might be omitted).
    • layout_name: The base name for the layout file (e.g., g513i).
    • basic_modes: A list of built-in modes like Static, Breathe, Rainbow, etc.
    • basic_zones: A list of zones that can be set via basic modes (e.g., Key1, Logo, BarLeft).
    • advanced_type: Defines the level of control available:
      • None: No advanced aura support.
      • PerKey: Full per-key control using LedCode.
      • Zoned: Supports specific zones like ZonedKbLeft, LightbarRight, etc.
        (
            board_name: "G513QR",
            layout_name: "g513i-per-key",
            basic_modes: [Static, Breathe, Strobe, Rainbow, Star, Rain, Highlight, Laser, Ripple, Pulse, Comet, Flash],
            basic_zones: [],
            advanced_type: PerKey,
        ),
  5. How deferred GPU settings work

    main

    GPU firmware writes are considered high-risk and are deferred until system shutdown to prevent conflicts with active GPU processes.

    The Lifecycle of a GPU Change:

    1. Queueing: When you call set_current_value() for a GPU attribute, the value is not applied immediately. Instead, it is stored in a queued_gpu map within the daemon.
    2. Verification: You can check if a change is pending by querying the queued_gpu_value() property via DBus. It returns the queued value, or -1 if no value is queued.
    3. Application: During the shutdown process, the asus-shutdown service monitors for the PrepareForShutdown signal. It waits for the discrete GPU to become idle (up to 8 seconds) and then calls apply_queued_gpu_value() to write the pending settings to the firmware.
    4. Activation: The changes take effect only after the system reboots.
  6. Understand the GPU mode two-attribute system

    main

    The system uses two separate firmware attributes to define three distinct GPU modes. If your hardware supports a GPU MUX, you must set both attributes correctly to switch modes. If your hardware does not support a MUX, the system falls back to a simplified mode using only dgpu_disable.

    Mode Mapping (with MUX support)

    Modedgpu_disablegpu_mux_modeEffect
    Integrated11iGPU only (dGPU disabled)
    Ultimate00dGPU only (GPU MUX enabled)
    Hybrid01Dynamic GPU switching (Optimus)

    Fallback Mapping (no MUX support)

    Modedgpu_disableEffect
    Integrated1iGPU only
    Hybrid0Dynamic switching
  7. Configure keyboard layouts in .ron format

    main

    Layouts are defined using Rusty Object Notation (.ron). A layout file consists of a locale (e.g., "US"), a key_shapes map, and key_rows.

    key_shapes

    A hashmap of String to ShapeType. Common shapes include:

    • Led(width, height, pad_left, pad_right, pad_top, pad_bottom): Defines an LED spot with dimensions and padding.
    • Blank(width, height): A non-LED space that occupies room in the layout.

    key_rows

    A list of rows, where each row contains a pad_left, pad_top, and a list of (Key, shape_name) tuples. Key is an enum mapping to specific USB packets/RGB indices. Special key types include:

    • Spacing: Acts like a non-visible LED.
    • Blocking: Intended to block effects like a row-laser.

    Layout Types

    1. Per-key layouts: Use PerKey advanced type and can include any LedCode.
    2. Zoned layouts: Use Zoned advanced type. They can include regular keys and specific zone codes (e.g., ZonedKbLeft, LightbarRightCorner), but cannot use per-key specific codes like LidLogo, LidLeft, or LidRight.
    (
        locale: "US",
        key_shapes: {
            "regular": Led(
                width: 1.0,
                height: 1.0,
                pad_left: 0.1,
                pad_right: 0.1,
                pad_top: 0.1,
                pad_bottom: 0.1,
            ),
            "func_space": Blank(
                width: 0.2,
                height: 0.0,
            ),
        },
        key_rows: [
            (
                pad_left: 0.1,
                pad_top: 0.1,
                row: [
                    (Spacing, "rog_spacer"),
                    (VolDown, "rog_row"),
                    (VolUp, "rog_row"),
                    (MicMute, "rog_row"),
                    (Rog, "rog_row"),
                ],
            ),
        ]
    )
  8. Understand the configuration management strategy in config-traits

    main

    The config_traits crate is designed to manage diverse configuration files across different formats. It handles parsing from multiple formats and provides mechanisms for migrating configuration files when fields or names change between versions.

    Format Support and Migration Path:

    • Canonical Format: The primary and preferred format is .ron (Rusty Object Notation). This is used because it supports Rust types natively, allows for comments, and is less verbose than JSON.
    • Fallback and Migration: If parsing a .ron file fails, the crate attempts to parse the configuration from json or toml. Once successfully parsed from these formats, the crate will update the configuration to the canonical .ron format.
  9. Write AniMe matrix data directly to USB via HID

    main

    For standalone use (e.g., building a Windows controller), rog-anime can transform image data into USB HID packets. The workflow involves:

    1. Creating an AniMeImage.
    2. Converting the image to an AniMeDataBuffer.
    3. Converting the buffer to AniMePacketType.
    4. Iterating over the packets to write them directly to the USB device.
    let mut image = AniMeImage::from_png(
            Path::new("./doom.png"),
            0.9, // scale
            0.0, // rotation
            Vec2::new(0.0, 0.0), // position
            0.3, // brightness
        )?;
    
    // convert to intermediate packet format
    let buffer = <AniMeDataBuffer>::from(&image);
    // then to USB HID
    let data = AniMePacketType::from(buffer);
    // and then write direct
    for packet in data.iter() {
        write_usb(packet); // some usb call here
    }
  10. How to add translations to rog-control-center

    main

    Translations are managed via .po files and compiled into binary catalogs at build time. To add a new language:

    1. Copy the template file rog-control-center/translations/en/rog-control-center.po to rog-control-center/translations/<YOUR_LOCALE>/rog-control-center.po.
    2. Edit the new .po file with your translations.
    3. Ensure msgfmt (from the gettext package) is installed on your system.
    4. Rebuild the project to compile the updated .po sources into binary catalogs.

    To test your local translations without a full rebuild, run the binary with the RUST_TRANSLATIONS=1 environment variable set.

    RUST_TRANSLATIONS=1 rog-control-center
  11. Use rog-anime with the asusd daemon via DBus

    main

    You can use rog-anime to communicate with the asusd daemon using the dbus feature (enabled by default). This is done by using AuraDbusClient to access the anime proxy and writing AniMeDataBuffer objects to it. This approach is recommended when working within the asus-nb-ctrl ecosystem on Linux.

    use std::{path::Path, thread::sleep, time::Duration};
    use rog_anime::{AniMeImage, Vec2, AniMeDataBuffer};
    use rog_dbus::AuraDbusClient;
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let (client, _) = AuraDbusClient::new().unwrap();
    
        let mut image = AniMeImage::from_png(
            Path::new("./doom.png"),
            0.9, // scale
            0.0, // rotation
            Vec2::new(0.0, 0.0), // position
            0.3, // brightness
        )?;
    
        loop {
            image.angle += 0.05;
            if image.angle > std::f32::consts::PI * 2.0 {
                image.angle = 0.0
            }
            image.update();
    
            client
                .proxies()
                .anime()
                .write(<AniMeDataBuffer>::from(&image))
                .unwrap();
            sleep(Duration::from_micros(500));
        }
    }