mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
346 lines
8.4 KiB
Rust
346 lines
8.4 KiB
Rust
//! Utils for writing to the `AniMe` USB device
|
|
//!
|
|
//! Use of the device requires a few steps:
|
|
//! 1. Initialise the device by writing the two packets from
|
|
//! `get_init_packets()` 2. Write data from `AnimePacketType`
|
|
//! 3. Write the packet from `get_flush_packet()`, which tells the device to
|
|
//! display the data from step 2
|
|
//!
|
|
//! Step 1 need to applied only on fresh system boot.
|
|
|
|
use std::str::FromStr;
|
|
|
|
use dmi_id::DMIID;
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use typeshare::typeshare;
|
|
#[cfg(feature = "dbus")]
|
|
use zbus::zvariant::{OwnedValue, Type, Value};
|
|
|
|
use crate::error::AnimeError;
|
|
use crate::AnimeType;
|
|
|
|
const PACKET_SIZE: usize = 640;
|
|
const DEV_PAGE: u8 = 0x5e;
|
|
pub const VENDOR_ID: u16 = 0x0b05;
|
|
pub const PROD_ID: u16 = 0x193b;
|
|
|
|
#[cfg_attr(
|
|
feature = "dbus",
|
|
derive(Type, Value, OwnedValue),
|
|
zvariant(signature = "u")
|
|
)]
|
|
#[typeshare]
|
|
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
|
|
/// Base LED brightness of the display
|
|
pub enum Brightness {
|
|
Off = 0,
|
|
Low = 1,
|
|
#[default]
|
|
Med = 2,
|
|
High = 3,
|
|
}
|
|
|
|
impl FromStr for Brightness {
|
|
type Err = AnimeError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Ok(match s {
|
|
"Off" | "off" => Brightness::Off,
|
|
"Low" | "low" => Brightness::Low,
|
|
"Med" | "med" => Brightness::Med,
|
|
"High" | "high" => Brightness::High,
|
|
_ => Brightness::Med,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<u8> for Brightness {
|
|
fn from(v: u8) -> Brightness {
|
|
match v {
|
|
0 => Brightness::Off,
|
|
1 => Brightness::Low,
|
|
3 => Brightness::High,
|
|
_ => Brightness::Med,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<i32> for Brightness {
|
|
fn from(v: i32) -> Brightness {
|
|
(v as u8).into()
|
|
}
|
|
}
|
|
|
|
impl From<Brightness> for i32 {
|
|
fn from(v: Brightness) -> i32 {
|
|
v as i32
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(
|
|
feature = "dbus",
|
|
derive(Type, Value, OwnedValue),
|
|
zvariant(signature = "s")
|
|
)]
|
|
#[typeshare]
|
|
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
|
|
pub enum AnimBooting {
|
|
#[default]
|
|
GlitchConstruction = 0,
|
|
StaticEmergence = 1,
|
|
}
|
|
|
|
impl FromStr for AnimBooting {
|
|
type Err = AnimeError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"GlitchConstruction" => Ok(Self::GlitchConstruction),
|
|
"StaticEmergence" => Ok(Self::StaticEmergence),
|
|
_ => Err(AnimeError::ParseError(s.to_owned())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<i32> for AnimBooting {
|
|
fn from(value: i32) -> Self {
|
|
match value {
|
|
0 => Self::GlitchConstruction,
|
|
1 => Self::StaticEmergence,
|
|
_ => Self::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<AnimBooting> for i32 {
|
|
fn from(value: AnimBooting) -> Self {
|
|
value as i32
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(
|
|
feature = "dbus",
|
|
derive(Type, Value, OwnedValue),
|
|
zvariant(signature = "s")
|
|
)]
|
|
#[typeshare]
|
|
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
|
|
pub enum AnimAwake {
|
|
#[default]
|
|
BinaryBannerScroll = 0,
|
|
RogLogoGlitch = 1,
|
|
}
|
|
|
|
impl FromStr for AnimAwake {
|
|
type Err = AnimeError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"BinaryBannerScroll" => Ok(Self::BinaryBannerScroll),
|
|
"RogLogoGlitch" => Ok(Self::RogLogoGlitch),
|
|
_ => Err(AnimeError::ParseError(s.to_owned())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<i32> for AnimAwake {
|
|
fn from(value: i32) -> Self {
|
|
match value {
|
|
0 => Self::BinaryBannerScroll,
|
|
1 => Self::RogLogoGlitch,
|
|
_ => Self::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<AnimAwake> for i32 {
|
|
fn from(value: AnimAwake) -> Self {
|
|
value as i32
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(
|
|
feature = "dbus",
|
|
derive(Type, Value, OwnedValue),
|
|
zvariant(signature = "s")
|
|
)]
|
|
#[typeshare]
|
|
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
|
|
pub enum AnimSleeping {
|
|
#[default]
|
|
BannerSwipe = 0,
|
|
Starfield = 1,
|
|
}
|
|
|
|
impl FromStr for AnimSleeping {
|
|
type Err = AnimeError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"BannerSwipe" => Ok(Self::BannerSwipe),
|
|
"Starfield" => Ok(Self::Starfield),
|
|
_ => Err(AnimeError::ParseError(s.to_owned())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<i32> for AnimSleeping {
|
|
fn from(value: i32) -> Self {
|
|
match value {
|
|
0 => Self::BannerSwipe,
|
|
1 => Self::Starfield,
|
|
_ => Self::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<AnimSleeping> for i32 {
|
|
fn from(value: AnimSleeping) -> Self {
|
|
value as i32
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(
|
|
feature = "dbus",
|
|
derive(Type, Value, OwnedValue),
|
|
zvariant(signature = "s")
|
|
)]
|
|
#[typeshare]
|
|
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
|
|
pub enum AnimShutdown {
|
|
#[default]
|
|
GlitchOut = 0,
|
|
SeeYa = 1,
|
|
}
|
|
|
|
impl FromStr for AnimShutdown {
|
|
type Err = AnimeError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"GlitchOut" => Ok(Self::GlitchOut),
|
|
"SeeYa" => Ok(Self::SeeYa),
|
|
_ => Err(AnimeError::ParseError(s.to_owned())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<i32> for AnimShutdown {
|
|
fn from(value: i32) -> Self {
|
|
match value {
|
|
0 => Self::GlitchOut,
|
|
1 => Self::SeeYa,
|
|
_ => Self::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<AnimShutdown> for i32 {
|
|
fn from(value: AnimShutdown) -> Self {
|
|
value as i32
|
|
}
|
|
}
|
|
|
|
/// `get_anime_type` is very broad, matching on part of the laptop board name
|
|
/// only. For this reason `find_node()` must be used also to verify if the USB
|
|
/// device is available.
|
|
///
|
|
/// The currently known USB device is `19b6`.
|
|
#[inline]
|
|
pub fn get_anime_type() -> Result<AnimeType, AnimeError> {
|
|
let dmi = DMIID::new().map_err(|_| AnimeError::NoDevice)?; // TODO: better error
|
|
let board_name = dmi.board_name;
|
|
|
|
if board_name.contains("GA401I") || board_name.contains("GA401Q") {
|
|
return Ok(AnimeType::GA401);
|
|
} else if board_name.contains("GA402R") {
|
|
return Ok(AnimeType::GA402);
|
|
} else if board_name.contains("GU604V") {
|
|
return Ok(AnimeType::GU604);
|
|
}
|
|
log::warn!("AniMe Matrix device found but not yet supported, will default to a GA402 layout");
|
|
Ok(AnimeType::Unknown)
|
|
}
|
|
|
|
/// Get the two device initialization packets. These are required for device
|
|
/// start after the laptop boots.
|
|
#[inline]
|
|
pub const fn pkts_for_init() -> [[u8; PACKET_SIZE]; 2] {
|
|
let mut packets = [[0; PACKET_SIZE]; 2];
|
|
packets[0][0] = DEV_PAGE; // This is the USB page we're using throughout
|
|
let mut count = 0;
|
|
// TODO: memcpy or slice copy
|
|
let bytes = "ASUS Tech.Inc.".as_bytes();
|
|
while count < bytes.len() {
|
|
packets[0][count + 1] = bytes[count];
|
|
count += 1;
|
|
}
|
|
//
|
|
packets[1][0] = DEV_PAGE;
|
|
packets[1][1] = 0xc2;
|
|
packets
|
|
}
|
|
|
|
/// Should be written to the device after writing the two main data packets that
|
|
/// make up the display data packet
|
|
#[inline]
|
|
pub const fn pkt_flush() -> [u8; PACKET_SIZE] {
|
|
let mut pkt = [0; PACKET_SIZE];
|
|
pkt[0] = DEV_PAGE;
|
|
pkt[1] = 0xc0;
|
|
pkt[2] = 0x03;
|
|
pkt
|
|
}
|
|
|
|
/// Packet for setting the brightness (0-3). Requires
|
|
/// `pkt_for_apply()` to be written after.
|
|
#[inline]
|
|
pub const fn pkt_set_brightness(brightness: Brightness) -> [u8; PACKET_SIZE] {
|
|
let mut pkt = [0; PACKET_SIZE];
|
|
pkt[0] = DEV_PAGE;
|
|
pkt[1] = 0xc0;
|
|
pkt[2] = 0x04;
|
|
pkt[3] = brightness as u8;
|
|
pkt
|
|
}
|
|
|
|
/// Enable the display?
|
|
#[inline]
|
|
pub const fn pkt_set_enable_display(status: bool) -> [u8; PACKET_SIZE] {
|
|
let mut pkt = [0; PACKET_SIZE];
|
|
pkt[0] = DEV_PAGE;
|
|
pkt[1] = 0xc3;
|
|
pkt[2] = 0x01;
|
|
pkt[3] = if status { 0x00 } else { 0x80 };
|
|
pkt
|
|
}
|
|
|
|
/// Enable builtin animations?
|
|
#[inline]
|
|
pub const fn pkt_set_enable_powersave_anim(status: bool) -> [u8; PACKET_SIZE] {
|
|
let mut pkt = [0; PACKET_SIZE];
|
|
pkt[0] = DEV_PAGE;
|
|
pkt[1] = 0xc4;
|
|
pkt[2] = 0x01;
|
|
pkt[3] = if status { 0x00 } else { 0x80 };
|
|
pkt
|
|
}
|
|
|
|
/// Set which animations are shown for each stage
|
|
#[inline]
|
|
pub const fn pkt_set_builtin_animations(
|
|
boot: AnimBooting,
|
|
awake: AnimAwake,
|
|
sleep: AnimSleeping,
|
|
shutdown: AnimShutdown,
|
|
) -> [u8; PACKET_SIZE] {
|
|
let mut pkt = [0; PACKET_SIZE];
|
|
pkt[0] = DEV_PAGE;
|
|
pkt[1] = 0xc5;
|
|
pkt[2] = (awake as u8)
|
|
| ((sleep as u8) << 0x01)
|
|
| ((shutdown as u8) << 0x02)
|
|
| ((boot as u8) << 0x03);
|
|
pkt
|
|
}
|