use std::{
collections::HashSet,
str::{from_utf8, Utf8Error},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, RwLock,
},
time::Duration,
};
use hidapi::{HidApi, HidDevice, HidResult};
use image::DynamicImage;
use crate::{
error::MirajazzError,
images::convert_image_with_format,
state::{DeviceState, DeviceStateReader},
types::{DeviceInput, ImageFormat},
};
pub fn new_hidapi() -> HidResult<HidApi> {
HidApi::new()
}
pub fn refresh_device_list(hidapi: &mut HidApi) -> HidResult<()> {
hidapi.refresh_devices()
}
pub fn list_devices(hidapi: &HidApi, vids: &[u16]) -> Vec<(u16, u16, String)> {
hidapi
.device_list()
.filter_map(|d| {
if !vids.contains(&d.vendor_id()) {
return None;
}
if let Some(serial) = d.serial_number() {
Some((d.vendor_id(), d.product_id(), serial.to_string()))
} else {
None
}
})
.collect::<HashSet<_>>()
.into_iter()
.collect()
}
pub fn extract_str(bytes: &[u8]) -> Result<String, Utf8Error> {
Ok(from_utf8(bytes)?.replace('\0', "").to_string())
}
struct ImageCache {
key: u8,
image_data: Vec<u8>,
}
pub struct Device {
pub vid: u16,
pub pid: u16,
is_v2: bool,
key_count: usize,
encoder_count: usize,
packet_size: usize,
hid_device: HidDevice,
image_cache: RwLock<Vec<ImageCache>>,
initialized: AtomicBool,
}
impl Device {
pub fn connect(
hidapi: &HidApi,
vid: u16,
pid: u16,
serial: &str,
is_v2: bool,
key_count: usize,
encoder_count: usize,
) -> Result<Device, MirajazzError> {
let hid_device = hidapi.open_serial(vid, pid, serial)?;
Ok(Device {
vid,
pid,
is_v2,
key_count,
encoder_count,
packet_size: if is_v2 { 1024 } else { 512 },
hid_device,
image_cache: RwLock::new(vec![]),
initialized: false.into(),
})
}
}
impl Device {
pub fn key_count(&self) -> usize {
self.key_count
}
pub fn encoder_count(&self) -> usize {
self.encoder_count
}
pub fn manufacturer(&self) -> Result<String, MirajazzError> {
Ok(self
.hid_device
.get_manufacturer_string()?
.unwrap_or_else(|| "Unknown".to_string()))
}
pub fn product(&self) -> Result<String, MirajazzError> {
Ok(self
.hid_device
.get_product_string()?
.unwrap_or_else(|| "Unknown".to_string()))
}
pub fn serial_number(&self) -> Result<String, MirajazzError> {
let serial = self.hid_device.get_serial_number_string()?;
match serial {
Some(serial) => {
if serial.is_empty() {
Ok("Unknown".to_string())
} else {
Ok(serial)
}
}
None => Ok("Unknown".to_string()),
}
.map(|s| s.replace('\u{0001}', ""))
}
pub fn firmware_version(&self) -> Result<String, MirajazzError> {
let bytes = self.get_feature_report(0x01, 20)?;
Ok(extract_str(&bytes[0..])?)
}
fn initialize(&self) -> Result<(), MirajazzError> {
if self.initialized.load(Ordering::Acquire) {
return Ok(());
}
self.initialized.store(true, Ordering::Release);
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x44, 0x49, 0x53];
self.write_extended_data(&mut buf)?;
let mut buf = vec![
0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, 0x00, 0x00,
];
self.write_extended_data(&mut buf)?;
Ok(())
}
pub fn read_input(
&self,
timeout: Option<Duration>,
process_input: impl Fn(u8, u8) -> Result<DeviceInput, MirajazzError>,
) -> Result<DeviceInput, MirajazzError> {
self.initialize()?;
let data = self.read_data(512, timeout)?;
if data[0] == 0 {
return Ok(DeviceInput::NoData);
}
Ok(process_input(data[9], data[10])?)
}
pub fn reset(&self) -> Result<(), MirajazzError> {
self.initialize()?;
self.set_brightness(100)?;
self.clear_all_button_images()?;
Ok(())
}
pub fn set_brightness(&self, percent: u8) -> Result<(), MirajazzError> {
self.initialize()?;
let percent = percent.clamp(0, 100);
let mut buf = vec![
0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, percent,
];
self.write_extended_data(&mut buf)?;
Ok(())
}
fn send_image(&self, key: u8, image_data: &[u8]) -> Result<(), MirajazzError> {
let mut buf = vec![
0x00,
0x43,
0x52,
0x54,
0x00,
0x00,
0x42,
0x41,
0x54,
0x00,
0x00,
(image_data.len() >> 8) as u8,
image_data.len() as u8,
key + 1,
];
self.write_extended_data(&mut buf)?;
self.write_image_data_reports(image_data)?;
Ok(())
}
pub fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), MirajazzError> {
let cache_entry = ImageCache {
key,
image_data: image_data.to_vec(), };
self.image_cache.write()?.push(cache_entry);
Ok(())
}
pub fn clear_button_image(&self, key: u8) -> Result<(), MirajazzError> {
self.initialize()?;
let mut buf = vec![
0x00,
0x43,
0x52,
0x54,
0x00,
0x00,
0x43,
0x4c,
0x45,
0x00,
0x00,
0x00,
if key == 0xff { 0xff } else { key + 1 },
];
self.write_extended_data(&mut buf)?;
Ok(())
}
pub fn clear_all_button_images(&self) -> Result<(), MirajazzError> {
self.initialize()?;
self.clear_button_image(0xFF)?;
if self.is_v2 {
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
self.write_extended_data(&mut buf)?;
}
Ok(())
}
pub fn set_button_image(
&self,
key: u8,
image_format: ImageFormat,
image: DynamicImage,
) -> Result<(), MirajazzError> {
self.initialize()?;
let image_data = convert_image_with_format(image_format, image)?;
self.write_image(key, &image_data)?;
Ok(())
}
pub fn sleep(&self) -> Result<(), MirajazzError> {
self.initialize()?;
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4e];
self.write_extended_data(&mut buf)?;
Ok(())
}
pub fn keep_alive(&self) -> Result<(), MirajazzError> {
self.initialize()?;
let mut buf = vec![
0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4F, 0x4E, 0x4E, 0x45, 0x43, 0x54,
];
self.write_extended_data(&mut buf)?;
Ok(())
}
pub fn shutdown(&self) -> Result<(), MirajazzError> {
self.initialize()?;
let mut buf = vec![
0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4c, 0x45, 0x00, 0x00, 0x44, 0x43,
];
self.write_extended_data(&mut buf)?;
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4E];
self.write_extended_data(&mut buf)?;
Ok(())
}
pub fn flush(&self) -> Result<(), MirajazzError> {
self.initialize()?;
if self.image_cache.write()?.is_empty() {
return Ok(());
}
for image in self.image_cache.read()?.iter() {
self.send_image(image.key, &image.image_data)?;
}
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
self.write_extended_data(&mut buf)?;
self.image_cache.write()?.clear();
Ok(())
}
pub fn get_reader(self: &Arc<Self>) -> Arc<DeviceStateReader> {
#[allow(clippy::arc_with_non_send_sync)]
Arc::new(DeviceStateReader {
device: self.clone(),
states: Mutex::new(DeviceState {
buttons: vec![false; self.key_count],
encoders: vec![false; self.encoder_count],
}),
})
}
fn write_image_data_reports(&self, image_data: &[u8]) -> Result<(), MirajazzError> {
let image_report_length = self.packet_size + 1;
let image_report_header_length = 1;
let image_report_payload_length = image_report_length - image_report_header_length;
let mut page_number = 0;
let mut bytes_remaining = image_data.len();
while bytes_remaining > 0 {
let this_length = bytes_remaining.min(image_report_payload_length);
let bytes_sent = page_number * image_report_payload_length;
let mut buf: Vec<u8> = [0x00].to_vec();
buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
buf.extend(vec![0u8; image_report_length - buf.len()]);
self.write_data(&buf)?;
bytes_remaining -= this_length;
page_number += 1;
}
Ok(())
}
pub fn get_feature_report(
&self,
report_id: u8,
length: usize,
) -> Result<Vec<u8>, MirajazzError> {
let mut buff = vec![0u8; length];
buff.insert(0, report_id);
self.hid_device.get_feature_report(buff.as_mut_slice())?;
Ok(buff)
}
pub fn send_feature_report(&self, payload: &[u8]) -> Result<(), MirajazzError> {
self.hid_device.send_feature_report(payload)?;
Ok(())
}
pub fn read_data(
&self,
length: usize,
timeout: Option<Duration>,
) -> Result<Vec<u8>, MirajazzError> {
self.hid_device.set_blocking_mode(timeout.is_some())?;
let mut buf = vec![0u8; length];
match timeout {
Some(timeout) => self
.hid_device
.read_timeout(buf.as_mut_slice(), timeout.as_millis() as i32),
None => self.hid_device.read(buf.as_mut_slice()),
}?;
Ok(buf)
}
pub fn write_data(&self, payload: &[u8]) -> Result<usize, MirajazzError> {
Ok(self.hid_device.write(payload)?)
}
pub fn write_extended_data(&self, payload: &mut Vec<u8>) -> Result<usize, MirajazzError> {
payload.extend(vec![0u8; 1 + self.packet_size - payload.len()]);
Ok(self.hid_device.write(payload)?)
}
}