mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
Compare commits
5 Commits
31939f41ba
...
f4c69c7e8c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4c69c7e8c | ||
|
|
7cf6c477eb | ||
|
|
b9c251d403 | ||
|
|
6ef977f6ae | ||
|
|
6e83884c0a |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -20,3 +20,8 @@ desktop-extensions/gnome*/@types/gir-generated
|
|||||||
desktop-extensions/gnome*/node_modules
|
desktop-extensions/gnome*/node_modules
|
||||||
desktop-extensions/gnome*/schemas/gschemas.compiled
|
desktop-extensions/gnome*/schemas/gschemas.compiled
|
||||||
desktop-extensions/gnome*/*.zip
|
desktop-extensions/gnome*/*.zip
|
||||||
|
|
||||||
|
# agents and reference
|
||||||
|
CLAUDE.md
|
||||||
|
AGENTS.md
|
||||||
|
/reference
|
||||||
|
|||||||
547
asusctl/examples/anime-led-scan.rs
Normal file
547
asusctl/examples/anime-led-scan.rs
Normal file
@@ -0,0 +1,547 @@
|
|||||||
|
//! LED scanning tool for discovering AniMe Matrix buffer-to-LED mappings.
|
||||||
|
//!
|
||||||
|
//! This tool lights up one buffer index at a time, allowing you to observe
|
||||||
|
//! which physical LED corresponds to each buffer position. This is essential
|
||||||
|
//! for mapping new device types like G835L where the exact layout is unknown.
|
||||||
|
//!
|
||||||
|
//! You might want to use it slowly, as it sometimes doesn't work properly.
|
||||||
|
//! Maybe there's better ways to make this reliable but for now it works for my use case.
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//! ```
|
||||||
|
//! cargo run --example anime-led-scan -- [options]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Controls
|
||||||
|
//! - `n` or `Enter`: Next index
|
||||||
|
//! - `p` or `Backspace`: Previous index
|
||||||
|
//! - `j` followed by number: Jump to specific index
|
||||||
|
//! - `+` / `-`: Adjust step size (default 1)
|
||||||
|
//! - `s`: Save current index to notes file
|
||||||
|
//! - `r`: Mark current index as row start
|
||||||
|
//! - `q` or `Ctrl+C`: Quit
|
||||||
|
//!
|
||||||
|
//! # Output
|
||||||
|
//! Creates a `led-scan-notes.txt` file with recorded observations.
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::{self, BufRead, Write};
|
||||||
|
|
||||||
|
use rog_anime::usb::{get_anime_type, Brightness};
|
||||||
|
use rog_anime::{AnimeDataBuffer, AnimeType};
|
||||||
|
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||||
|
use zbus::blocking::Connection;
|
||||||
|
|
||||||
|
/// Saved device state for restoration on exit
|
||||||
|
struct SavedState {
|
||||||
|
builtins_enabled: bool,
|
||||||
|
brightness: Brightness,
|
||||||
|
display_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_help(scan_len: usize, buffer_len: usize) {
|
||||||
|
println!("\n=== LED Scan Tool ===");
|
||||||
|
println!(
|
||||||
|
"Scan range: 0-{} (buffer size: {})",
|
||||||
|
scan_len - 1,
|
||||||
|
buffer_len
|
||||||
|
);
|
||||||
|
println!("Commands:");
|
||||||
|
println!(" n, Enter - Next index");
|
||||||
|
println!(" p, Backspace - Previous index");
|
||||||
|
println!(" j <num> - Jump to index");
|
||||||
|
println!(" + / - - Increase/decrease step size");
|
||||||
|
println!(" s - Save note for current index");
|
||||||
|
println!(" r - Mark as row start");
|
||||||
|
println!(" a - Auto-scan (runs through all indices)");
|
||||||
|
println!(" f - Fill all buffer bytes");
|
||||||
|
println!(" f <start> <end> - Fill range (inclusive)");
|
||||||
|
println!(" p1/p2/p3 - Fill pane 1/2/3 only (each is 627 bytes)");
|
||||||
|
println!(" hold - Hold current LED (press Enter to release)");
|
||||||
|
println!(" hold <s> <e> - Hold range (press Enter to release)");
|
||||||
|
println!(" c - Clear display");
|
||||||
|
println!(" row - Step through rows (G835L, provisional)");
|
||||||
|
println!(" row <n> - Show specific row (G835L, provisional)");
|
||||||
|
println!(" allrows - Light all rows sequentially (G835L)");
|
||||||
|
println!(" rowmap - Print the full row mapping (G835L)");
|
||||||
|
println!(" h - Show this help");
|
||||||
|
println!(" q - Quit and restore state");
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_note(index: usize, note: &str) -> io::Result<()> {
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open("led-scan-notes.txt")?;
|
||||||
|
writeln!(file, "Index {}: {}", index, note)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_single_led(
|
||||||
|
proxy: &AnimeProxyBlocking,
|
||||||
|
anime_type: AnimeType,
|
||||||
|
index: usize,
|
||||||
|
brightness: u8,
|
||||||
|
) {
|
||||||
|
let mut buffer = AnimeDataBuffer::new(anime_type);
|
||||||
|
let data = buffer.data_mut();
|
||||||
|
if index < data.len() {
|
||||||
|
data[index] = brightness;
|
||||||
|
}
|
||||||
|
if let Err(e) = proxy.write(buffer) {
|
||||||
|
eprintln!("Error writing to device: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_display(proxy: &AnimeProxyBlocking, anime_type: AnimeType) {
|
||||||
|
let buffer = AnimeDataBuffer::new(anime_type);
|
||||||
|
let _ = proxy.write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_display(proxy: &AnimeProxyBlocking, anime_type: AnimeType, brightness: u8) {
|
||||||
|
let mut buffer = AnimeDataBuffer::new(anime_type);
|
||||||
|
let data = buffer.data_mut();
|
||||||
|
for byte in data.iter_mut() {
|
||||||
|
*byte = brightness;
|
||||||
|
}
|
||||||
|
if let Err(e) = proxy.write(buffer) {
|
||||||
|
eprintln!("Error writing to device: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill a range of LEDs. Both start and end are INCLUSIVE.
|
||||||
|
fn fill_range(
|
||||||
|
proxy: &AnimeProxyBlocking,
|
||||||
|
anime_type: AnimeType,
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
brightness: u8,
|
||||||
|
) {
|
||||||
|
let mut buffer = AnimeDataBuffer::new(anime_type);
|
||||||
|
let data = buffer.data_mut();
|
||||||
|
for i in start..=end.min(data.len().saturating_sub(1)) {
|
||||||
|
data[i] = brightness;
|
||||||
|
}
|
||||||
|
if let Err(e) = proxy.write(buffer) {
|
||||||
|
eprintln!("Error writing to device: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_pane(proxy: &AnimeProxyBlocking, anime_type: AnimeType, pane: usize, brightness: u8) {
|
||||||
|
const PANE_LEN: usize = 627;
|
||||||
|
let start = pane * PANE_LEN;
|
||||||
|
let end = start + PANE_LEN - 1;
|
||||||
|
fill_range(proxy, anime_type, start, end, brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// G835L row pattern (PROVISIONAL - needs hardware verification):
|
||||||
|
/// - Rows 0-1: 1 LED each
|
||||||
|
/// - Rows 2-3: 2 LEDs each
|
||||||
|
/// - ... (pairs of rows with same length)
|
||||||
|
/// - Rows 26-27: 14 LEDs each
|
||||||
|
/// - Rows 28+: 15 LEDs each (constant)
|
||||||
|
///
|
||||||
|
/// Returns (start_index, end_index_inclusive, row_length)
|
||||||
|
fn g835l_row_bounds(row: usize) -> (usize, usize, usize) {
|
||||||
|
let triangle_rows = 28;
|
||||||
|
let triangle_leds = 210;
|
||||||
|
|
||||||
|
if row < triangle_rows {
|
||||||
|
let length = row / 2 + 1;
|
||||||
|
let mut start = 0usize;
|
||||||
|
for r in 0..row {
|
||||||
|
start += r / 2 + 1;
|
||||||
|
}
|
||||||
|
(start, start + length - 1, length)
|
||||||
|
} else {
|
||||||
|
let rows_after_triangle = row - triangle_rows;
|
||||||
|
let start = triangle_leds + rows_after_triangle * 15;
|
||||||
|
(start, start + 14, 15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn g835l_total_rows() -> usize {
|
||||||
|
28 + 40
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_state(proxy: &AnimeProxyBlocking) -> SavedState {
|
||||||
|
SavedState {
|
||||||
|
builtins_enabled: proxy.builtins_enabled().unwrap_or(false),
|
||||||
|
brightness: proxy.brightness().unwrap_or(Brightness::Med),
|
||||||
|
display_enabled: proxy.enable_display().unwrap_or(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_state(proxy: &AnimeProxyBlocking, state: &SavedState) {
|
||||||
|
let _ = proxy.set_builtins_enabled(state.builtins_enabled);
|
||||||
|
let _ = proxy.set_brightness(state.brightness);
|
||||||
|
let _ = proxy.set_enable_display(state.display_enabled);
|
||||||
|
let _ = proxy.run_main_loop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
|
let mut start_index = 0usize;
|
||||||
|
let mut brightness = 200u8;
|
||||||
|
let mut scan_limit: Option<usize> = None;
|
||||||
|
|
||||||
|
let mut i = 1;
|
||||||
|
while i < args.len() {
|
||||||
|
match args[i].as_str() {
|
||||||
|
"--start" | "-s" => {
|
||||||
|
if i + 1 < args.len() {
|
||||||
|
start_index = args[i + 1].parse().unwrap_or(0);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"--brightness" | "-b" => {
|
||||||
|
if i + 1 < args.len() {
|
||||||
|
brightness = args[i + 1].parse().unwrap_or(200);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"--limit" | "-l" => {
|
||||||
|
if i + 1 < args.len() {
|
||||||
|
scan_limit = args[i + 1].parse().ok();
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"--help" | "-h" => {
|
||||||
|
println!("LED Scan Tool for AniMe Matrix");
|
||||||
|
println!();
|
||||||
|
println!("Usage: anime-led-scan [options]");
|
||||||
|
println!();
|
||||||
|
println!("Options:");
|
||||||
|
println!(" -s, --start <N> Start at index N (default: 0)");
|
||||||
|
println!(" -b, --brightness <N> LED brightness 0-255 (default: 200)");
|
||||||
|
println!(" -l, --limit <N> Cap scan range to N indices (e.g. 810 for G835L)");
|
||||||
|
println!(" -h, --help Show this help");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = match Connection::system() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to connect to D-Bus: {}", e);
|
||||||
|
eprintln!("Make sure asusd is running.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let proxy = match AnimeProxyBlocking::new(&conn) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to create Anime proxy: {}", e);
|
||||||
|
eprintln!("Make sure asusd supports your device.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let anime_type = get_anime_type();
|
||||||
|
let buffer_len = anime_type.data_length();
|
||||||
|
let scan_len = scan_limit.unwrap_or(buffer_len).min(buffer_len);
|
||||||
|
|
||||||
|
println!("=== LED Scan Tool ===");
|
||||||
|
println!("Device type: {:?}", anime_type);
|
||||||
|
println!("Buffer length: {} bytes", buffer_len);
|
||||||
|
println!("Scan range: 0-{}", scan_len - 1);
|
||||||
|
println!("Brightness: {}", brightness);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Save current state for restoration
|
||||||
|
let saved_state = save_state(&proxy);
|
||||||
|
println!("Saved device state for restoration on exit.");
|
||||||
|
|
||||||
|
// Stop system animations
|
||||||
|
if let Err(e) = proxy.run_main_loop(false) {
|
||||||
|
eprintln!("Warning: Could not stop main loop: {}", e);
|
||||||
|
}
|
||||||
|
println!("Stopped system animations.");
|
||||||
|
|
||||||
|
print_help(scan_len, buffer_len);
|
||||||
|
|
||||||
|
let mut current_index = start_index.min(scan_len - 1);
|
||||||
|
let mut step = 1usize;
|
||||||
|
|
||||||
|
write_single_led(&proxy, anime_type, current_index, brightness);
|
||||||
|
println!(">>> Index: {} (step: {})", current_index, step);
|
||||||
|
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let mut input = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
input.clear();
|
||||||
|
print!("> ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
|
||||||
|
if stdin.lock().read_line(&mut input).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd = input.trim();
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
"q" | "quit" | "exit" => {
|
||||||
|
clear_display(&proxy, anime_type);
|
||||||
|
restore_state(&proxy, &saved_state);
|
||||||
|
println!("Restored device state. Goodbye!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
"n" | "" => {
|
||||||
|
current_index = (current_index + step).min(scan_len - 1);
|
||||||
|
write_single_led(&proxy, anime_type, current_index, brightness);
|
||||||
|
println!(">>> Index: {} (step: {})", current_index, step);
|
||||||
|
}
|
||||||
|
"p" => {
|
||||||
|
current_index = current_index.saturating_sub(step);
|
||||||
|
write_single_led(&proxy, anime_type, current_index, brightness);
|
||||||
|
println!(">>> Index: {} (step: {})", current_index, step);
|
||||||
|
}
|
||||||
|
"+" => {
|
||||||
|
step = step.saturating_mul(2).max(1);
|
||||||
|
println!("Step size: {}", step);
|
||||||
|
}
|
||||||
|
"-" => {
|
||||||
|
step = step.saturating_div(2).max(1);
|
||||||
|
println!("Step size: {}", step);
|
||||||
|
}
|
||||||
|
"r" => {
|
||||||
|
if let Err(e) = save_note(current_index, "ROW START") {
|
||||||
|
eprintln!("Error saving note: {}", e);
|
||||||
|
} else {
|
||||||
|
println!("Saved: Index {} marked as ROW START", current_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"h" | "help" | "?" => {
|
||||||
|
print_help(scan_len, buffer_len);
|
||||||
|
}
|
||||||
|
cmd if cmd.starts_with('j') => {
|
||||||
|
let num_str = cmd.trim_start_matches('j').trim();
|
||||||
|
if let Ok(idx) = num_str.parse::<usize>() {
|
||||||
|
if idx < scan_len {
|
||||||
|
current_index = idx;
|
||||||
|
write_single_led(&proxy, anime_type, current_index, brightness);
|
||||||
|
println!(">>> Index: {} (step: {})", current_index, step);
|
||||||
|
} else {
|
||||||
|
println!("Index {} out of range (max: {})", idx, scan_len - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Usage: j <number>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd if cmd.starts_with('s') && !cmd.starts_with("show") => {
|
||||||
|
let note = cmd.trim_start_matches('s').trim();
|
||||||
|
let note = if note.is_empty() { "observed" } else { note };
|
||||||
|
if let Err(e) = save_note(current_index, note) {
|
||||||
|
eprintln!("Error saving note: {}", e);
|
||||||
|
} else {
|
||||||
|
println!("Saved note for index {}", current_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"a" => {
|
||||||
|
println!("Auto-scan mode (0 to {})...", scan_len - 1);
|
||||||
|
let delay = std::time::Duration::from_millis(10);
|
||||||
|
for idx in current_index..scan_len {
|
||||||
|
write_single_led(&proxy, anime_type, idx, brightness);
|
||||||
|
print!("\rIndex: {} / {} ", idx, scan_len - 1);
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
std::thread::sleep(delay);
|
||||||
|
current_index = idx;
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
println!("Auto-scan complete. Current index: {}", current_index);
|
||||||
|
}
|
||||||
|
"c" => {
|
||||||
|
clear_display(&proxy, anime_type);
|
||||||
|
println!("Display cleared");
|
||||||
|
}
|
||||||
|
"f" => {
|
||||||
|
fill_display(&proxy, anime_type, brightness);
|
||||||
|
println!("All buffer bytes filled at brightness {}", brightness);
|
||||||
|
}
|
||||||
|
"p1" => {
|
||||||
|
fill_pane(&proxy, anime_type, 0, brightness);
|
||||||
|
println!("Pane 1 (indices 0-626) filled");
|
||||||
|
}
|
||||||
|
"p2" => {
|
||||||
|
fill_pane(&proxy, anime_type, 1, brightness);
|
||||||
|
println!("Pane 2 (indices 627-1253) filled");
|
||||||
|
}
|
||||||
|
"p3" => {
|
||||||
|
fill_pane(&proxy, anime_type, 2, brightness);
|
||||||
|
println!("Pane 3 (indices 1254-1880) filled");
|
||||||
|
}
|
||||||
|
cmd if cmd.starts_with("f ") => {
|
||||||
|
let parts: Vec<&str> = cmd.split_whitespace().collect();
|
||||||
|
if parts.len() == 3 {
|
||||||
|
if let (Ok(start), Ok(end)) =
|
||||||
|
(parts[1].parse::<usize>(), parts[2].parse::<usize>())
|
||||||
|
{
|
||||||
|
fill_range(&proxy, anime_type, start, end, brightness);
|
||||||
|
println!("Filled indices {} to {}", start, end);
|
||||||
|
} else {
|
||||||
|
println!("Usage: f <start> <end>");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Usage: f <start> <end>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"show" => {
|
||||||
|
write_single_led(&proxy, anime_type, current_index, brightness);
|
||||||
|
println!(">>> Index: {} (step: {})", current_index, step);
|
||||||
|
}
|
||||||
|
"row" => {
|
||||||
|
if anime_type != AnimeType::G835L {
|
||||||
|
println!("Warning: Row commands use G835L mapping (provisional). You can add to this code to support other types. `examples/anime-led-scan.rs[402:425]`");
|
||||||
|
}
|
||||||
|
println!("Row stepping mode. Press Enter for next row, 'q' to quit.");
|
||||||
|
let total = g835l_total_rows();
|
||||||
|
for row_num in 0..total {
|
||||||
|
let (start, end, len) = g835l_row_bounds(row_num);
|
||||||
|
if end >= scan_len {
|
||||||
|
println!("Row {} exceeds scan limit, stopping.", row_num);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("Row {}: indices {}-{} ({} LEDs)", row_num, start, end, len);
|
||||||
|
fill_range(&proxy, anime_type, start, end, brightness);
|
||||||
|
input.clear();
|
||||||
|
print!("(Enter=next, q=quit) > ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
if stdin.lock().read_line(&mut input).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if input.trim() == "q" {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
clear_display(&proxy, anime_type);
|
||||||
|
}
|
||||||
|
println!("Row stepping done.");
|
||||||
|
}
|
||||||
|
cmd if cmd.starts_with("row ") => {
|
||||||
|
if anime_type != AnimeType::G835L {
|
||||||
|
println!("Warning: Row commands use G835L mapping (provisional).");
|
||||||
|
}
|
||||||
|
let row_str = cmd.trim_start_matches("row ").trim();
|
||||||
|
if let Ok(row_num) = row_str.parse::<usize>() {
|
||||||
|
let total = g835l_total_rows();
|
||||||
|
if row_num < total {
|
||||||
|
let (start, end, len) = g835l_row_bounds(row_num);
|
||||||
|
if end < scan_len {
|
||||||
|
println!("Row {}: indices {}-{} ({} LEDs)", row_num, start, end, len);
|
||||||
|
fill_range(&proxy, anime_type, start, end, brightness);
|
||||||
|
} else {
|
||||||
|
println!("Row {} exceeds scan limit", row_num);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Row {} out of range (max: {})", row_num, total - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Usage: row <number>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"allrows" => {
|
||||||
|
if anime_type != AnimeType::G835L {
|
||||||
|
println!("Warning: Row commands use G835L mapping (provisional).");
|
||||||
|
}
|
||||||
|
println!("Lighting all rows sequentially (200ms each)...");
|
||||||
|
let total = g835l_total_rows();
|
||||||
|
let delay = std::time::Duration::from_millis(200);
|
||||||
|
for row_num in 0..total {
|
||||||
|
let (start, end, len) = g835l_row_bounds(row_num);
|
||||||
|
if end >= scan_len {
|
||||||
|
println!("\nRow {} exceeds scan limit, stopping.", row_num);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
print!(
|
||||||
|
"\rRow {}/{}: indices {}-{} ({} LEDs) ",
|
||||||
|
row_num,
|
||||||
|
total - 1,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
len
|
||||||
|
);
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
fill_range(&proxy, anime_type, start, end, brightness);
|
||||||
|
std::thread::sleep(delay);
|
||||||
|
clear_display(&proxy, anime_type);
|
||||||
|
}
|
||||||
|
println!("\nDone.");
|
||||||
|
}
|
||||||
|
"rowmap" => {
|
||||||
|
if anime_type != AnimeType::G835L {
|
||||||
|
println!("Warning: Row map is for G835L (provisional).");
|
||||||
|
}
|
||||||
|
println!("G835L Row Map:");
|
||||||
|
let total = g835l_total_rows();
|
||||||
|
for row_num in 0..total {
|
||||||
|
let (start, end, len) = g835l_row_bounds(row_num);
|
||||||
|
let marker = if end >= scan_len {
|
||||||
|
" (exceeds limit)"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
" Row {:2}: indices {:4}-{:4} ({:2} LEDs){}",
|
||||||
|
row_num, start, end, len, marker
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"hold" => {
|
||||||
|
// Single write, wait for Enter to release
|
||||||
|
println!("Holding index {}. Press Enter to release...", current_index);
|
||||||
|
write_single_led(&proxy, anime_type, current_index, brightness);
|
||||||
|
input.clear();
|
||||||
|
let _ = stdin.lock().read_line(&mut input);
|
||||||
|
clear_display(&proxy, anime_type);
|
||||||
|
println!("Released.");
|
||||||
|
}
|
||||||
|
cmd if cmd.starts_with("hold ") => {
|
||||||
|
let arg = cmd.trim_start_matches("hold ").trim();
|
||||||
|
let (start, end): (usize, usize) = match arg {
|
||||||
|
"p1" | "1" => (0, 626),
|
||||||
|
"p2" | "2" => (627, 1253),
|
||||||
|
_ => {
|
||||||
|
let parts: Vec<&str> = arg.split_whitespace().collect();
|
||||||
|
if parts.len() == 2 {
|
||||||
|
if let (Ok(s), Ok(e)) = (parts[0].parse(), parts[1].parse()) {
|
||||||
|
(s, e)
|
||||||
|
} else {
|
||||||
|
println!("Usage: hold p1, hold p2, or hold <start> <end>");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Usage: hold p1, hold p2, or hold <start> <end>");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!("Holding range {}-{}. Press Enter to release...", start, end);
|
||||||
|
fill_range(&proxy, anime_type, start, end, brightness);
|
||||||
|
input.clear();
|
||||||
|
let _ = stdin.lock().read_line(&mut input);
|
||||||
|
clear_display(&proxy, anime_type);
|
||||||
|
println!("Released.");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Ok(idx) = cmd.parse::<usize>() {
|
||||||
|
if idx < scan_len {
|
||||||
|
current_index = idx;
|
||||||
|
write_single_led(&proxy, anime_type, current_index, brightness);
|
||||||
|
println!(">>> Index: {} (step: {})", current_index, step);
|
||||||
|
} else {
|
||||||
|
println!("Index {} out of range (max: {})", idx, scan_len - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Unknown command: '{}'. Type 'h' for help.", cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -114,7 +114,7 @@ impl AnimeType {
|
|||||||
match self {
|
match self {
|
||||||
AnimeType::GU604 => 70,
|
AnimeType::GU604 => 70,
|
||||||
// TODO: Find G635L W*H
|
// TODO: Find G635L W*H
|
||||||
// AnimeType::G635L => 68
|
AnimeType::G635L => 68,
|
||||||
AnimeType::G835L => 68,
|
AnimeType::G835L => 68,
|
||||||
_ => 74,
|
_ => 74,
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ impl AnimeType {
|
|||||||
match self {
|
match self {
|
||||||
AnimeType::GA401 => 36,
|
AnimeType::GA401 => 36,
|
||||||
AnimeType::GU604 => 43,
|
AnimeType::GU604 => 43,
|
||||||
// AnimeType::G635L => 34,
|
AnimeType::G635L => 34,
|
||||||
AnimeType::G835L => 34,
|
AnimeType::G835L => 34,
|
||||||
_ => 39,
|
_ => 39,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ impl AnimeDiagonal {
|
|||||||
/// Generate the base image from inputs. The result can be displayed as is
|
/// Generate the base image from inputs. The result can be displayed as is
|
||||||
/// or updated via scale, position, or angle then displayed again after
|
/// or updated via scale, position, or angle then displayed again after
|
||||||
/// `update()`.
|
/// `update()`.
|
||||||
///
|
|
||||||
/// TODO: G835L and G635L only supports grayscale
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_png(
|
pub fn from_png(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
@@ -385,8 +383,6 @@ impl AnimeDiagonal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement `to_g635l_packets` and `to_g835l_packets` functions
|
// TODO: Implement `to_g635l_packets` and `to_g835l_packets` functions
|
||||||
// IMPORTANT NOTE: G635L and G835L ONLY SUPPORT GRAYSCALE
|
|
||||||
//
|
|
||||||
// fn to_g835l_packets(buf: &[u8]) -> Result<AnimeDataBuffer> {
|
// fn to_g835l_packets(buf: &[u8]) -> Result<AnimeDataBuffer> {
|
||||||
// let mut buf = vec![0u8; AnimeType::GU604.data_length()];
|
// let mut buf = vec![0u8; AnimeType::GU604.data_length()];
|
||||||
|
|
||||||
|
|||||||
BIN
rog-anime/tests/data/g835l-diagonal-fullbright.png
Normal file
BIN
rog-anime/tests/data/g835l-diagonal-fullbright.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 187 B |
BIN
rog-anime/tests/data/g835l-diagonal.gif
Normal file
BIN
rog-anime/tests/data/g835l-diagonal.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 829 B |
BIN
rog-anime/tests/data/g835l-diagonal.png
Normal file
BIN
rog-anime/tests/data/g835l-diagonal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 B |
@@ -102,8 +102,9 @@ pub fn setup_window(config: Arc<Mutex<Config>>) -> MainWindow {
|
|||||||
available.contains(&"xyz.ljones.Aura".to_string()),
|
available.contains(&"xyz.ljones.Aura".to_string()),
|
||||||
available.contains(&"xyz.ljones.Anime".to_string()),
|
available.contains(&"xyz.ljones.Anime".to_string()),
|
||||||
available.contains(&"xyz.ljones.FanCurves".to_string()),
|
available.contains(&"xyz.ljones.FanCurves".to_string()),
|
||||||
true,
|
true, // GPU Configuration
|
||||||
true,
|
true, // App Settings
|
||||||
|
true, // About
|
||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { PageFans } from "pages/fans.slint";
|
|||||||
import { PageAnime, AnimePageData } from "pages/anime.slint";
|
import { PageAnime, AnimePageData } from "pages/anime.slint";
|
||||||
import { RogItem } from "widgets/common.slint";
|
import { RogItem } from "widgets/common.slint";
|
||||||
import { PageAura } from "pages/aura.slint";
|
import { PageAura } from "pages/aura.slint";
|
||||||
|
import { PageGPU } from "pages/gpu.slint";
|
||||||
import { Node } from "widgets/graph.slint";
|
import { Node } from "widgets/graph.slint";
|
||||||
export { Node }
|
export { Node }
|
||||||
import { FanPageData, FanType, Profile } from "types/fan_types.slint";
|
import { FanPageData, FanType, Profile } from "types/fan_types.slint";
|
||||||
@@ -24,7 +25,15 @@ export component MainWindow inherits Window {
|
|||||||
default-font-size: 14px;
|
default-font-size: 14px;
|
||||||
default-font-weight: 400;
|
default-font-weight: 400;
|
||||||
icon: @image-url("../data/rog-control-center.png");
|
icon: @image-url("../data/rog-control-center.png");
|
||||||
in property <[bool]> sidebar_items_avilable: [true, true, true, true, true, true];
|
in property <[bool]> sidebar_items_avilable: [
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true, // GPU Configuration
|
||||||
|
true, // App Settings
|
||||||
|
true, // About
|
||||||
|
];
|
||||||
private property <bool> show_notif;
|
private property <bool> show_notif;
|
||||||
private property <bool> fade_cover;
|
private property <bool> fade_cover;
|
||||||
private property <bool> toast: false;
|
private property <bool> toast: false;
|
||||||
@@ -58,8 +67,9 @@ export component MainWindow inherits Window {
|
|||||||
@tr("Menu2" => "Keyboard Aura"),
|
@tr("Menu2" => "Keyboard Aura"),
|
||||||
@tr("Menu3" => "AniMe Matrix"),
|
@tr("Menu3" => "AniMe Matrix"),
|
||||||
@tr("Menu4" => "Fan Curves"),
|
@tr("Menu4" => "Fan Curves"),
|
||||||
@tr("Menu5" => "App Settings"),
|
@tr("Menu5" => "GPU Configuration"),
|
||||||
@tr("Menu6" => "About"),
|
@tr("Menu6" => "App Settings"),
|
||||||
|
@tr("Menu7" => "About"),
|
||||||
];
|
];
|
||||||
available: root.sidebar_items_avilable;
|
available: root.sidebar_items_avilable;
|
||||||
}
|
}
|
||||||
@@ -89,26 +99,34 @@ export component MainWindow inherits Window {
|
|||||||
height: root.height + 12px;
|
height: root.height + 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
aura := PageAura {
|
/*if(side-bar.current-item == 1):*/ aura := PageAura {
|
||||||
width: root.width - side-bar.width;
|
width: root.width - side-bar.width;
|
||||||
visible: side-bar.current-item == 1;
|
visible: side-bar.current-item == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(side-bar.current-item == 2): PageAnime {
|
if(side-bar.current-item == 2): PageAnime {
|
||||||
width: root.width - side-bar.width;
|
width: root.width - side-bar.width;
|
||||||
|
visible: side-bar.current-item == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
fans := PageFans {
|
if(side-bar.current-item == 3): fans := PageFans {
|
||||||
width: root.width - side-bar.width;
|
width: root.width - side-bar.width;
|
||||||
visible: side-bar.current-item == 3;
|
visible: side-bar.current-item == 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(side-bar.current-item == 4): PageAppSettings {
|
if(side-bar.current-item == 4): PageGPU {
|
||||||
width: root.width - side-bar.width;
|
width: root.width - side-bar.width;
|
||||||
|
visible: side-bar.current-item == 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(side-bar.current-item == 5): PageAbout {
|
if(side-bar.current-item == 5): PageAppSettings {
|
||||||
width: root.width - side-bar.width;
|
width: root.width - side-bar.width;
|
||||||
|
visible: side-bar.current-item == 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(side-bar.current-item == 6): PageAbout {
|
||||||
|
width: root.width - side-bar.width;
|
||||||
|
visible: side-bar.current-item == 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
if toast: Rectangle {
|
if toast: Rectangle {
|
||||||
|
|||||||
102
rog-control-center/ui/pages/gpu.slint
Normal file
102
rog-control-center/ui/pages/gpu.slint
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { Palette, TabWidget, Button, CheckBox } from "std-widgets.slint";
|
||||||
|
import { Graph, Node } from "../widgets/graph.slint";
|
||||||
|
import { SystemToggle } from "../widgets/common.slint";
|
||||||
|
import { Profile, FanType, FanPageData } from "../types/fan_types.slint";
|
||||||
|
import { Palette, HorizontalBox , VerticalBox, ScrollView, Slider, Button, Switch, ComboBox, GroupBox, StandardButton} from "std-widgets.slint";
|
||||||
|
|
||||||
|
export global GPUPageData {
|
||||||
|
// GPU mode and device state
|
||||||
|
in-out property <int> gpu_mux_mode: 1; // 0 = Ultra/Discreet, 1 = Integrated/Optimus
|
||||||
|
in-out property <int> dgpu_disabled: 0; // 1 == dGPU disabled
|
||||||
|
in-out property <int> egpu_enabled: 0; // 1 == eGPU (XGMobile) enabled
|
||||||
|
callback cb_gpu_mux_mode(int);
|
||||||
|
callback cb_dgpu_disabled(int);
|
||||||
|
callback cb_egpu_enabled(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
export component PageGPU inherits Rectangle {
|
||||||
|
|
||||||
|
clip: true;
|
||||||
|
ScrollView {
|
||||||
|
VerticalLayout {
|
||||||
|
padding: 10px;
|
||||||
|
spacing: 10px;
|
||||||
|
alignment: LayoutAlignment.start;
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-color: Palette.accent-background;
|
||||||
|
border-width: 3px;
|
||||||
|
border-radius: 10px;
|
||||||
|
height: 40px;
|
||||||
|
Text {
|
||||||
|
font-size: 18px;
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
horizontal-alignment: TextHorizontalAlignment.center;
|
||||||
|
text: @tr("GPU Configuration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupBox {
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: 10px;
|
||||||
|
|
||||||
|
// Ultra (discreet) mode button - disabled if dGPU is marked disabled
|
||||||
|
Rectangle {
|
||||||
|
width: 120px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
border-width: 2px;
|
||||||
|
background: GPUPageData.gpu_mux_mode == 0 ? Palette.accent-background : Palette.control-background;
|
||||||
|
opacity: GPUPageData.dgpu_disabled == 1 ? 0.5 : 1.0;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
horizontal-alignment: TextHorizontalAlignment.center;
|
||||||
|
vertical-alignment: TextVerticalAlignment.center;
|
||||||
|
text: @tr("Ultra");
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchArea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
clicked => {
|
||||||
|
if (GPUPageData.dgpu_disabled != 1 && GPUPageData.gpu_mux_mode != 0) {
|
||||||
|
GPUPageData.cb_gpu_mux_mode(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integrated (Optimus) mode button
|
||||||
|
Rectangle {
|
||||||
|
width: 120px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
border-width: 2px;
|
||||||
|
background: GPUPageData.gpu_mux_mode == 1 ? Palette.accent-background : Palette.control-background;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
horizontal-alignment: TextHorizontalAlignment.center;
|
||||||
|
vertical-alignment: TextVerticalAlignment.center;
|
||||||
|
text: @tr("Integrated");
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchArea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
clicked => {
|
||||||
|
if (GPUPageData.gpu_mux_mode != 1) {
|
||||||
|
GPUPageData.cb_gpu_mux_mode(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -253,6 +253,13 @@ export component PageSystem inherits Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if SystemPageData.kbd_leds_awake != -1 ||
|
||||||
|
SystemPageData.kbd_leds_sleep != -1 ||
|
||||||
|
SystemPageData.kbd_leds_boot != -1 ||
|
||||||
|
SystemPageData.kbd_leds_shutdown != -1: VerticalLayout {
|
||||||
|
padding: 0px;
|
||||||
|
spacing: 0px;
|
||||||
|
alignment: LayoutAlignment.start;
|
||||||
Rectangle {
|
Rectangle {
|
||||||
background: Palette.alternate-background;
|
background: Palette.alternate-background;
|
||||||
border-color: Palette.accent-background;
|
border-color: Palette.accent-background;
|
||||||
@@ -263,31 +270,12 @@ export component PageSystem inherits Rectangle {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: Palette.control-foreground;
|
color: Palette.control-foreground;
|
||||||
horizontal-alignment: TextHorizontalAlignment.center;
|
horizontal-alignment: TextHorizontalAlignment.center;
|
||||||
text: @tr("Armoury settings");
|
text: @tr("Keyboard Power Management");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !SystemPageData.asus_armoury_loaded: Rectangle {
|
|
||||||
border-width: 3px;
|
|
||||||
border-color: red;
|
|
||||||
max-height: 30px;
|
|
||||||
VerticalBox {
|
|
||||||
Text {
|
|
||||||
text: @tr("no_asus_armoury_driver_1" => "The asus-armoury driver is not loaded");
|
|
||||||
font-size: 16px;
|
|
||||||
horizontal-alignment: TextHorizontalAlignment.center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: @tr("no_asus_armoury_driver_2" => "For advanced features you will require a kernel with this driver added.");
|
|
||||||
font-size: 16px;
|
|
||||||
horizontal-alignment: TextHorizontalAlignment.center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupBox {
|
GroupBox {
|
||||||
title: @tr("Keyboard Power Management");
|
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
spacing: 10px;
|
spacing: 10px;
|
||||||
if SystemPageData.kbd_leds_awake != -1: SystemToggleInt {
|
if SystemPageData.kbd_leds_awake != -1: SystemToggleInt {
|
||||||
@@ -323,6 +311,40 @@ export component PageSystem inherits Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-color: Palette.accent-background;
|
||||||
|
border-width: 3px;
|
||||||
|
border-radius: 10px;
|
||||||
|
height: 40px;
|
||||||
|
Text {
|
||||||
|
font-size: 18px;
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
horizontal-alignment: TextHorizontalAlignment.center;
|
||||||
|
text: @tr("Armoury settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !SystemPageData.asus_armoury_loaded: Rectangle {
|
||||||
|
border-width: 3px;
|
||||||
|
border-color: red;
|
||||||
|
max-height: 30px;
|
||||||
|
VerticalBox {
|
||||||
|
Text {
|
||||||
|
text: @tr("no_asus_armoury_driver_1" => "The asus-armoury driver is not loaded");
|
||||||
|
font-size: 16px;
|
||||||
|
horizontal-alignment: TextHorizontalAlignment.center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: @tr("no_asus_armoury_driver_2" => "For advanced features you will require a kernel with this driver added.");
|
||||||
|
font-size: 16px;
|
||||||
|
horizontal-alignment: TextHorizontalAlignment.center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HorizontalBox {
|
HorizontalBox {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use zbus::proxy;
|
|||||||
#[proxy(
|
#[proxy(
|
||||||
interface = "xyz.ljones.Anime",
|
interface = "xyz.ljones.Anime",
|
||||||
default_service = "xyz.ljones.Asusd",
|
default_service = "xyz.ljones.Asusd",
|
||||||
default_path = "/xyz/ljones"
|
default_path = "/xyz/ljones/aura/anime"
|
||||||
)]
|
)]
|
||||||
pub trait Anime {
|
pub trait Anime {
|
||||||
/// DeviceState method
|
/// DeviceState method
|
||||||
|
|||||||
Reference in New Issue
Block a user