Try to implement slash bar functionality - part 1

This commit is contained in:
jochen@g14
2024-03-25 01:54:05 +01:00
parent 1366422d96
commit cdc9ca7b58
33 changed files with 887 additions and 367 deletions

View File

@@ -2,28 +2,39 @@
name = "rog_slash"
license = "MPL-2.0"
version.workspace = true
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
repository = "https://gitlab.com/asus-linux/asus-nb-ctrl"
homepage = "https://gitlab.com/asus-linux/asus-nb-ctrl"
documentation = "https://docs.rs/rog-anime"
documentation = "https://docs.rs/rog-slash"
description = "ASUS Slash display"
keywords = ["ROG", "ASUS", "AniMe", "Slash"]
edition = "2024"
edition = "2021"
exclude = ["data"]
[features]
default = ["dbus", "detect"]
dbus = ["zbus"]
detect = ["dmi_id"]
[lib]
name = "rog_slash"
path = "src/lib.rs"
[dependencies]
png_pong.workspace = true
pix.workspace = true
gif.workspace = true
log.workspace = true
serde.workspace = true
serde_derive.workspace = true
zbus.workspace = true
concat-idents.workspace = true
udev.workspace = true
inotify.workspace = true
glam.workspace = true
typeshare.workspace = true
rusb.workspace = true
zbus = { workspace = true, optional = true }
dmi_id = { path = "../dmi-id", optional = true }
[dev-dependencies]
cargo-husky.workspace = true
rog_aura = { path = "../rog-aura" }
cargo-husky.workspace = true

105
rog-slash/src/data.rs Normal file
View File

@@ -0,0 +1,105 @@
use std::str::FromStr;
use serde_derive::{Deserialize, Serialize};
use typeshare::typeshare;
#[cfg(feature = "dbus")]
use zbus::zvariant::Type;
use crate::error::SlashError;
#[typeshare]
#[cfg_attr(feature = "dbus", derive(Type), zvariant(signature = "s"))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum SlashType {
GA403,
Unknown,
}
impl FromStr for SlashType {
type Err = SlashError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(match s {
"ga403" | "GA403" => Self::GA403,
_ => Self::Unknown,
})
}
}
#[typeshare]
// #[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum SlashMode {
Bounce = 0x10,
Slash = 0x12,
Loading = 0x13,
BitStream = 0x1D,
Transmission = 0x1A,
Flow = 0x19,
Flux = 0x25,
Phantom = 0x24,
Spectrum = 0x26,
Hazard = 0x32,
Interfacing = 0x33,
Ramp = 0x34,
GameOver = 0x42,
Start = 0x43,
Buzzer = 0x44,
}
impl SlashMode {
pub const fn to_string(&self) -> &str
{
match &self {
SlashMode::Bounce => "Bounce",
SlashMode::Slash => "Slash",
SlashMode::Loading => "Loading",
SlashMode::BitStream => "BitStream",
SlashMode::Transmission => "Transmission",
SlashMode::Flow => "Flow",
SlashMode::Flux => "Flux",
SlashMode::Phantom => "Phantom",
SlashMode::Spectrum => "Spectrum",
SlashMode::Hazard => "Hazard",
SlashMode::Interfacing => "Interfacing",
SlashMode::Ramp => "Ramp",
SlashMode::GameOver => "GameOver",
SlashMode::Start => "Start",
SlashMode::Buzzer => "Buzzer",
}
}
pub const fn list() -> [&'static str; 15] {
[
SlashMode::Bounce.to_string(),
SlashMode::Slash.to_string(),
SlashMode::Loading.to_string(),
SlashMode::BitStream.to_string(),
SlashMode::Transmission.to_string(),
SlashMode::Flow.to_string(),
SlashMode::Flux.to_string(),
SlashMode::Phantom.to_string(),
SlashMode::Spectrum.to_string(),
SlashMode::Hazard.to_string(),
SlashMode::Interfacing.to_string(),
SlashMode::Ramp.to_string(),
SlashMode::GameOver.to_string(),
SlashMode::Start.to_string(),
SlashMode::Buzzer.to_string(),
]
}
}
// TODO: move this out
#[typeshare]
#[cfg_attr(feature = "dbus", derive(Type))]
#[typeshare]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
pub struct DeviceState {
pub slash_enabled: bool,
pub slash_brightness: u8,
pub slash_interval: u8,
pub slash_mode: u8,
}

39
rog-slash/src/error.rs Normal file
View File

@@ -0,0 +1,39 @@
use std::error::Error;
use std::fmt;
pub type Result<T> = std::result::Result<T, SlashError>;
#[derive(Debug)]
pub enum SlashError {
Dbus(String),
Udev(String, std::io::Error),
NoDevice,
UnsupportedDevice,
DataBufferLength,
ParseError(String),
}
impl fmt::Display for SlashError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SlashError::ParseError(e) => write!(f, "Could not parse {e}"),
SlashError::Dbus(detail) => write!(f, "{}", detail),
SlashError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
SlashError::NoDevice => write!(f, "No Slash device found"),
SlashError::DataBufferLength => write!(
f,
"The data buffer was incorrect length for generating USB packets"
),
SlashError::UnsupportedDevice => write!(f, "Unsupported Slash device found"),
}
}
}
impl Error for SlashError {}
impl From<SlashError> for zbus::fdo::Error {
#[inline]
fn from(err: SlashError) -> Self {
zbus::fdo::Error::Failed(format!("{}", err))
}
}

View File

@@ -1,3 +1,12 @@
/// The main data conversion for transfering in shortform over dbus or other,
/// or writing directly to the USB device
mod data;
pub use data::*;
/// Base errors that are possible
pub mod error;
/// Provides const methods to create the USB HID control packets
pub mod usb;
pub mod usb;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -1,345 +1,129 @@
//! Utils for writing to the `AniMe` USB device
//! Utils for writing to the `Slash` 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`
//! `get_init_packets()` 2. Write data from `SLashPacketType`
//! 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;
//! Step 1 needs to be applied only on fresh system boot.
use dmi_id::DMIID;
use serde_derive::{Deserialize, Serialize};
use typeshare::typeshare;
#[cfg(feature = "dbus")]
use zbus::zvariant::{OwnedValue, Type, Value};
use crate::error::SlashError;
use crate::SlashType;
use crate::error::AnimeError;
use crate::AnimeType;
const PACKET_SIZE: usize = 640;
const PACKET_SIZE: usize = 128;
const DEV_PAGE: u8 = 0x5e;
pub const VENDOR_ID: u16 = 0x0b05;
pub const PROD_ID: u16 = 0x193b;
pub const VENDOR_ID: u16 = 0x0B05;
pub const PROD_ID: u16 = 0x193B;
pub const PROD_ID_STR: &str = "193B";
#[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,
}
pub type SlashUsbPacket = [u8;PACKET_SIZE];
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`.
/// The currently known USB device is `193B`.
#[inline]
pub fn get_anime_type() -> Result<AnimeType, AnimeError> {
let dmi = DMIID::new().map_err(|_| AnimeError::NoDevice)?; // TODO: better error
pub fn get_slash_type() -> Result<SlashType, SlashError> {
let dmi = DMIID::new().map_err(|_| SlashError::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);
if board_name.contains("GA403") {
return Ok(SlashType::GA403);
}
log::warn!("AniMe Matrix device found but not yet supported, will default to a GA402 layout");
Ok(AnimeType::Unknown)
log::warn!("AniMe Slash device found but not yet supported, will default to a GA403 layout");
Ok(SlashType::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
pub const fn pkts_for_init() -> [SlashUsbPacket; 2] {
let mut pkt1 = [0;PACKET_SIZE];
pkt1[0] = DEV_PAGE;
pkt1[1] = 0xD7;
pkt1[2] = 0x00;
pkt1[3] = 0x00;
pkt1[4] = 0x01;
pkt1[5] = 0xAC;
let mut pkt2 = [0;PACKET_SIZE];
pkt2[0] = DEV_PAGE;
pkt2[1] = 0xD2;
pkt2[2] = 0x02;
pkt2[3] = 0x01;
pkt2[4] = 0x08;
pkt2[5] = 0xAB;
[pkt1, pkt2]
}
/// 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];
pub const fn pkt_save() -> SlashUsbPacket {
let mut pkt = [0;PACKET_SIZE];
pkt[0] = DEV_PAGE;
pkt[1] = 0xc0;
pkt[1] = 0xD4;
pkt[2] = 0x00;
pkt[3] = 0x00;
pkt[4] = 0x01;
pkt[5] = 0xAB;
pkt
}
#[inline]
pub const fn pkt_set_mode(mode: u8) -> [SlashUsbPacket; 2] {
let mut pkt1 = [0;PACKET_SIZE];
pkt1[0] = DEV_PAGE;
pkt1[1] = 0x02;
pkt1[2] = 0x03;
pkt1[3] = 0x00;
pkt1[4] = 0x0C;
let mut pkt2 = [0;PACKET_SIZE];
pkt2[0] = DEV_PAGE;
pkt2[1] = 0xD3;
pkt2[2] = 0x04;
pkt2[3] = 0x00;
pkt2[4] = 0x0C;
pkt2[5] = 0x01;
pkt2[6] = mode;
pkt2[7] = 0x02;
pkt2[8] = 0x19;
pkt2[9] = 0x03;
pkt2[10] = 0x13;
pkt2[11] = 0x04;
pkt2[12] = 0x11;
pkt2[13] = 0x05;
pkt2[14] = 0x12;
pkt2[15] = 0x06;
pkt2[16] = 0x13;
[pkt1, pkt2]
}
#[inline]
pub const fn pkt_set_options(enabled: bool, brightness: u8, interval: u8) -> SlashUsbPacket {
let status_byte = if enabled { 0x01 } else { 0x00 };
let mut pkt = [0;PACKET_SIZE];
pkt[0] = DEV_PAGE;
pkt[1] = 0xD3;
pkt[2] = 0x03;
pkt
}
pkt[3] = 0x01;
pkt[4] = 0x08;
pkt[5] = 0xAB;
pkt[6] = 0xFF;
pkt[7] = 0x01;
pkt[8] = status_byte;
pkt[9] = 0x06;
pkt[10] = brightness;
pkt[11] = 0xFF;
pkt[12] = interval;
/// 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
}