1#![cfg_attr(docsrs, feature(doc_cfg))]
6#![warn(missing_docs)]
7
8use std::collections::HashSet;
9use std::error::Error;
10use std::fmt::{Display, Formatter};
11use std::iter::zip;
12use std::str::Utf8Error;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::RwLock;
15use std::sync::{Arc, Mutex, PoisonError};
16use std::time::Duration;
17
18use crate::images::{convert_image, ImageRect};
19use hidapi::{HidApi, HidDevice, HidError, HidResult};
20use image::{DynamicImage, ImageError};
21
22use crate::info::{is_vendor_familiar, Kind};
23use crate::util::{ajazz03_read_input, mirabox_extend_packet, ajazz153_to_elgato_input, elgato_to_ajazz153, extract_str, inverse_key_index, get_feature_report, read_button_states, read_data, write_data};
24
25pub mod info;
27pub mod util;
29pub mod images;
31
32#[cfg(feature = "async")]
34#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
35pub mod asynchronous;
36#[cfg(feature = "async")]
37#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
38pub use asynchronous::AsyncAjazz;
39
40pub fn new_hidapi() -> HidResult<HidApi> {
44 HidApi::new()
45}
46
47pub fn refresh_device_list(hidapi: &mut HidApi) -> HidResult<()> {
49 hidapi.refresh_devices()
50}
51
52pub fn list_devices(hidapi: &HidApi) -> Vec<(Kind, String)> {
56 hidapi
57 .device_list()
58 .filter_map(|d| {
59 if !is_vendor_familiar(&d.vendor_id()) {
60 return None;
61 }
62
63 if let Some(serial) = d.serial_number() {
64 Some((Kind::from_vid_pid(d.vendor_id(), d.product_id())?, serial.to_string()))
65 } else {
66 None
67 }
68 })
69 .collect::<HashSet<_>>()
70 .into_iter()
71 .collect()
72}
73
74#[derive(Clone, Debug)]
76pub enum AjazzInput {
77 NoData,
79
80 ButtonStateChange(Vec<bool>),
82
83 EncoderStateChange(Vec<bool>),
85
86 EncoderTwist(Vec<i8>),
88}
89
90impl AjazzInput {
91 pub fn is_empty(&self) -> bool {
93 matches!(self, AjazzInput::NoData)
94 }
95}
96
97pub struct Ajazz {
99 kind: Kind,
101 device: HidDevice,
103 image_cache: RwLock<Vec<ImageCache>>,
105 initialized: AtomicBool,
107}
108
109struct ImageCache {
110 key: u8,
111 image_data: Vec<u8>,
112}
113
114impl Ajazz {
116 pub fn connect(hidapi: &HidApi, kind: Kind, serial: &str) -> Result<Ajazz, AjazzError> {
118 let device = hidapi.open_serial(kind.vendor_id(), kind.product_id(), serial)?;
119
120 Ok(Ajazz {
121 kind,
122 device,
123 image_cache: RwLock::new(vec![]),
124 initialized: false.into(),
125 })
126 }
127}
128
129impl Ajazz {
131 pub fn kind(&self) -> Kind {
133 self.kind
134 }
135
136 pub fn manufacturer(&self) -> Result<String, AjazzError> {
138 Ok(self.device.get_manufacturer_string()?.unwrap_or_else(|| "Unknown".to_string()))
139 }
140
141 pub fn product(&self) -> Result<String, AjazzError> {
143 Ok(self.device.get_product_string()?.unwrap_or_else(|| "Unknown".to_string()))
144 }
145
146 pub fn serial_number(&self) -> Result<String, AjazzError> {
148 let serial = self.device.get_serial_number_string()?;
149 match serial {
150 Some(serial) => {
151 if serial.is_empty() {
152 Ok("Unknown".to_string())
153 } else {
154 Ok(serial)
155 }
156 }
157 None => Ok("Unknown".to_string()),
158 }
159 }
160
161 pub fn firmware_version(&self) -> Result<String, AjazzError> {
163 let bytes = get_feature_report(&self.device, 0x01, 20)?;
164 Ok(extract_str(&bytes[0..])?)
165 }
166
167 fn initialize(&self) -> Result<(), AjazzError> {
169 if self.initialized.load(Ordering::Acquire) {
170 return Ok(());
171 }
172
173 self.initialized.store(true, Ordering::Release);
174
175 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x44, 0x49, 0x53];
176 mirabox_extend_packet(&self.kind, &mut buf);
177 write_data(&self.device, buf.as_slice())?;
178
179 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, 0x00, 0x00];
180 mirabox_extend_packet(&self.kind, &mut buf);
181 write_data(&self.device, buf.as_slice())?;
182
183 Ok(())
184 }
185
186 pub fn read_input(&self, timeout: Option<Duration>) -> Result<AjazzInput, AjazzError> {
188 self.initialize()?;
189 match &self.kind {
190 kind if kind.is_ajazz_v1() => {
191 let data = read_data(&self.device, 512, timeout)?;
192
193 if data[0] == 0 {
194 return Ok(AjazzInput::NoData);
195 }
196
197 let mut states = vec![0x01];
198 states.extend(vec![0u8; (self.kind.key_count() + 1) as usize]);
199
200 if data[9] != 0 {
201 let key = match self.kind {
202 Kind::Akp815 => inverse_key_index(&self.kind, data[9] - 1),
203 Kind::Akp153 | Kind::Akp153E | Kind::Akp153R => ajazz153_to_elgato_input(&self.kind, data[9] - 1),
204 _ => unimplemented!(),
205 };
206
207 states[(key + 1) as usize] = 0x1u8;
208 }
209
210 Ok(AjazzInput::ButtonStateChange(read_button_states(&self.kind, &states)))
211 }
212
213 kind if kind.is_ajazz_v2() => {
214 let data = read_data(&self.device, 512, timeout)?;
215
216 if data[0] == 0 {
217 return Ok(AjazzInput::NoData);
218 }
219
220 ajazz03_read_input(&self.kind, data[9], 0x01)
222 }
223
224 _ => Err(AjazzError::UnsupportedOperation),
225 }
226 }
227
228 pub fn reset(&self) -> Result<(), AjazzError> {
230 self.initialize()?;
231 self.set_brightness(100)?;
232 self.clear_all_button_images()?;
233 Ok(())
234 }
235
236 pub fn set_brightness(&self, percent: u8) -> Result<(), AjazzError> {
238 self.initialize()?;
239 let percent = percent.clamp(0, 100);
240
241 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, percent];
242
243 mirabox_extend_packet(&self.kind, &mut buf);
244
245 write_data(&self.device, buf.as_slice())?;
246
247 Ok(())
248 }
249
250 fn send_image(&self, key: u8, image_data: &[u8]) -> Result<(), AjazzError> {
251 if key >= self.kind.key_count() {
252 return Err(AjazzError::InvalidKeyIndex);
253 }
254
255 let key = match self.kind {
256 Kind::Akp153 | Kind::Akp153E | Kind::Akp153R => elgato_to_ajazz153(&self.kind, key),
257 Kind::Akp815 => inverse_key_index(&self.kind, key),
258 _ => key,
259 };
260
261 let mut buf = vec![
262 0x00,
263 0x43,
264 0x52,
265 0x54,
266 0x00,
267 0x00,
268 0x42,
269 0x41,
270 0x54,
271 0x00,
272 0x00,
273 (image_data.len() >> 8) as u8,
274 image_data.len() as u8,
275 key + 1,
276 ];
277
278 mirabox_extend_packet(&self.kind, &mut buf);
279
280 write_data(&self.device, buf.as_slice())?;
281
282 self.write_image_data_reports(image_data, WriteImageParameters::for_key(self.kind, image_data.len()), |page_number, this_length, last_package| {
283 vec![0x00]
284 })?;
285 Ok(())
286 }
287
288 pub fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), AjazzError> {
291 if matches!(self.kind, Kind::Akp03 | Kind::Akp03E | Kind::Akp03R | Kind::Akp03RRev2) && key >= 6 {
293 return Ok(());
294 }
295
296 let cache_entry = ImageCache {
297 key,
298 image_data: image_data.to_vec(), };
300
301 self.image_cache.write()?.push(cache_entry);
302
303 Ok(())
304 }
305
306 pub fn clear_button_image(&self, key: u8) -> Result<(), AjazzError> {
309 self.initialize()?;
310
311 let key = match self.kind {
312 Kind::Akp815 => inverse_key_index(&self.kind, key),
313 Kind::Akp153 | Kind::Akp153E | Kind::Akp153R => elgato_to_ajazz153(&self.kind, key),
314 _ => key,
315 };
316
317 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4c, 0x45, 0x00, 0x00, 0x00, if key == 0xff { 0xff } else { key + 1 }];
318
319 mirabox_extend_packet(&self.kind, &mut buf);
320
321 write_data(&self.device, buf.as_slice())?;
322
323 Ok(())
324 }
325
326 pub fn clear_all_button_images(&self) -> Result<(), AjazzError> {
329 self.initialize()?;
330 match self.kind {
331 kind if kind.is_ajazz_v1() => self.clear_button_image(0xff),
332 kind if kind.is_ajazz_v2() => {
333 self.clear_button_image(0xFF)?;
334
335 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
337 mirabox_extend_packet(&self.kind, &mut buf);
338 write_data(&self.device, buf.as_slice())?;
339
340 Ok(())
341 }
342 _ => {
343 for i in 0..self.kind.key_count() {
344 self.clear_button_image(i)?
345 }
346 Ok(())
347 }
348 }
349 }
350
351 pub fn set_button_image(&self, key: u8, image: DynamicImage) -> Result<(), AjazzError> {
354 self.initialize()?;
355 let image_data = convert_image(self.kind, image)?;
356 self.write_image(key, &image_data)?;
357 Ok(())
358 }
359
360 pub fn set_logo_image(&self, image: DynamicImage) -> Result<(), AjazzError> {
362 self.initialize()?;
363
364 if self.kind.lcd_strip_size().is_none() {
365 return Err(AjazzError::UnsupportedOperation);
366 }
367 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x4f, 0x47, 0x00, 0x12, 0xc3, 0xc0, 0x01];
369
370 mirabox_extend_packet(&self.kind, &mut buf);
371
372 write_data(&self.device, buf.as_slice())?;
373
374 let mut image_buffer: DynamicImage = DynamicImage::new_rgb8(854, 480);
375
376 let ratio = 854.0 / 480.0;
377
378 let mode = "cover";
379
380 match mode {
381 "contain" => {
382 let (image_w, image_h) = (image.width(), image.height());
383 let image_ratio = image_w as f32 / image_h as f32;
384
385 let (ws, hs) = if image_ratio > ratio {
386 (854, (854.0 / image_ratio) as u32)
387 } else {
388 ((480.0 * image_ratio) as u32, 480)
389 };
390
391 let resized_image = image.resize(ws, hs, image::imageops::FilterType::Nearest);
392 image::imageops::overlay(
393 &mut image_buffer,
394 &resized_image,
395 ((854 - resized_image.width()) / 2) as i64,
396 ((480 - resized_image.height()) / 2) as i64,
397 );
398 }
399 "cover" => {
400 let resized_image = image.resize_to_fill(854, 480, image::imageops::FilterType::Nearest);
401 image::imageops::overlay(
402 &mut image_buffer,
403 &resized_image,
404 ((854 - resized_image.width()) / 2) as i64,
405 ((480 - resized_image.height()) / 2) as i64,
406 );
407 }
408 _ => {
409 let (image_w, image_h) = (image.width(), image.height());
410 let image_ratio = image_w as f32 / image_h as f32;
411
412 let (ws, hs) = if image_ratio > ratio {
413 ((480.0 * image_ratio) as u32, 480)
414 } else {
415 (854, (854.0 / image_ratio) as u32)
416 };
417
418 let resized_image = image.resize(ws, hs, image::imageops::FilterType::Nearest);
419 image::imageops::overlay(
420 &mut image_buffer,
421 &resized_image,
422 ((854 - resized_image.width()) / 2) as i64,
423 ((480 - resized_image.height()) / 2) as i64,
424 );
425 }
426 }
427
428 let mut image_data = image_buffer.rotate90().fliph().flipv().into_rgb8().to_vec();
429 for x in (0..image_data.len()).step_by(3) {
430 (image_data[x], image_data[x + 2]) = (image_data[x + 2], image_data[x])
431 }
432
433 let image_report_length = match self.kind {
434 kind if kind.is_ajazz_v1() => 513,
435 kind if kind.is_ajazz_v2() => 1025,
436 _ => 1024,
437 };
438
439 let image_report_header_length = 1;
440
441 let image_report_payload_length = image_report_length - image_report_header_length;
442
443 let mut page_number = 0;
444 let mut bytes_remaining = image_data.len();
445
446 while bytes_remaining > 0 {
447 let this_length = bytes_remaining.min(image_report_payload_length);
448 let bytes_sent = page_number * image_report_payload_length;
449
450 let mut buf: Vec<u8> = vec![0x00];
452
453 buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
455
456 buf.extend(vec![0u8; image_report_length - buf.len()]);
458
459 write_data(&self.device, &buf)?;
460
461 bytes_remaining -= this_length;
462 page_number += 1;
463 }
464
465 Ok(())
466 }
467
468 pub fn sleep(&self) -> Result<(), AjazzError> {
470 self.initialize()?;
471
472 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4e];
473
474 mirabox_extend_packet(&self.kind, &mut buf);
475
476 write_data(&self.device, buf.as_slice())?;
477
478 Ok(())
479 }
480
481 pub fn keep_alive(&self) -> Result<(), AjazzError> {
483 self.initialize()?;
484
485 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4F, 0x4E, 0x4E, 0x45, 0x43, 0x54];
486 mirabox_extend_packet(&self.kind, &mut buf);
487 write_data(&self.device, buf.as_slice())?;
488 Ok(())
489 }
490
491 pub fn shutdown(&self) -> Result<(), AjazzError> {
493 self.initialize()?;
494
495 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4c, 0x45, 0x00, 0x00, 0x44, 0x43];
496 mirabox_extend_packet(&self.kind, &mut buf);
497 write_data(&self.device, buf.as_slice())?;
498
499 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4E];
500 mirabox_extend_packet(&self.kind, &mut buf);
501 write_data(&self.device, buf.as_slice())?;
502
503 Ok(())
504 }
505
506 pub fn flush(&self) -> Result<(), AjazzError> {
508 self.initialize()?;
509
510 if self.image_cache.write()?.is_empty() {
511 return Ok(());
512 }
513
514 for image in self.image_cache.read()?.iter() {
515 self.send_image(image.key, &image.image_data)?;
516 }
517
518 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
519
520 mirabox_extend_packet(&self.kind, &mut buf);
521
522 write_data(&self.device, buf.as_slice())?;
523
524 self.image_cache.write()?.clear();
525
526 Ok(())
527 }
528
529 pub fn get_reader(self: &Arc<Self>) -> Arc<DeviceStateReader> {
531 #[allow(clippy::arc_with_non_send_sync)]
532 Arc::new(DeviceStateReader {
533 device: self.clone(),
534 states: Mutex::new(DeviceState {
535 buttons: vec![false; self.kind.key_count() as usize],
536 encoders: vec![false; self.kind.encoder_count() as usize],
537 }),
538 })
539 }
540
541 fn write_image_data_reports<T>(&self, image_data: &[u8], parameters: WriteImageParameters, header_fn: T) -> Result<(), AjazzError>
542 where
543 T: Fn(usize, usize, bool) -> Vec<u8>,
544 {
545 let image_report_length = parameters.image_report_length;
546 let image_report_payload_length = parameters.image_report_payload_length;
547
548 let mut page_number = 0;
549 let mut bytes_remaining = image_data.len();
550
551 while bytes_remaining > 0 {
552 let this_length = bytes_remaining.min(image_report_payload_length);
553 let bytes_sent = page_number * image_report_payload_length;
554
555 let mut buf: Vec<u8> = header_fn(page_number, this_length, this_length == bytes_remaining);
557
558 buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
559
560 buf.extend(vec![0u8; image_report_length - buf.len()]);
562
563 write_data(&self.device, &buf)?;
564
565 bytes_remaining -= this_length;
566 page_number += 1;
567 }
568
569 Ok(())
570 }
571}
572
573#[derive(Clone, Copy)]
574struct WriteImageParameters {
575 pub image_report_length: usize,
576 pub image_report_payload_length: usize,
577}
578
579impl WriteImageParameters {
580 pub fn for_key(kind: Kind, image_data_len: usize) -> Self {
581 let image_report_length = match kind {
582 kind if kind.is_ajazz_v1() => 513,
583 kind if kind.is_ajazz_v2() => 1025,
584 _ => 1024,
585 };
586
587 let image_report_header_length = 1;
588
589 let image_report_payload_length = image_report_length - image_report_header_length;
590
591 Self {
592 image_report_length,
593 image_report_payload_length,
594 }
595 }
596}
597
598#[derive(Debug)]
600pub enum AjazzError {
601 HidError(HidError),
603
604 Utf8Error(Utf8Error),
606
607 ImageError(ImageError),
609
610 #[cfg(feature = "async")]
611 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
612 JoinError(tokio::task::JoinError),
614
615 PoisonError,
617
618 InvalidKeyIndex,
620
621 UnrecognizedPID,
623
624 UnsupportedOperation,
626
627 BadData,
629}
630
631impl Display for AjazzError {
632 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
633 write!(f, "{:?}", self)
634 }
635}
636
637impl Error for AjazzError {}
638
639impl From<HidError> for AjazzError {
640 fn from(e: HidError) -> Self {
641 Self::HidError(e)
642 }
643}
644
645impl From<Utf8Error> for AjazzError {
646 fn from(e: Utf8Error) -> Self {
647 Self::Utf8Error(e)
648 }
649}
650
651impl From<ImageError> for AjazzError {
652 fn from(e: ImageError) -> Self {
653 Self::ImageError(e)
654 }
655}
656
657#[cfg(feature = "async")]
658impl From<tokio::task::JoinError> for AjazzError {
659 fn from(e: tokio::task::JoinError) -> Self {
660 Self::JoinError(e)
661 }
662}
663
664impl<T> From<PoisonError<T>> for AjazzError {
665 fn from(_value: PoisonError<T>) -> Self {
666 Self::PoisonError
667 }
668}
669
670#[derive(Copy, Clone, Debug, Hash)]
672pub enum DeviceStateUpdate {
673 ButtonDown(u8),
675
676 ButtonUp(u8),
678
679 EncoderDown(u8),
681
682 EncoderUp(u8),
684
685 EncoderTwist(u8, i8),
687}
688
689#[derive(Default)]
690struct DeviceState {
691 pub buttons: Vec<bool>,
692 pub encoders: Vec<bool>,
693}
694
695pub struct DeviceStateReader {
697 device: Arc<Ajazz>,
698 states: Mutex<DeviceState>,
699}
700
701impl DeviceStateReader {
702 pub fn read(&self, timeout: Option<Duration>) -> Result<Vec<DeviceStateUpdate>, AjazzError> {
704 let input = self.device.read_input(timeout)?;
705 let mut my_states = self.states.lock()?;
706
707 let mut updates = vec![];
708
709 match input {
710 AjazzInput::ButtonStateChange(buttons) => {
711 for (index, (their, mine)) in zip(buttons.iter(), my_states.buttons.iter()).enumerate() {
712 if *their && !*mine {
713 updates.push(DeviceStateUpdate::ButtonDown(index as u8));
714 } else if *their && *mine {
715 updates.push(DeviceStateUpdate::ButtonUp(index as u8));
716 }
717 }
718
719 my_states.buttons = buttons;
720 }
721
722 AjazzInput::EncoderStateChange(encoders) => {
723 for (index, (their, mine)) in zip(encoders.iter(), my_states.encoders.iter()).enumerate() {
724 if *their {
725 updates.push(DeviceStateUpdate::EncoderDown(index as u8));
726 updates.push(DeviceStateUpdate::EncoderUp(index as u8));
727 }
728 }
729
730 my_states.encoders = encoders;
731 }
732
733 AjazzInput::EncoderTwist(twist) => {
734 for (index, change) in twist.iter().enumerate() {
735 if *change != 0 {
736 updates.push(DeviceStateUpdate::EncoderTwist(index as u8, *change));
737 }
738 }
739 }
740
741 _ => {}
742 }
743
744 drop(my_states);
745
746 Ok(updates)
747 }
748}