1#![cfg_attr(docsrs, feature(doc_cfg))]
8#![warn(missing_docs)]
9
10use std::collections::HashSet;
11use std::error::Error;
12use std::fmt::{Display, Formatter};
13use std::iter::zip;
14use std::str::Utf8Error;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::RwLock;
17use std::sync::{Arc, Mutex, PoisonError};
18use std::time::Duration;
19
20use crate::images::{convert_image, ImageRect};
21use hidapi::{HidApi, HidDevice, HidError, HidResult};
22use image::{DynamicImage, ImageError};
23
24use crate::info::{is_vendor_familiar, Kind};
25use crate::util::{
26 ajazz03_read_input, mirabox_extend_packet, ajazz153_to_elgato_input, elgato_to_ajazz153, extract_str, flip_key_index, get_feature_report, inverse_key_index, read_button_states, read_data,
27 read_encoder_input, read_lcd_input, send_feature_report, write_data,
28};
29
30pub mod info;
32pub mod util;
34pub mod images;
36
37#[cfg(feature = "async")]
39#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
40pub mod asynchronous;
41#[cfg(feature = "async")]
42#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
43pub use asynchronous::AsyncStreamDeck;
44
45pub fn new_hidapi() -> HidResult<HidApi> {
49 HidApi::new()
50}
51
52pub fn refresh_device_list(hidapi: &mut HidApi) -> HidResult<()> {
54 hidapi.refresh_devices()
55}
56
57pub fn list_devices(hidapi: &HidApi, only_elgato: bool) -> Vec<(Kind, String)> {
61 hidapi
62 .device_list()
63 .filter_map(|d| {
64 if (only_elgato && d.vendor_id() != info::ELGATO_VENDOR_ID) || !is_vendor_familiar(&d.vendor_id()) {
65 return None;
66 }
67
68 if let Some(serial) = d.serial_number() {
69 Some((Kind::from_vid_pid(d.vendor_id(), d.product_id())?, serial.to_string()))
70 } else {
71 None
72 }
73 })
74 .collect::<HashSet<_>>()
75 .into_iter()
76 .collect()
77}
78
79#[derive(Clone, Debug)]
81pub enum StreamDeckInput {
82 NoData,
84
85 ButtonStateChange(Vec<bool>),
87
88 EncoderStateChange(Vec<bool>),
90
91 EncoderTwist(Vec<i8>),
93
94 TouchScreenPress(u16, u16),
96
97 TouchScreenLongPress(u16, u16),
99
100 TouchScreenSwipe((u16, u16), (u16, u16)),
102}
103
104impl StreamDeckInput {
105 pub fn is_empty(&self) -> bool {
107 matches!(self, StreamDeckInput::NoData)
108 }
109}
110
111pub struct StreamDeck {
113 kind: Kind,
115 device: HidDevice,
117 image_cache: RwLock<Vec<ImageCache>>,
119 initialized: AtomicBool,
121}
122
123struct ImageCache {
124 key: u8,
125 image_data: Vec<u8>,
126}
127
128impl StreamDeck {
130 pub fn connect(hidapi: &HidApi, kind: Kind, serial: &str) -> Result<StreamDeck, StreamDeckError> {
132 let device = hidapi.open_serial(kind.vendor_id(), kind.product_id(), serial)?;
133
134 Ok(StreamDeck {
135 kind,
136 device,
137 image_cache: RwLock::new(vec![]),
138 initialized: false.into(),
139 })
140 }
141}
142
143impl StreamDeck {
145 pub fn kind(&self) -> Kind {
147 self.kind
148 }
149
150 pub fn manufacturer(&self) -> Result<String, StreamDeckError> {
152 Ok(self.device.get_manufacturer_string()?.unwrap_or_else(|| "Unknown".to_string()))
153 }
154
155 pub fn product(&self) -> Result<String, StreamDeckError> {
157 Ok(self.device.get_product_string()?.unwrap_or_else(|| "Unknown".to_string()))
158 }
159
160 pub fn serial_number(&self) -> Result<String, StreamDeckError> {
162 match self.kind {
163 kind if kind.is_mirabox() => {
164 let serial = self.device.get_serial_number_string()?;
165 match serial {
166 Some(serial) => {
167 if serial.is_empty() {
168 Ok("Unknown".to_string())
169 } else {
170 Ok(serial)
171 }
172 }
173 None => Ok("Unknown".to_string()),
174 }
175 }
176
177 Kind::Original | Kind::Mini => {
178 let bytes = get_feature_report(&self.device, 0x03, 17)?;
179 Ok(extract_str(&bytes[5..])?)
180 }
181
182 Kind::MiniMk2 => {
183 let bytes = get_feature_report(&self.device, 0x03, 32)?;
184 Ok(extract_str(&bytes[5..])?)
185 }
186
187 _ => {
188 let bytes = get_feature_report(&self.device, 0x06, 32)?;
189 Ok(extract_str(&bytes[2..])?)
190 }
191 }
192 .map(|s| s.replace('\u{0001}', ""))
193 }
194
195 pub fn firmware_version(&self) -> Result<String, StreamDeckError> {
197 match self.kind {
198 Kind::Original | Kind::Mini | Kind::MiniMk2 => {
199 let bytes = get_feature_report(&self.device, 0x04, 17)?;
200 Ok(extract_str(&bytes[5..])?)
201 }
202
203 kind if kind.is_mirabox() => {
204 let bytes = get_feature_report(&self.device, 0x01, 20)?;
205 Ok(extract_str(&bytes[0..])?)
206 }
207
208 _ => {
209 let bytes = get_feature_report(&self.device, 0x05, 32)?;
210 Ok(extract_str(&bytes[6..])?)
211 }
212 }
213 }
214
215 fn initialize(&self) -> Result<(), StreamDeckError> {
217 if self.initialized.load(Ordering::Acquire) {
218 return Ok(());
219 }
220
221 self.initialized.store(true, Ordering::Release);
222
223 if self.kind.is_mirabox() {
224 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x44, 0x49, 0x53];
225 mirabox_extend_packet(&self.kind, &mut buf);
226 write_data(&self.device, buf.as_slice())?;
227
228 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, 0x00, 0x00];
229 mirabox_extend_packet(&self.kind, &mut buf);
230 write_data(&self.device, buf.as_slice())?;
231 }
232
233 Ok(())
234 }
235
236 pub fn read_input(&self, timeout: Option<Duration>) -> Result<StreamDeckInput, StreamDeckError> {
238 self.initialize()?;
239 match &self.kind {
240 Kind::Plus => {
241 let data = read_data(&self.device, 14.max(5 + self.kind.encoder_count() as usize), timeout)?;
242
243 if data[0] == 0 {
244 return Ok(StreamDeckInput::NoData);
245 }
246
247 match &data[1] {
248 0x0 => Ok(StreamDeckInput::ButtonStateChange(read_button_states(&self.kind, &data))),
249
250 0x2 => Ok(read_lcd_input(&data)?),
251
252 0x3 => Ok(read_encoder_input(&self.kind, &data)?),
253
254 _ => Err(StreamDeckError::BadData),
255 }
256 }
257
258 kind if kind.is_mirabox_v1() => {
259 let data = read_data(&self.device, 512, timeout)?;
260
261 if data[0] == 0 {
262 return Ok(StreamDeckInput::NoData);
263 }
264
265 let mut states = vec![0x01];
266 states.extend(vec![0u8; (self.kind.key_count() + 1) as usize]);
267
268 if data[9] != 0 {
269 let key = match self.kind {
270 Kind::Akp815 => inverse_key_index(&self.kind, data[9] - 1),
271 Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::MiraBoxHSV293S => ajazz153_to_elgato_input(&self.kind, data[9] - 1),
272 Kind::MiraBoxDK0108D => data[9] - 1,
273 _ => unimplemented!(),
274 };
275
276 if self.kind == Kind::MiraBoxDK0108D && key > self.kind.key_count() {
279 return Ok(StreamDeckInput::NoData);
280 }
281
282 states[(key + 1) as usize] = 0x1u8;
283 }
284
285 Ok(StreamDeckInput::ButtonStateChange(read_button_states(&self.kind, &states)))
286 }
287
288 kind if kind.is_mirabox_v2() => {
289 let data = read_data(&self.device, 512, timeout)?;
290
291 if data[0] == 0 {
292 return Ok(StreamDeckInput::NoData);
293 }
294
295 if self.kind == Kind::MiraBoxN3EN {
296 ajazz03_read_input(&self.kind, data[9], data[10])
297 } else {
298 ajazz03_read_input(&self.kind, data[9], 0x01)
300 }
301 }
302
303 _ => {
304 let data = match self.kind {
305 Kind::Original | Kind::Mini | Kind::MiniMk2 => read_data(&self.device, 1 + self.kind.key_count() as usize, timeout),
306 _ => read_data(&self.device, 4 + self.kind.key_count() as usize + self.kind.touchpoint_count() as usize, timeout),
307 }?;
308
309 if data[0] == 0 {
310 return Ok(StreamDeckInput::NoData);
311 }
312
313 Ok(StreamDeckInput::ButtonStateChange(read_button_states(&self.kind, &data)))
314 }
315 }
316 }
317
318 pub fn reset(&self) -> Result<(), StreamDeckError> {
320 self.initialize()?;
321 match self.kind {
322 Kind::Original | Kind::Mini | Kind::MiniMk2 => {
323 let mut buf = vec![0x0B, 0x63];
324
325 buf.extend(vec![0u8; 15]);
326
327 Ok(send_feature_report(&self.device, buf.as_slice())?)
328 }
329
330 kind if kind.is_mirabox() => {
331 self.set_brightness(100)?;
332 self.clear_all_button_images()?;
333 Ok(())
334 }
335
336 _ => {
337 let mut buf = vec![0x03, 0x02];
338
339 buf.extend(vec![0u8; 30]);
340
341 Ok(send_feature_report(&self.device, buf.as_slice())?)
342 }
343 }
344 }
345
346 pub fn set_brightness(&self, percent: u8) -> Result<(), StreamDeckError> {
348 self.initialize()?;
349 let percent = percent.clamp(0, 100);
350
351 match self.kind {
352 Kind::Original | Kind::Mini | Kind::MiniMk2 => {
353 let mut buf = vec![0x05, 0x55, 0xaa, 0xd1, 0x01, percent];
354
355 buf.extend(vec![0u8; 11]);
356
357 Ok(send_feature_report(&self.device, buf.as_slice())?)
358 }
359
360 kind if kind.is_mirabox() => {
361 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, percent];
362
363 mirabox_extend_packet(&self.kind, &mut buf);
364
365 write_data(&self.device, buf.as_slice())?;
366
367 Ok(())
368 }
369
370 _ => {
371 let mut buf = vec![0x03, 0x08, percent];
372
373 buf.extend(vec![0u8; 29]);
374
375 Ok(send_feature_report(&self.device, buf.as_slice())?)
376 }
377 }
378 }
379
380 fn send_image(&self, key: u8, image_data: &[u8]) -> Result<(), StreamDeckError> {
381 if key >= self.kind.key_count() {
382 return Err(StreamDeckError::InvalidKeyIndex);
383 }
384
385 let key = if let Kind::Original = self.kind {
386 flip_key_index(&self.kind, key)
387 } else if let Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::MiraBoxHSV293S = self.kind {
388 elgato_to_ajazz153(&self.kind, key)
389 } else if let Kind::Akp815 = self.kind {
390 inverse_key_index(&self.kind, key)
391 } else {
392 key
393 };
394
395 if !self.kind.is_visual() {
396 return Err(StreamDeckError::NoScreen);
397 }
398
399 if self.kind.is_mirabox() {
400 let mut buf = vec![
401 0x00,
402 0x43,
403 0x52,
404 0x54,
405 0x00,
406 0x00,
407 0x42,
408 0x41,
409 0x54,
410 0x00,
411 0x00,
412 (image_data.len() >> 8) as u8,
413 image_data.len() as u8,
414 key + 1,
415 ];
416
417 mirabox_extend_packet(&self.kind, &mut buf);
418
419 write_data(&self.device, buf.as_slice())?;
420 }
421
422 self.write_image_data_reports(
423 image_data,
424 WriteImageParameters::for_key(self.kind, image_data.len()),
425 |page_number, this_length, last_package| match self.kind {
426 Kind::Original => vec![0x02, 0x01, (page_number + 1) as u8, 0, if last_package { 1 } else { 0 }, key + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
427
428 Kind::Mini | Kind::MiniMk2 => vec![0x02, 0x01, page_number as u8, 0, if last_package { 1 } else { 0 }, key + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
429
430 kind if kind.is_mirabox() => vec![0x00],
431
432 _ => vec![
433 0x02,
434 0x07,
435 key,
436 if last_package { 1 } else { 0 },
437 (this_length & 0xff) as u8,
438 (this_length >> 8) as u8,
439 (page_number & 0xff) as u8,
440 (page_number >> 8) as u8,
441 ],
442 },
443 )?;
444 Ok(())
445 }
446
447 pub fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), StreamDeckError> {
450 if matches!(self.kind, Kind::Akp03 | Kind::Akp03E | Kind::Akp03R) && key >= 6 {
452 return Ok(());
453 }
454
455 let cache_entry = ImageCache {
456 key,
457 image_data: image_data.to_vec(), };
459
460 self.image_cache.write()?.push(cache_entry);
461
462 Ok(())
463 }
464
465 pub fn write_lcd(&self, x: u16, y: u16, rect: &ImageRect) -> Result<(), StreamDeckError> {
468 self.initialize()?;
469 match self.kind {
470 Kind::Plus => (),
471 _ => return Err(StreamDeckError::UnsupportedOperation),
472 }
473
474 self.write_image_data_reports(
475 rect.data.as_slice(),
476 WriteImageParameters {
477 image_report_length: 1024,
478 image_report_payload_length: 1024 - 16,
479 },
480 |page_number, this_length, last_package| {
481 vec![
482 0x02,
483 0x0c,
484 (x & 0xff) as u8,
485 (x >> 8) as u8,
486 (y & 0xff) as u8,
487 (y >> 8) as u8,
488 (rect.w & 0xff) as u8,
489 (rect.w >> 8) as u8,
490 (rect.h & 0xff) as u8,
491 (rect.h >> 8) as u8,
492 if last_package { 1 } else { 0 },
493 (page_number & 0xff) as u8,
494 (page_number >> 8) as u8,
495 (this_length & 0xff) as u8,
496 (this_length >> 8) as u8,
497 0,
498 ]
499 },
500 )
501 }
502
503 pub fn write_lcd_fill(&self, image_data: &[u8]) -> Result<(), StreamDeckError> {
512 self.initialize()?;
513 match self.kind {
514 Kind::Neo => self.write_image_data_reports(
515 image_data,
516 WriteImageParameters {
517 image_report_length: 1024,
518 image_report_payload_length: 1024 - 8,
519 },
520 |page_number, this_length, last_package| {
521 vec![
522 0x02,
523 0x0b,
524 0,
525 if last_package { 1 } else { 0 },
526 (this_length & 0xff) as u8,
527 (this_length >> 8) as u8,
528 (page_number & 0xff) as u8,
529 (page_number >> 8) as u8,
530 ]
531 },
532 ),
533
534 Kind::Plus => {
535 let (w, h) = self.kind.lcd_strip_size().unwrap();
536
537 self.write_image_data_reports(
538 image_data,
539 WriteImageParameters {
540 image_report_length: 1024,
541 image_report_payload_length: 1024 - 16,
542 },
543 |page_number, this_length, last_package| {
544 vec![
545 0x02,
546 0x0c,
547 0,
548 0,
549 0,
550 0,
551 (w & 0xff) as u8,
552 (w >> 8) as u8,
553 (h & 0xff) as u8,
554 (h >> 8) as u8,
555 if last_package { 1 } else { 0 },
556 (page_number & 0xff) as u8,
557 (page_number >> 8) as u8,
558 (this_length & 0xff) as u8,
559 (this_length >> 8) as u8,
560 0,
561 ]
562 },
563 )
564 }
565
566 _ => Err(StreamDeckError::UnsupportedOperation),
567 }
568 }
569
570 pub fn clear_button_image(&self, key: u8) -> Result<(), StreamDeckError> {
573 self.initialize()?;
574
575 if self.kind.is_mirabox() {
576 let key = match self.kind {
577 Kind::Akp815 => inverse_key_index(&self.kind, key),
578 Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::MiraBoxHSV293S => elgato_to_ajazz153(&self.kind, key),
579 _ => key,
580 };
581
582 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4c, 0x45, 0x00, 0x00, 0x00, if key == 0xff { 0xff } else { key + 1 }];
583
584 mirabox_extend_packet(&self.kind, &mut buf);
585
586 write_data(&self.device, buf.as_slice())?;
587
588 Ok(())
589 } else {
590 Ok(self.send_image(key, &self.kind.blank_image())?)
591 }
592 }
593
594 pub fn clear_all_button_images(&self) -> Result<(), StreamDeckError> {
597 self.initialize()?;
598 match self.kind {
599 kind if kind.is_mirabox_v1() => self.clear_button_image(0xff),
600 kind if kind.is_mirabox_v2() => {
601 self.clear_button_image(0xFF)?;
602
603 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
605 mirabox_extend_packet(&self.kind, &mut buf);
606 write_data(&self.device, buf.as_slice())?;
607
608 Ok(())
609 }
610 _ => {
611 for i in 0..self.kind.key_count() {
612 self.clear_button_image(i)?
613 }
614 Ok(())
615 }
616 }
617 }
618
619 pub fn set_button_image(&self, key: u8, image: DynamicImage) -> Result<(), StreamDeckError> {
622 self.initialize()?;
623 let image_data = convert_image(self.kind, image)?;
624 self.write_image(key, &image_data)?;
625 Ok(())
626 }
627
628 pub fn set_logo_image(&self, image: DynamicImage) -> Result<(), StreamDeckError> {
630 self.initialize()?;
631
632 if !self.kind.is_mirabox() {
633 return Err(StreamDeckError::UnsupportedOperation);
634 }
635
636 if self.kind.lcd_strip_size().is_none() {
637 return Err(StreamDeckError::UnsupportedOperation);
638 }
639 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x4f, 0x47, 0x00, 0x12, 0xc3, 0xc0, 0x01];
641
642 mirabox_extend_packet(&self.kind, &mut buf);
643
644 write_data(&self.device, buf.as_slice())?;
645
646 let mut image_buffer: DynamicImage = DynamicImage::new_rgb8(854, 480);
647
648 let ratio = 854.0 / 480.0;
649
650 let mode = "cover";
651
652 match mode {
653 "contain" => {
654 let (image_w, image_h) = (image.width(), image.height());
655 let image_ratio = image_w as f32 / image_h as f32;
656
657 let (ws, hs) = if image_ratio > ratio {
658 (854, (854.0 / image_ratio) as u32)
659 } else {
660 ((480.0 * image_ratio) as u32, 480)
661 };
662
663 let resized_image = image.resize(ws, hs, image::imageops::FilterType::Nearest);
664 image::imageops::overlay(
665 &mut image_buffer,
666 &resized_image,
667 ((854 - resized_image.width()) / 2) as i64,
668 ((480 - resized_image.height()) / 2) as i64,
669 );
670 }
671 "cover" => {
672 let resized_image = image.resize_to_fill(854, 480, image::imageops::FilterType::Nearest);
673 image::imageops::overlay(
674 &mut image_buffer,
675 &resized_image,
676 ((854 - resized_image.width()) / 2) as i64,
677 ((480 - resized_image.height()) / 2) as i64,
678 );
679 }
680 _ => {
681 let (image_w, image_h) = (image.width(), image.height());
682 let image_ratio = image_w as f32 / image_h as f32;
683
684 let (ws, hs) = if image_ratio > ratio {
685 ((480.0 * image_ratio) as u32, 480)
686 } else {
687 (854, (854.0 / image_ratio) as u32)
688 };
689
690 let resized_image = image.resize(ws, hs, image::imageops::FilterType::Nearest);
691 image::imageops::overlay(
692 &mut image_buffer,
693 &resized_image,
694 ((854 - resized_image.width()) / 2) as i64,
695 ((480 - resized_image.height()) / 2) as i64,
696 );
697 }
698 }
699
700 let mut image_data = image_buffer.rotate90().fliph().flipv().into_rgb8().to_vec();
701 for x in (0..image_data.len()).step_by(3) {
702 (image_data[x], image_data[x + 2]) = (image_data[x + 2], image_data[x])
703 }
704
705 let image_report_length = match self.kind {
706 Kind::Original => 8191,
707 kind if kind.is_mirabox_v1() => 513,
708 kind if kind.is_mirabox_v2() => 1025,
709 _ => 1024,
710 };
711
712 let image_report_header_length = match self.kind {
713 Kind::Original | Kind::Mini | Kind::MiniMk2 => 16,
714 kind if kind.is_mirabox() => 1,
715 _ => 8,
716 };
717
718 let image_report_payload_length = match self.kind {
719 Kind::Original => image_data.len() / 2,
720 _ => image_report_length - image_report_header_length,
721 };
722
723 let mut page_number = 0;
724 let mut bytes_remaining = image_data.len();
725
726 while bytes_remaining > 0 {
727 let this_length = bytes_remaining.min(image_report_payload_length);
728 let bytes_sent = page_number * image_report_payload_length;
729
730 let mut buf: Vec<u8> = vec![0x00];
732
733 buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
735
736 buf.extend(vec![0u8; image_report_length - buf.len()]);
738
739 write_data(&self.device, &buf)?;
740
741 bytes_remaining -= this_length;
742 page_number += 1;
743 }
744
745 Ok(())
746 }
747
748 pub fn set_touchpoint_color(&self, point: u8, red: u8, green: u8, blue: u8) -> Result<(), StreamDeckError> {
750 self.initialize()?;
751 if point >= self.kind.touchpoint_count() {
752 return Err(StreamDeckError::InvalidTouchPointIndex);
753 }
754
755 let mut buf = vec![0x03, 0x06];
756
757 let touchpoint_index: u8 = point + self.kind.key_count();
758 buf.extend(vec![touchpoint_index]);
759 buf.extend(vec![red, green, blue]);
760
761 Ok(send_feature_report(&self.device, buf.as_slice())?)
762 }
763
764 pub fn sleep(&self) -> Result<(), StreamDeckError> {
766 self.initialize()?;
767
768 if !self.kind.is_mirabox() {
769 return Err(StreamDeckError::UnsupportedOperation);
770 }
771
772 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4e];
773
774 mirabox_extend_packet(&self.kind, &mut buf);
775
776 write_data(&self.device, buf.as_slice())?;
777
778 Ok(())
779 }
780
781 pub fn keep_alive(&self) -> Result<(), StreamDeckError> {
783 self.initialize()?;
784
785 if !self.kind.is_mirabox() {
786 return Err(StreamDeckError::UnsupportedOperation);
787 }
788
789 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4F, 0x4E, 0x4E, 0x45, 0x43, 0x54];
790 mirabox_extend_packet(&self.kind, &mut buf);
791 write_data(&self.device, buf.as_slice())?;
792 Ok(())
793 }
794
795 pub fn shutdown(&self) -> Result<(), StreamDeckError> {
797 self.initialize()?;
798
799 if !self.kind.is_mirabox() {
800 return Err(StreamDeckError::UnsupportedOperation);
801 }
802
803 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4c, 0x45, 0x00, 0x00, 0x44, 0x43];
804 mirabox_extend_packet(&self.kind, &mut buf);
805 write_data(&self.device, buf.as_slice())?;
806
807 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4E];
808 mirabox_extend_packet(&self.kind, &mut buf);
809 write_data(&self.device, buf.as_slice())?;
810
811 Ok(())
812 }
813
814 pub fn flush(&self) -> Result<(), StreamDeckError> {
816 self.initialize()?;
817
818 if self.image_cache.write()?.is_empty() {
819 return Ok(());
820 }
821
822 for image in self.image_cache.read()?.iter() {
823 self.send_image(image.key, &image.image_data)?;
824 }
825
826 if self.kind.is_mirabox() {
827 let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
828
829 mirabox_extend_packet(&self.kind, &mut buf);
830
831 write_data(&self.device, buf.as_slice())?;
832 }
833
834 self.image_cache.write()?.clear();
835
836 Ok(())
837 }
838
839 pub fn get_reader(self: &Arc<Self>) -> Arc<DeviceStateReader> {
841 #[allow(clippy::arc_with_non_send_sync)]
842 Arc::new(DeviceStateReader {
843 device: self.clone(),
844 states: Mutex::new(DeviceState {
845 buttons: vec![false; self.kind.key_count() as usize + self.kind.touchpoint_count() as usize],
846 encoders: vec![false; self.kind.encoder_count() as usize],
847 }),
848 })
849 }
850
851 fn write_image_data_reports<T>(&self, image_data: &[u8], parameters: WriteImageParameters, header_fn: T) -> Result<(), StreamDeckError>
852 where
853 T: Fn(usize, usize, bool) -> Vec<u8>,
854 {
855 let image_report_length = parameters.image_report_length;
856 let image_report_payload_length = parameters.image_report_payload_length;
857
858 let mut page_number = 0;
859 let mut bytes_remaining = image_data.len();
860
861 while bytes_remaining > 0 {
862 let this_length = bytes_remaining.min(image_report_payload_length);
863 let bytes_sent = page_number * image_report_payload_length;
864
865 let mut buf: Vec<u8> = header_fn(page_number, this_length, this_length == bytes_remaining);
867
868 buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
869
870 buf.extend(vec![0u8; image_report_length - buf.len()]);
872
873 write_data(&self.device, &buf)?;
874
875 bytes_remaining -= this_length;
876 page_number += 1;
877 }
878
879 Ok(())
880 }
881}
882
883#[derive(Clone, Copy)]
884struct WriteImageParameters {
885 pub image_report_length: usize,
886 pub image_report_payload_length: usize,
887}
888
889impl WriteImageParameters {
890 pub fn for_key(kind: Kind, image_data_len: usize) -> Self {
891 let image_report_length = match kind {
892 Kind::Original => 8191,
893 kind if kind.is_mirabox_v1() => 513,
894 kind if kind.is_mirabox_v2() => 1025,
895 _ => 1024,
896 };
897
898 let image_report_header_length = match kind {
899 Kind::Original | Kind::Mini | Kind::MiniMk2 => 16,
900 kind if kind.is_mirabox() => 1,
901 _ => 8,
902 };
903
904 let image_report_payload_length = match kind {
905 Kind::Original => image_data_len / 2,
906 _ => image_report_length - image_report_header_length,
907 };
908
909 Self {
910 image_report_length,
911 image_report_payload_length,
912 }
913 }
914}
915
916#[derive(Debug)]
918pub enum StreamDeckError {
919 HidError(HidError),
921
922 Utf8Error(Utf8Error),
924
925 ImageError(ImageError),
927
928 #[cfg(feature = "async")]
929 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
930 JoinError(tokio::task::JoinError),
932
933 PoisonError,
935
936 NoScreen,
938
939 InvalidKeyIndex,
941
942 InvalidTouchPointIndex,
944
945 UnrecognizedPID,
947
948 UnsupportedOperation,
950
951 BadData,
953}
954
955impl Display for StreamDeckError {
956 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
957 write!(f, "{:?}", self)
958 }
959}
960
961impl Error for StreamDeckError {}
962
963impl From<HidError> for StreamDeckError {
964 fn from(e: HidError) -> Self {
965 Self::HidError(e)
966 }
967}
968
969impl From<Utf8Error> for StreamDeckError {
970 fn from(e: Utf8Error) -> Self {
971 Self::Utf8Error(e)
972 }
973}
974
975impl From<ImageError> for StreamDeckError {
976 fn from(e: ImageError) -> Self {
977 Self::ImageError(e)
978 }
979}
980
981#[cfg(feature = "async")]
982impl From<tokio::task::JoinError> for StreamDeckError {
983 fn from(e: tokio::task::JoinError) -> Self {
984 Self::JoinError(e)
985 }
986}
987
988impl<T> From<PoisonError<T>> for StreamDeckError {
989 fn from(_value: PoisonError<T>) -> Self {
990 Self::PoisonError
991 }
992}
993
994#[derive(Copy, Clone, Debug, Hash)]
996pub enum DeviceStateUpdate {
997 ButtonDown(u8),
999
1000 ButtonUp(u8),
1002
1003 EncoderDown(u8),
1005
1006 EncoderUp(u8),
1008
1009 EncoderTwist(u8, i8),
1011
1012 TouchPointDown(u8),
1014
1015 TouchPointUp(u8),
1017
1018 TouchScreenPress(u16, u16),
1020
1021 TouchScreenLongPress(u16, u16),
1023
1024 TouchScreenSwipe((u16, u16), (u16, u16)),
1026}
1027
1028#[derive(Default)]
1029struct DeviceState {
1030 pub buttons: Vec<bool>,
1032 pub encoders: Vec<bool>,
1033}
1034
1035pub struct DeviceStateReader {
1037 device: Arc<StreamDeck>,
1038 states: Mutex<DeviceState>,
1039}
1040
1041impl DeviceStateReader {
1042 pub fn read(&self, timeout: Option<Duration>) -> Result<Vec<DeviceStateUpdate>, StreamDeckError> {
1044 let input = self.device.read_input(timeout)?;
1045 let mut my_states = self.states.lock()?;
1046
1047 let mut updates = vec![];
1048
1049 match input {
1050 StreamDeckInput::ButtonStateChange(buttons) => {
1051 for (index, (their, mine)) in zip(buttons.iter(), my_states.buttons.iter()).enumerate() {
1052 if self.device.kind.is_mirabox() && self.device.kind != Kind::MiraBoxN3EN {
1053 if *their {
1054 updates.push(DeviceStateUpdate::ButtonDown(index as u8));
1055 updates.push(DeviceStateUpdate::ButtonUp(index as u8));
1056 }
1057 } else if their != mine {
1058 let key_count = self.device.kind.key_count();
1059 if index < key_count as usize {
1060 if *their {
1061 updates.push(DeviceStateUpdate::ButtonDown(index as u8));
1062 } else {
1063 updates.push(DeviceStateUpdate::ButtonUp(index as u8));
1064 }
1065 } else if *their {
1066 updates.push(DeviceStateUpdate::TouchPointDown(index as u8 - key_count));
1067 } else {
1068 updates.push(DeviceStateUpdate::TouchPointUp(index as u8 - key_count));
1069 }
1070 }
1071 }
1072
1073 my_states.buttons = buttons;
1074 }
1075
1076 StreamDeckInput::EncoderStateChange(encoders) => {
1077 for (index, (their, mine)) in zip(encoders.iter(), my_states.encoders.iter()).enumerate() {
1078 if self.device.kind.is_mirabox() && self.device.kind != Kind::MiraBoxN3EN {
1079 if *their {
1080 updates.push(DeviceStateUpdate::EncoderDown(index as u8));
1081 updates.push(DeviceStateUpdate::EncoderUp(index as u8));
1082 }
1083 } else if *their != *mine {
1084 if *their {
1085 updates.push(DeviceStateUpdate::EncoderDown(index as u8));
1086 } else {
1087 updates.push(DeviceStateUpdate::EncoderUp(index as u8));
1088 }
1089 }
1090 }
1091
1092 my_states.encoders = encoders;
1093 }
1094
1095 StreamDeckInput::EncoderTwist(twist) => {
1096 for (index, change) in twist.iter().enumerate() {
1097 if *change != 0 {
1098 updates.push(DeviceStateUpdate::EncoderTwist(index as u8, *change));
1099 }
1100 }
1101 }
1102
1103 StreamDeckInput::TouchScreenPress(x, y) => {
1104 updates.push(DeviceStateUpdate::TouchScreenPress(x, y));
1105 }
1106
1107 StreamDeckInput::TouchScreenLongPress(x, y) => {
1108 updates.push(DeviceStateUpdate::TouchScreenLongPress(x, y));
1109 }
1110
1111 StreamDeckInput::TouchScreenSwipe(s, e) => {
1112 updates.push(DeviceStateUpdate::TouchScreenSwipe(s, e));
1113 }
1114
1115 _ => {}
1116 }
1117
1118 drop(my_states);
1119
1120 Ok(updates)
1121 }
1122}