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    ajazz153_to_elgato_input, elgato_to_ajazz153, extract_str, flip_key_index, get_feature_report, inverse_key_index, read_button_states, read_data, read_encoder_input, read_lcd_input,
27    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) -> Vec<(Kind, String)> {
61    hidapi
62        .device_list()
63        .filter_map(|d| {
64            if !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::Akp153 | Kind::Akp153E | Kind::Akp815 | Kind::MiraBoxHSV293S => {
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::Akp153 | Kind::Akp153E | Kind::Akp815 | Kind::MiraBoxHSV293S => {
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        self.initialized.store(true, Ordering::Release);
221        match self.kind {
222            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
223                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x44, 0x49, 0x53];
224                buf.extend(vec![0u8; 513 - buf.len()]);
225                write_data(&self.device, buf.as_slice())?;
226
227                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, 0x00, 0x00];
228                buf.extend(vec![0u8; 513 - buf.len()]);
229                write_data(&self.device, buf.as_slice())?;
230            }
231
232            _ => {}
233        }
234        Ok(())
235    }
236
237    /// Reads all possible input from Stream Deck device
238    pub fn read_input(&self, timeout: Option<Duration>) -> Result<StreamDeckInput, StreamDeckError> {
239        self.initialize()?;
240        match &self.kind {
241            Kind::Plus => {
242                let data = read_data(&self.device, 14.max(5 + self.kind.encoder_count() as usize), timeout)?;
243
244                if data[0] == 0 {
245                    return Ok(StreamDeckInput::NoData);
246                }
247
248                match &data[1] {
249                    0x0 => Ok(StreamDeckInput::ButtonStateChange(read_button_states(&self.kind, &data))),
250
251                    0x2 => Ok(read_lcd_input(&data)?),
252
253                    0x3 => Ok(read_encoder_input(&self.kind, &data)?),
254
255                    _ => Err(StreamDeckError::BadData),
256                }
257            }
258
259            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
260                let data = read_data(&self.device, 512, timeout)?;
261
262                if data[0] == 0 {
263                    return Ok(StreamDeckInput::NoData);
264                }
265
266                let mut states = vec![0x01];
267                states.extend(vec![0u8; (self.kind.key_count() + 1) as usize]);
268
269                if data[9] != 0 {
270                    let key = if self.kind == Kind::Akp815 {
271                        inverse_key_index(&self.kind, data[9] - 1)
272                    } else {
273                        ajazz153_to_elgato_input(&self.kind, data[9] - 1)
274                    };
275
276                    states[(key + 1) as usize] = 0x1u8;
277                }
278
279                Ok(StreamDeckInput::ButtonStateChange(read_button_states(&self.kind, &states)))
280            }
281
282            _ => {
283                let data = match self.kind {
284                    Kind::Original | Kind::Mini | Kind::MiniMk2 => read_data(&self.device, 1 + self.kind.key_count() as usize, timeout),
285                    _ => read_data(&self.device, 4 + self.kind.key_count() as usize + self.kind.touchpoint_count() as usize, timeout),
286                }?;
287
288                if data[0] == 0 {
289                    return Ok(StreamDeckInput::NoData);
290                }
291
292                Ok(StreamDeckInput::ButtonStateChange(read_button_states(&self.kind, &data)))
293            }
294        }
295    }
296
297    /// Resets the device
298    pub fn reset(&self) -> Result<(), StreamDeckError> {
299        self.initialize()?;
300        match self.kind {
301            Kind::Original | Kind::Mini | Kind::MiniMk2 => {
302                let mut buf = vec![0x0B, 0x63];
303
304                buf.extend(vec![0u8; 15]);
305
306                Ok(send_feature_report(&self.device, buf.as_slice())?)
307            }
308
309            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
310                self.set_brightness(100)?;
311                self.clear_button_image(0xff)?;
312                Ok(())
313            }
314
315            _ => {
316                let mut buf = vec![0x03, 0x02];
317
318                buf.extend(vec![0u8; 30]);
319
320                Ok(send_feature_report(&self.device, buf.as_slice())?)
321            }
322        }
323    }
324
325    /// Sets brightness of the device, value range is 0 - 100
326    pub fn set_brightness(&self, percent: u8) -> Result<(), StreamDeckError> {
327        self.initialize()?;
328        let percent = percent.clamp(0, 100);
329
330        match self.kind {
331            Kind::Original | Kind::Mini | Kind::MiniMk2 => {
332                let mut buf = vec![0x05, 0x55, 0xaa, 0xd1, 0x01, percent];
333
334                buf.extend(vec![0u8; 11]);
335
336                Ok(send_feature_report(&self.device, buf.as_slice())?)
337            }
338
339            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
340                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, percent];
341
342                buf.extend(vec![0u8; 513 - buf.len()]);
343
344                write_data(&self.device, buf.as_slice())?;
345
346                Ok(())
347            }
348
349            _ => {
350                let mut buf = vec![0x03, 0x08, percent];
351
352                buf.extend(vec![0u8; 29]);
353
354                Ok(send_feature_report(&self.device, buf.as_slice())?)
355            }
356        }
357    }
358
359    fn send_image(&self, key: u8, image_data: &[u8]) -> Result<(), StreamDeckError> {
360        if key >= self.kind.key_count() {
361            return Err(StreamDeckError::InvalidKeyIndex);
362        }
363
364        let key = if let Kind::Original = self.kind {
365            flip_key_index(&self.kind, key)
366        } else if let Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::MiraBoxHSV293S = self.kind {
367            elgato_to_ajazz153(&self.kind, key)
368        } else if let Kind::Akp815 = self.kind {
369            inverse_key_index(&self.kind, key)
370        } else {
371            key
372        };
373
374        if !self.kind.is_visual() {
375            return Err(StreamDeckError::NoScreen);
376        }
377
378        match self.kind {
379            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
380                let mut buf = vec![
381                    0x00,
382                    0x43,
383                    0x52,
384                    0x54,
385                    0x00,
386                    0x00,
387                    0x42,
388                    0x41,
389                    0x54,
390                    0x00,
391                    0x00,
392                    (image_data.len() >> 8) as u8,
393                    image_data.len() as u8,
394                    key + 1,
395                ];
396
397                buf.extend(vec![0u8; 513 - buf.len()]);
398
399                write_data(&self.device, buf.as_slice())?;
400            }
401
402            _ => {}
403        }
404
405        self.write_image_data_reports(
406            image_data,
407            WriteImageParameters::for_key(self.kind, image_data.len()),
408            |page_number, this_length, last_package| match self.kind {
409                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],
410
411                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],
412
413                Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => vec![0x00],
414
415                _ => vec![
416                    0x02,
417                    0x07,
418                    key,
419                    if last_package { 1 } else { 0 },
420                    (this_length & 0xff) as u8,
421                    (this_length >> 8) as u8,
422                    (page_number & 0xff) as u8,
423                    (page_number >> 8) as u8,
424                ],
425            },
426        )?;
427        Ok(())
428    }
429
430    /// Writes image data to Stream Deck device, changes must be flushed with `.flush()` before
431    /// they will appear on the device!
432    pub fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), StreamDeckError> {
433        let cache_entry = ImageCache {
434            key,
435            image_data: image_data.to_vec(), // Convert &[u8] to Vec<u8>
436        };
437
438        self.image_cache.write()?.push(cache_entry);
439
440        Ok(())
441    }
442
443    /// Writes image data to Stream Deck device's lcd strip/screen as region.
444    /// Only Stream Deck Plus supports writing LCD regions, for Stream Deck Neo use write_lcd_fill
445    pub fn write_lcd(&self, x: u16, y: u16, rect: &ImageRect) -> Result<(), StreamDeckError> {
446        self.initialize()?;
447        match self.kind {
448            Kind::Plus => (),
449            _ => return Err(StreamDeckError::UnsupportedOperation),
450        }
451
452        self.write_image_data_reports(
453            rect.data.as_slice(),
454            WriteImageParameters {
455                image_report_length: 1024,
456                image_report_payload_length: 1024 - 16,
457            },
458            |page_number, this_length, last_package| {
459                vec![
460                    0x02,
461                    0x0c,
462                    (x & 0xff) as u8,
463                    (x >> 8) as u8,
464                    (y & 0xff) as u8,
465                    (y >> 8) as u8,
466                    (rect.w & 0xff) as u8,
467                    (rect.w >> 8) as u8,
468                    (rect.h & 0xff) as u8,
469                    (rect.h >> 8) as u8,
470                    if last_package { 1 } else { 0 },
471                    (page_number & 0xff) as u8,
472                    (page_number >> 8) as u8,
473                    (this_length & 0xff) as u8,
474                    (this_length >> 8) as u8,
475                    0,
476                ]
477            },
478        )
479    }
480
481    /// Writes image data to Stream Deck device's lcd strip/screen as full fill
482    ///
483    /// You can convert your images into proper image_data like this:
484    /// ```
485    /// use elgato_streamdeck::images::convert_image_with_format;
486    /// let image_data = convert_image_with_format(device.kind().lcd_image_format(), image).unwrap();
487    /// device.write_lcd_fill(&image_data);
488    /// ```
489    pub fn write_lcd_fill(&self, image_data: &[u8]) -> Result<(), StreamDeckError> {
490        self.initialize()?;
491        match self.kind {
492            Kind::Neo => self.write_image_data_reports(
493                image_data,
494                WriteImageParameters {
495                    image_report_length: 1024,
496                    image_report_payload_length: 1024 - 8,
497                },
498                |page_number, this_length, last_package| {
499                    vec![
500                        0x02,
501                        0x0b,
502                        0,
503                        if last_package { 1 } else { 0 },
504                        (this_length & 0xff) as u8,
505                        (this_length >> 8) as u8,
506                        (page_number & 0xff) as u8,
507                        (page_number >> 8) as u8,
508                    ]
509                },
510            ),
511
512            Kind::Plus => {
513                let (w, h) = self.kind.lcd_strip_size().unwrap();
514
515                self.write_image_data_reports(
516                    image_data,
517                    WriteImageParameters {
518                        image_report_length: 1024,
519                        image_report_payload_length: 1024 - 16,
520                    },
521                    |page_number, this_length, last_package| {
522                        vec![
523                            0x02,
524                            0x0c,
525                            0,
526                            0,
527                            0,
528                            0,
529                            (w & 0xff) as u8,
530                            (w >> 8) as u8,
531                            (h & 0xff) as u8,
532                            (h >> 8) as u8,
533                            if last_package { 1 } else { 0 },
534                            (page_number & 0xff) as u8,
535                            (page_number >> 8) as u8,
536                            (this_length & 0xff) as u8,
537                            (this_length >> 8) as u8,
538                            0,
539                        ]
540                    },
541                )
542            }
543
544            _ => Err(StreamDeckError::UnsupportedOperation),
545        }
546    }
547
548    /// Sets button's image to blank, changes must be flushed with `.flush()` before
549    /// they will appear on the device!
550    pub fn clear_button_image(&self, key: u8) -> Result<(), StreamDeckError> {
551        self.initialize()?;
552        match self.kind {
553            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
554                let key = if self.kind == Kind::Akp815 {
555                    inverse_key_index(&self.kind, key)
556                } else {
557                    elgato_to_ajazz153(&self.kind, key)
558                };
559
560                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4c, 0x45, 0x00, 0x00, 0x00, if key == 0xff { 0xff } else { key + 1 }];
561
562                buf.extend(vec![0u8; 513 - buf.len()]);
563
564                write_data(&self.device, buf.as_slice())?;
565
566                Ok(())
567            }
568
569            _ => Ok(self.send_image(key, &self.kind.blank_image())?),
570        }
571    }
572
573    /// Sets blank images to every button, changes must be flushed with `.flush()` before
574    /// they will appear on the device!
575    pub fn clear_all_button_images(&self) -> Result<(), StreamDeckError> {
576        self.initialize()?;
577        match self.kind {
578            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => self.clear_button_image(0xff),
579            _ => {
580                for i in 0..self.kind.key_count() {
581                    self.clear_button_image(i)?
582                }
583                Ok(())
584            }
585        }
586    }
587
588    /// Sets specified button's image, changes must be flushed with `.flush()` before
589    /// they will appear on the device!
590    pub fn set_button_image(&self, key: u8, image: DynamicImage) -> Result<(), StreamDeckError> {
591        self.initialize()?;
592        let image_data = convert_image(self.kind, image)?;
593        self.write_image(key, &image_data)?;
594        Ok(())
595    }
596
597    /// Set logo image
598    pub fn set_logo_image(&self, image: DynamicImage) -> Result<(), StreamDeckError> {
599        self.initialize()?;
600        match self.kind {
601            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => (),
602            _ => return Err(StreamDeckError::UnsupportedOperation),
603        }
604
605        if self.kind.lcd_strip_size().is_none() {
606            return Err(StreamDeckError::UnsupportedOperation);
607        }
608        // 854 * 480 * 3
609        let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x4f, 0x47, 0x00, 0x12, 0xc3, 0xc0, 0x01];
610
611        buf.extend(vec![0u8; 513 - buf.len()]);
612
613        write_data(&self.device, buf.as_slice())?;
614
615        let mut image_buffer: DynamicImage = DynamicImage::new_rgb8(854, 480);
616
617        let ratio = 854.0 / 480.0;
618
619        let mode = "cover";
620
621        match mode {
622            "contain" => {
623                let (image_w, image_h) = (image.width(), image.height());
624                let image_ratio = image_w as f32 / image_h as f32;
625
626                let (ws, hs) = if image_ratio > ratio {
627                    (854, (854.0 / image_ratio) as u32)
628                } else {
629                    ((480.0 * image_ratio) as u32, 480)
630                };
631
632                let resized_image = image.resize(ws, hs, image::imageops::FilterType::Nearest);
633                image::imageops::overlay(
634                    &mut image_buffer,
635                    &resized_image,
636                    ((854 - resized_image.width()) / 2) as i64,
637                    ((480 - resized_image.height()) / 2) as i64,
638                );
639            }
640            "cover" => {
641                let resized_image = image.resize_to_fill(854, 480, image::imageops::FilterType::Nearest);
642                image::imageops::overlay(
643                    &mut image_buffer,
644                    &resized_image,
645                    ((854 - resized_image.width()) / 2) as i64,
646                    ((480 - resized_image.height()) / 2) as i64,
647                );
648            }
649            _ => {
650                let (image_w, image_h) = (image.width(), image.height());
651                let image_ratio = image_w as f32 / image_h as f32;
652
653                let (ws, hs) = if image_ratio > ratio {
654                    ((480.0 * image_ratio) as u32, 480)
655                } else {
656                    (854, (854.0 / image_ratio) as u32)
657                };
658
659                let resized_image = image.resize(ws, hs, image::imageops::FilterType::Nearest);
660                image::imageops::overlay(
661                    &mut image_buffer,
662                    &resized_image,
663                    ((854 - resized_image.width()) / 2) as i64,
664                    ((480 - resized_image.height()) / 2) as i64,
665                );
666            }
667        }
668
669        let mut image_data = image_buffer.rotate90().fliph().flipv().into_rgb8().to_vec();
670        for x in (0..image_data.len()).step_by(3) {
671            (image_data[x], image_data[x + 2]) = (image_data[x + 2], image_data[x])
672        }
673
674        let image_report_length = match self.kind {
675            Kind::Original => 8191,
676            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => 513,
677            _ => 1024,
678        };
679
680        let image_report_header_length = match self.kind {
681            Kind::Original | Kind::Mini | Kind::MiniMk2 => 16,
682            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => 1,
683            _ => 8,
684        };
685
686        let image_report_payload_length = match self.kind {
687            Kind::Original => image_data.len() / 2,
688            _ => image_report_length - image_report_header_length,
689        };
690
691        let mut page_number = 0;
692        let mut bytes_remaining = image_data.len();
693
694        while bytes_remaining > 0 {
695            let this_length = bytes_remaining.min(image_report_payload_length);
696            let bytes_sent = page_number * image_report_payload_length;
697
698            // Create buffer with Report ID as first byte
699            let mut buf: Vec<u8> = vec![0x00];
700
701            // Selecting header based on device
702            buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
703
704            // Adding padding
705            buf.extend(vec![0u8; image_report_length - buf.len()]);
706
707            write_data(&self.device, &buf)?;
708
709            bytes_remaining -= this_length;
710            page_number += 1;
711        }
712
713        Ok(())
714    }
715
716    /// Sets specified touch point's led strip color
717    pub fn set_touchpoint_color(&self, point: u8, red: u8, green: u8, blue: u8) -> Result<(), StreamDeckError> {
718        self.initialize()?;
719        if point >= self.kind.touchpoint_count() {
720            return Err(StreamDeckError::InvalidTouchPointIndex);
721        }
722
723        let mut buf = vec![0x03, 0x06];
724
725        let touchpoint_index: u8 = point + self.kind.key_count();
726        buf.extend(vec![touchpoint_index]);
727        buf.extend(vec![red, green, blue]);
728
729        Ok(send_feature_report(&self.device, buf.as_slice())?)
730    }
731
732    /// Sleeps the device
733    pub fn sleep(&self) -> Result<(), StreamDeckError> {
734        self.initialize()?;
735        match self.kind {
736            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
737                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4e];
738
739                buf.extend(vec![0u8; 513 - buf.len()]);
740
741                write_data(&self.device, buf.as_slice())?;
742
743                Ok(())
744            }
745
746            _ => Err(StreamDeckError::UnsupportedOperation),
747        }
748    }
749
750    /// Make periodic events to the device, to keep it alive
751    pub fn keep_alive(&self) -> Result<(), StreamDeckError> {
752        self.initialize()?;
753        match self.kind {
754            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
755                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4F, 0x4E, 0x4E, 0x45, 0x43, 0x54];
756                buf.extend(vec![0u8; 513 - buf.len()]);
757                write_data(&self.device, buf.as_slice())?;
758                Ok(())
759            }
760
761            _ => Err(StreamDeckError::UnsupportedOperation),
762        }
763    }
764
765    /// Shutdown the device
766    pub fn shutdown(&self) -> Result<(), StreamDeckError> {
767        self.initialize()?;
768        match self.kind {
769            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 => {
770                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4c, 0x45, 0x00, 0x00, 0x44, 0x43];
771                buf.extend(vec![0u8; 513 - buf.len()]);
772                write_data(&self.device, buf.as_slice())?;
773
774                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4E];
775                buf.extend(vec![0u8; 513 - buf.len()]);
776                write_data(&self.device, buf.as_slice())?;
777
778                Ok(())
779            }
780
781            _ => Err(StreamDeckError::UnsupportedOperation),
782        }
783    }
784
785    /// Flushes the button's image to the device
786    pub fn flush(&self) -> Result<(), StreamDeckError> {
787        self.initialize()?;
788
789        if self.image_cache.write()?.len() == 0 {
790            return Ok(());
791        }
792
793        for image in self.image_cache.read()?.iter() {
794            self.send_image(image.key, &image.image_data)?;
795        }
796
797        match self.kind {
798            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
799                let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
800
801                buf.extend(vec![0u8; 513 - buf.len()]);
802
803                write_data(&self.device, buf.as_slice())?;
804            }
805
806            _ => {}
807        }
808
809        self.image_cache.write()?.clear();
810
811        Ok(())
812    }
813
814    /// Returns button state reader for this device
815    pub fn get_reader(self: &Arc<Self>) -> Arc<DeviceStateReader> {
816        #[allow(clippy::arc_with_non_send_sync)]
817        Arc::new(DeviceStateReader {
818            device: self.clone(),
819            states: Mutex::new(DeviceState {
820                buttons: vec![false; self.kind.key_count() as usize + self.kind.touchpoint_count() as usize],
821                encoders: vec![false; self.kind.encoder_count() as usize],
822            }),
823        })
824    }
825
826    fn write_image_data_reports<T>(&self, image_data: &[u8], parameters: WriteImageParameters, header_fn: T) -> Result<(), StreamDeckError>
827    where
828        T: Fn(usize, usize, bool) -> Vec<u8>,
829    {
830        let image_report_length = parameters.image_report_length;
831        let image_report_payload_length = parameters.image_report_payload_length;
832
833        let mut page_number = 0;
834        let mut bytes_remaining = image_data.len();
835
836        while bytes_remaining > 0 {
837            let this_length = bytes_remaining.min(image_report_payload_length);
838            let bytes_sent = page_number * image_report_payload_length;
839
840            // Selecting header based on device
841            let mut buf: Vec<u8> = header_fn(page_number, this_length, this_length == bytes_remaining);
842
843            buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
844
845            // Adding padding
846            buf.extend(vec![0u8; image_report_length - buf.len()]);
847
848            write_data(&self.device, &buf)?;
849
850            bytes_remaining -= this_length;
851            page_number += 1;
852        }
853
854        Ok(())
855    }
856}
857
858#[derive(Clone, Copy)]
859struct WriteImageParameters {
860    pub image_report_length: usize,
861    pub image_report_payload_length: usize,
862}
863
864impl WriteImageParameters {
865    pub fn for_key(kind: Kind, image_data_len: usize) -> Self {
866        let image_report_length = match kind {
867            Kind::Original => 8191,
868            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => 513,
869            _ => 1024,
870        };
871
872        let image_report_header_length = match kind {
873            Kind::Original | Kind::Mini | Kind::MiniMk2 => 16,
874            Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => 1,
875            _ => 8,
876        };
877
878        let image_report_payload_length = match kind {
879            Kind::Original => image_data_len / 2,
880            _ => image_report_length - image_report_header_length,
881        };
882
883        Self {
884            image_report_length,
885            image_report_payload_length,
886        }
887    }
888}
889
890/// Errors that can occur while working with Stream Decks
891#[derive(Debug)]
892pub enum StreamDeckError {
893    /// HidApi error
894    HidError(HidError),
895
896    /// Failed to convert bytes into string
897    Utf8Error(Utf8Error),
898
899    /// Failed to encode image
900    ImageError(ImageError),
901
902    #[cfg(feature = "async")]
903    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
904    /// Tokio join error
905    JoinError(tokio::task::JoinError),
906
907    /// Reader mutex was poisoned
908    PoisonError,
909
910    /// There's literally nowhere to write the image
911    NoScreen,
912
913    /// Key index is invalid
914    InvalidKeyIndex,
915
916    /// Key index is invalid
917    InvalidTouchPointIndex,
918
919    /// Unrecognized Product ID
920    UnrecognizedPID,
921
922    /// The device doesn't support doing that
923    UnsupportedOperation,
924
925    /// Stream Deck sent unexpected data
926    BadData,
927}
928
929impl Display for StreamDeckError {
930    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
931        write!(f, "{:?}", self)
932    }
933}
934
935impl Error for StreamDeckError {}
936
937impl From<HidError> for StreamDeckError {
938    fn from(e: HidError) -> Self {
939        Self::HidError(e)
940    }
941}
942
943impl From<Utf8Error> for StreamDeckError {
944    fn from(e: Utf8Error) -> Self {
945        Self::Utf8Error(e)
946    }
947}
948
949impl From<ImageError> for StreamDeckError {
950    fn from(e: ImageError) -> Self {
951        Self::ImageError(e)
952    }
953}
954
955#[cfg(feature = "async")]
956impl From<tokio::task::JoinError> for StreamDeckError {
957    fn from(e: tokio::task::JoinError) -> Self {
958        Self::JoinError(e)
959    }
960}
961
962impl<T> From<PoisonError<T>> for StreamDeckError {
963    fn from(_value: PoisonError<T>) -> Self {
964        Self::PoisonError
965    }
966}
967
968/// Tells what changed in button states
969#[derive(Copy, Clone, Debug, Hash)]
970pub enum DeviceStateUpdate {
971    /// Button got pressed down
972    ButtonDown(u8),
973
974    /// Button got released
975    ButtonUp(u8),
976
977    /// Encoder got pressed down
978    EncoderDown(u8),
979
980    /// Encoder was released from being pressed down
981    EncoderUp(u8),
982
983    /// Encoder was twisted
984    EncoderTwist(u8, i8),
985
986    /// Touch Point got pressed down
987    TouchPointDown(u8),
988
989    /// Touch Point got released
990    TouchPointUp(u8),
991
992    /// Touch screen received short press
993    TouchScreenPress(u16, u16),
994
995    /// Touch screen received long press
996    TouchScreenLongPress(u16, u16),
997
998    /// Touch screen received a swipe
999    TouchScreenSwipe((u16, u16), (u16, u16)),
1000}
1001
1002#[derive(Default)]
1003struct DeviceState {
1004    /// Buttons include Touch Points state
1005    pub buttons: Vec<bool>,
1006    pub encoders: Vec<bool>,
1007}
1008
1009/// Button reader that keeps state of the Stream Deck and returns events instead of full states
1010pub struct DeviceStateReader {
1011    device: Arc<StreamDeck>,
1012    states: Mutex<DeviceState>,
1013}
1014
1015impl DeviceStateReader {
1016    /// Reads states and returns updates
1017    pub fn read(&self, timeout: Option<Duration>) -> Result<Vec<DeviceStateUpdate>, StreamDeckError> {
1018        let input = self.device.read_input(timeout)?;
1019        let mut my_states = self.states.lock()?;
1020
1021        let mut updates = vec![];
1022
1023        match input {
1024            StreamDeckInput::ButtonStateChange(buttons) => {
1025                for (index, (their, mine)) in zip(buttons.iter(), my_states.buttons.iter()).enumerate() {
1026                    match self.device.kind {
1027                        Kind::Akp153 | Kind::Akp153E | Kind::Akp153R | Kind::Akp815 | Kind::MiraBoxHSV293S => {
1028                            if *their {
1029                                updates.push(DeviceStateUpdate::ButtonDown(index as u8));
1030                                updates.push(DeviceStateUpdate::ButtonUp(index as u8));
1031                            }
1032                        }
1033                        _ => {
1034                            if their != mine {
1035                                let key_count = self.device.kind.key_count();
1036                                if index < key_count as usize {
1037                                    if *their {
1038                                        updates.push(DeviceStateUpdate::ButtonDown(index as u8));
1039                                    } else {
1040                                        updates.push(DeviceStateUpdate::ButtonUp(index as u8));
1041                                    }
1042                                } else if *their {
1043                                    updates.push(DeviceStateUpdate::TouchPointDown(index as u8 - key_count));
1044                                } else {
1045                                    updates.push(DeviceStateUpdate::TouchPointUp(index as u8 - key_count));
1046                                }
1047                            }
1048                        }
1049                    }
1050                }
1051
1052                my_states.buttons = buttons;
1053            }
1054
1055            StreamDeckInput::EncoderStateChange(encoders) => {
1056                for (index, (their, mine)) in zip(encoders.iter(), my_states.encoders.iter()).enumerate() {
1057                    if *their != *mine {
1058                        if *their {
1059                            updates.push(DeviceStateUpdate::EncoderDown(index as u8));
1060                        } else {
1061                            updates.push(DeviceStateUpdate::EncoderUp(index as u8));
1062                        }
1063                    }
1064                }
1065
1066                my_states.encoders = encoders;
1067            }
1068
1069            StreamDeckInput::EncoderTwist(twist) => {
1070                for (index, change) in twist.iter().enumerate() {
1071                    if *change != 0 {
1072                        updates.push(DeviceStateUpdate::EncoderTwist(index as u8, *change));
1073                    }
1074                }
1075            }
1076
1077            StreamDeckInput::TouchScreenPress(x, y) => {
1078                updates.push(DeviceStateUpdate::TouchScreenPress(x, y));
1079            }
1080
1081            StreamDeckInput::TouchScreenLongPress(x, y) => {
1082                updates.push(DeviceStateUpdate::TouchScreenLongPress(x, y));
1083            }
1084
1085            StreamDeckInput::TouchScreenSwipe(s, e) => {
1086                updates.push(DeviceStateUpdate::TouchScreenSwipe(s, e));
1087            }
1088
1089            _ => {}
1090        }
1091
1092        drop(my_states);
1093
1094        Ok(updates)
1095    }
1096}