elgato_streamdeck/
lib.rs

1//! Elgato Streamdeck library
2//!
3//! Library for interacting with Elgato Stream Decks through [hidapi](https://crates.io/crates/hidapi).
4//! Heavily based on [python-elgato-streamdeck](https://github.com/abcminiuser/python-elgato-streamdeck) and partially on
5//! [streamdeck library for rust](https://github.com/ryankurte/rust-streamdeck).
6
7#![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
30/// Various information about Stream Deck devices
31pub mod info;
32/// Utility functions for working with Stream Deck devices
33pub mod util;
34/// Image processing functions
35pub mod images;
36
37/// Async Stream Deck
38#[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
45/// Creates an instance of the HidApi
46///
47/// Can be used if you don't want to link hidapi crate into your project
48pub fn new_hidapi() -> HidResult<HidApi> {
49    HidApi::new()
50}
51
52/// Actually refreshes the device list
53pub fn refresh_device_list(hidapi: &mut HidApi) -> HidResult<()> {
54    hidapi.refresh_devices()
55}
56
57/// Returns a list of devices as (Kind, Serial Number) that could be found using HidApi.
58///
59/// **WARNING:** To refresh the list, use [refresh_device_list]
60pub 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/// Type of input that the device produced
80#[derive(Clone, Debug)]
81pub enum StreamDeckInput {
82    /// No data was passed from the device
83    NoData,
84
85    /// Button was pressed
86    ButtonStateChange(Vec<bool>),
87
88    /// Encoder/Knob was pressed
89    EncoderStateChange(Vec<bool>),
90
91    /// Encoder/Knob was twisted/turned
92    EncoderTwist(Vec<i8>),
93
94    /// Touch screen received short press
95    TouchScreenPress(u16, u16),
96
97    /// Touch screen received long press
98    TouchScreenLongPress(u16, u16),
99
100    /// Touch screen received a swipe
101    TouchScreenSwipe((u16, u16), (u16, u16)),
102}
103
104impl StreamDeckInput {
105    /// Checks if there's data received or not
106    pub fn is_empty(&self) -> bool {
107        matches!(self, StreamDeckInput::NoData)
108    }
109}
110
111/// Interface for a Stream Deck device
112pub struct StreamDeck {
113    /// Kind of the device
114    kind: Kind,
115    /// Connected HIDDevice
116    device: HidDevice,
117    /// Temporarily cache the image before sending it to the device
118    image_cache: RwLock<Vec<ImageCache>>,
119    /// Device needs to be initialized
120    initialized: AtomicBool,
121}
122
123struct ImageCache {
124    key: u8,
125    image_data: Vec<u8>,
126}
127
128/// Static functions of the struct
129impl StreamDeck {
130    /// Attempts to connect to the device
131    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
143/// Instance methods of the struct
144impl StreamDeck {
145    /// Returns kind of the Stream Deck
146    pub fn kind(&self) -> Kind {
147        self.kind
148    }
149
150    /// Returns manufacturer string of the device
151    pub fn manufacturer(&self) -> Result<String, StreamDeckError> {
152        Ok(self.device.get_manufacturer_string()?.unwrap_or_else(|| "Unknown".to_string()))
153    }
154
155    /// Returns product string of the device
156    pub fn product(&self) -> Result<String, StreamDeckError> {
157        Ok(self.device.get_product_string()?.unwrap_or_else(|| "Unknown".to_string()))
158    }
159
160    /// Returns serial number of the device
161    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    /// Returns firmware version of the StreamDeck
196    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    /// Initializes the device
216    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    /// Reads all possible input from Stream Deck device
237    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                    // This device can slide its view, and as it does, it sends events for keys 48, 49 and 50.
277                    // This functionality is not yet implemented in this library. So, for now, drop related events.
278                    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                    // Devices not returning a state for the input
299                    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    /// Resets the device
319    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    /// Sets brightness of the device, value range is 0 - 100
347    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    /// Writes image data to Stream Deck device, changes must be flushed with `.flush()` before
448    /// they will appear on the device!
449    pub fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), StreamDeckError> {
450        // Key count is 9 for AKP03x, but only the first 6 (0-5) have screens, so don't output anything for keys 6, 7, 8
451        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(), // Convert &[u8] to Vec<u8>
458        };
459
460        self.image_cache.write()?.push(cache_entry);
461
462        Ok(())
463    }
464
465    /// Writes image data to Stream Deck device's lcd strip/screen as region.
466    /// Only Stream Deck Plus supports writing LCD regions, for Stream Deck Neo use write_lcd_fill
467    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    /// Writes image data to Stream Deck device's lcd strip/screen as full fill
504    ///
505    /// You can convert your images into proper image_data like this:
506    /// ```
507    /// use elgato_streamdeck::images::convert_image_with_format;
508    /// let image_data = convert_image_with_format(device.kind().lcd_image_format(), image).unwrap();
509    /// device.write_lcd_fill(&image_data);
510    /// ```
511    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    /// Sets button's image to blank, changes must be flushed with `.flush()` before
571    /// they will appear on the device!
572    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    /// Sets blank images to every button, changes must be flushed with `.flush()` before
595    /// they will appear on the device!
596    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                // Mirabox "v2" requires STP to commit clearing the screen
604                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    /// Sets specified button's image, changes must be flushed with `.flush()` before
620    /// they will appear on the device!
621    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    /// Set logo image
629    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        // 854 * 480 * 3
640        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            // Create buffer with Report ID as first byte
731            let mut buf: Vec<u8> = vec![0x00];
732
733            // Selecting header based on device
734            buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
735
736            // Adding padding
737            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    /// Sets specified touch point's led strip color
749    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    /// Sleeps the device
765    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    /// Make periodic events to the device, to keep it alive
782    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    /// Shutdown the device
796    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    /// Flushes the button's image to the device
815    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    /// Returns button state reader for this device
840    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            // Selecting header based on device
866            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            // Adding padding
871            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/// Errors that can occur while working with Stream Decks
917#[derive(Debug)]
918pub enum StreamDeckError {
919    /// HidApi error
920    HidError(HidError),
921
922    /// Failed to convert bytes into string
923    Utf8Error(Utf8Error),
924
925    /// Failed to encode image
926    ImageError(ImageError),
927
928    #[cfg(feature = "async")]
929    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
930    /// Tokio join error
931    JoinError(tokio::task::JoinError),
932
933    /// Reader mutex was poisoned
934    PoisonError,
935
936    /// There's literally nowhere to write the image
937    NoScreen,
938
939    /// Key index is invalid
940    InvalidKeyIndex,
941
942    /// Key index is invalid
943    InvalidTouchPointIndex,
944
945    /// Unrecognized Product ID
946    UnrecognizedPID,
947
948    /// The device doesn't support doing that
949    UnsupportedOperation,
950
951    /// Stream Deck sent unexpected data
952    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/// Tells what changed in button states
995#[derive(Copy, Clone, Debug, Hash)]
996pub enum DeviceStateUpdate {
997    /// Button got pressed down
998    ButtonDown(u8),
999
1000    /// Button got released
1001    ButtonUp(u8),
1002
1003    /// Encoder got pressed down
1004    EncoderDown(u8),
1005
1006    /// Encoder was released from being pressed down
1007    EncoderUp(u8),
1008
1009    /// Encoder was twisted
1010    EncoderTwist(u8, i8),
1011
1012    /// Touch Point got pressed down
1013    TouchPointDown(u8),
1014
1015    /// Touch Point got released
1016    TouchPointUp(u8),
1017
1018    /// Touch screen received short press
1019    TouchScreenPress(u16, u16),
1020
1021    /// Touch screen received long press
1022    TouchScreenLongPress(u16, u16),
1023
1024    /// Touch screen received a swipe
1025    TouchScreenSwipe((u16, u16), (u16, u16)),
1026}
1027
1028#[derive(Default)]
1029struct DeviceState {
1030    /// Buttons include Touch Points state
1031    pub buttons: Vec<bool>,
1032    pub encoders: Vec<bool>,
1033}
1034
1035/// Button reader that keeps state of the Stream Deck and returns events instead of full states
1036pub struct DeviceStateReader {
1037    device: Arc<StreamDeck>,
1038    states: Mutex<DeviceState>,
1039}
1040
1041impl DeviceStateReader {
1042    /// Reads states and returns updates
1043    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}