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::RwLock;
16use std::sync::{Arc, Mutex, PoisonError};
17use std::time::Duration;
18
19use crate::images::{convert_image, ImageRect};
20use hidapi::{HidApi, HidDevice, HidError, HidResult};
21use image::{DynamicImage, ImageError};
22
23use crate::info::{is_vendor_familiar, Kind};
24use crate::util::{extract_str, flip_key_index, get_feature_report, read_button_states, read_data, read_encoder_input, read_lcd_input, send_feature_report, write_data};
25
26/// Various information about Stream Deck devices
27pub mod info;
28/// Utility functions for working with Stream Deck devices
29pub mod util;
30/// Image processing functions
31pub mod images;
32
33/// Async Stream Deck
34#[cfg(feature = "async")]
35#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
36pub mod asynchronous;
37#[cfg(feature = "async")]
38#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
39pub use asynchronous::AsyncStreamDeck;
40
41/// Creates an instance of the HidApi
42///
43/// Can be used if you don't want to link hidapi crate into your project
44pub fn new_hidapi() -> HidResult<HidApi> {
45    HidApi::new()
46}
47
48/// Actually refreshes the device list
49pub fn refresh_device_list(hidapi: &mut HidApi) -> HidResult<()> {
50    hidapi.refresh_devices()
51}
52
53/// Returns a list of devices as (Kind, Serial Number) that could be found using HidApi.
54///
55/// **WARNING:** To refresh the list, use [refresh_device_list]
56pub fn list_devices(hidapi: &HidApi) -> Vec<(Kind, String)> {
57    hidapi
58        .device_list()
59        .filter_map(|d| {
60            if !is_vendor_familiar(&d.vendor_id()) {
61                return None;
62            }
63
64            if let Some(serial) = d.serial_number() {
65                Some((Kind::from_vid_pid(d.vendor_id(), d.product_id())?, serial.to_string()))
66            } else {
67                None
68            }
69        })
70        .collect::<HashSet<_>>()
71        .into_iter()
72        .collect()
73}
74
75/// Type of input that the device produced
76#[derive(Clone, Debug)]
77pub enum StreamDeckInput {
78    /// No data was passed from the device
79    NoData,
80
81    /// Button was pressed
82    ButtonStateChange(Vec<bool>),
83
84    /// Encoder/Knob was pressed
85    EncoderStateChange(Vec<bool>),
86
87    /// Encoder/Knob was twisted/turned
88    EncoderTwist(Vec<i8>),
89
90    /// Touch screen received short press
91    TouchScreenPress(u16, u16),
92
93    /// Touch screen received long press
94    TouchScreenLongPress(u16, u16),
95
96    /// Touch screen received a swipe
97    TouchScreenSwipe((u16, u16), (u16, u16)),
98}
99
100impl StreamDeckInput {
101    /// Checks if there's data received or not
102    pub fn is_empty(&self) -> bool {
103        matches!(self, StreamDeckInput::NoData)
104    }
105}
106
107/// Interface for a Stream Deck device
108pub struct StreamDeck {
109    /// Kind of the device
110    kind: Kind,
111    /// Connected HIDDevice
112    device: HidDevice,
113    /// Temporarily cache the image before sending it to the device
114    image_cache: RwLock<Vec<ImageCache>>,
115}
116
117struct ImageCache {
118    key: u8,
119    image_data: Vec<u8>,
120}
121
122/// Static functions of the struct
123impl StreamDeck {
124    /// Attempts to connect to the device
125    pub fn connect(hidapi: &HidApi, kind: Kind, serial: &str) -> Result<StreamDeck, StreamDeckError> {
126        let device = hidapi.open_serial(kind.vendor_id(), kind.product_id(), serial)?;
127
128        Ok(StreamDeck {
129            kind,
130            device,
131            image_cache: RwLock::new(vec![]),
132        })
133    }
134}
135
136/// Instance methods of the struct
137impl StreamDeck {
138    /// Returns kind of the Stream Deck
139    pub fn kind(&self) -> Kind {
140        self.kind
141    }
142
143    /// Returns manufacturer string of the device
144    pub fn manufacturer(&self) -> Result<String, StreamDeckError> {
145        Ok(self.device.get_manufacturer_string()?.unwrap_or_else(|| "Unknown".to_string()))
146    }
147
148    /// Returns product string of the device
149    pub fn product(&self) -> Result<String, StreamDeckError> {
150        Ok(self.device.get_product_string()?.unwrap_or_else(|| "Unknown".to_string()))
151    }
152
153    /// Returns serial number of the device
154    pub fn serial_number(&self) -> Result<String, StreamDeckError> {
155        match self.kind {
156            Kind::Original | Kind::Mini => {
157                let bytes = get_feature_report(&self.device, 0x03, 17)?;
158                Ok(extract_str(&bytes[5..])?)
159            }
160
161            Kind::MiniMk2 | Kind::MiniDiscord | Kind::MiniMk2Module => {
162                let bytes = get_feature_report(&self.device, 0x03, 32)?;
163                Ok(extract_str(&bytes[5..])?)
164            }
165
166            _ => {
167                let bytes = get_feature_report(&self.device, 0x06, 32)?;
168                Ok(extract_str(&bytes[2..])?)
169            }
170        }
171        .map(|s| s.replace('\u{0001}', ""))
172    }
173
174    /// Returns firmware version of the StreamDeck
175    pub fn firmware_version(&self) -> Result<String, StreamDeckError> {
176        match self.kind {
177            Kind::Original | Kind::Mini | Kind::MiniMk2 | Kind::MiniDiscord => {
178                let bytes = get_feature_report(&self.device, 0x04, 17)?;
179                Ok(extract_str(&bytes[5..])?)
180            }
181
182            Kind::MiniMk2Module => {
183                let bytes = get_feature_report(&self.device, 0xA1, 17)?;
184                Ok(extract_str(&bytes[5..])?)
185            }
186
187            _ => {
188                let bytes = get_feature_report(&self.device, 0x05, 32)?;
189                Ok(extract_str(&bytes[6..])?)
190            }
191        }
192    }
193
194    /// Reads all possible input from Stream Deck device
195    pub fn read_input(&self, timeout: Option<Duration>) -> Result<StreamDeckInput, StreamDeckError> {
196        match &self.kind {
197            Kind::Plus => {
198                let data = read_data(&self.device, 14.max(5 + self.kind.encoder_count() as usize), timeout)?;
199
200                if data[0] == 0 {
201                    return Ok(StreamDeckInput::NoData);
202                }
203
204                match &data[1] {
205                    0x0 => Ok(StreamDeckInput::ButtonStateChange(read_button_states(&self.kind, &data))),
206
207                    0x2 => Ok(read_lcd_input(&data)?),
208
209                    0x3 => Ok(read_encoder_input(&self.kind, &data)?),
210
211                    _ => Err(StreamDeckError::BadData),
212                }
213            }
214
215            _ => {
216                let data = match self.kind {
217                    Kind::Original | Kind::Mini | Kind::MiniMk2 | Kind::MiniDiscord | Kind::MiniMk2Module => read_data(&self.device, 1 + self.kind.key_count() as usize, timeout),
218                    _ => read_data(&self.device, 4 + self.kind.key_count() as usize + self.kind.touchpoint_count() as usize, timeout),
219                }?;
220
221                if data[0] == 0 {
222                    return Ok(StreamDeckInput::NoData);
223                }
224
225                Ok(StreamDeckInput::ButtonStateChange(read_button_states(&self.kind, &data)))
226            }
227        }
228    }
229
230    /// Resets the device
231    pub fn reset(&self) -> Result<(), StreamDeckError> {
232        match self.kind {
233            Kind::Original | Kind::Mini | Kind::MiniMk2 | Kind::MiniDiscord | Kind::MiniMk2Module => {
234                let mut buf = vec![0x0B, 0x63];
235
236                buf.extend(vec![0u8; 15]);
237
238                Ok(send_feature_report(&self.device, buf.as_slice())?)
239            }
240
241            _ => {
242                let mut buf = vec![0x03, 0x02];
243
244                buf.extend(vec![0u8; 30]);
245
246                Ok(send_feature_report(&self.device, buf.as_slice())?)
247            }
248        }
249    }
250
251    /// Sets brightness of the device, value range is 0 - 100
252    pub fn set_brightness(&self, percent: u8) -> Result<(), StreamDeckError> {
253        let percent = percent.clamp(0, 100);
254
255        match self.kind {
256            Kind::Original | Kind::Mini | Kind::MiniMk2 | Kind::MiniDiscord | Kind::MiniMk2Module => {
257                let mut buf = vec![0x05, 0x55, 0xaa, 0xd1, 0x01, percent];
258
259                buf.extend(vec![0u8; 11]);
260
261                Ok(send_feature_report(&self.device, buf.as_slice())?)
262            }
263
264            _ => {
265                let mut buf = vec![0x03, 0x08, percent];
266
267                buf.extend(vec![0u8; 29]);
268
269                Ok(send_feature_report(&self.device, buf.as_slice())?)
270            }
271        }
272    }
273
274    fn send_image(&self, key: u8, image_data: &[u8]) -> Result<(), StreamDeckError> {
275        if key >= self.kind.key_count() {
276            return Err(StreamDeckError::InvalidKeyIndex);
277        }
278
279        let key = if let Kind::Original = self.kind { flip_key_index(&self.kind, key) } else { key };
280
281        if !self.kind.is_visual() {
282            return Err(StreamDeckError::NoScreen);
283        }
284
285        self.write_image_data_reports(
286            image_data,
287            WriteImageParameters::for_key(self.kind, image_data.len()),
288            |page_number, this_length, last_package| match self.kind {
289                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],
290
291                Kind::Mini | Kind::MiniMk2 | Kind::MiniDiscord | Kind::MiniMk2Module => 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],
292
293                _ => vec![
294                    0x02,
295                    0x07,
296                    key,
297                    if last_package { 1 } else { 0 },
298                    (this_length & 0xff) as u8,
299                    (this_length >> 8) as u8,
300                    (page_number & 0xff) as u8,
301                    (page_number >> 8) as u8,
302                ],
303            },
304        )?;
305        Ok(())
306    }
307
308    /// Writes image data to Stream Deck device, changes must be flushed with `.flush()` before
309    /// they will appear on the device!
310    pub fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), StreamDeckError> {
311        let cache_entry = ImageCache { key, image_data: image_data.to_vec() };
312
313        self.image_cache.write()?.push(cache_entry);
314
315        Ok(())
316    }
317
318    /// Writes image data to Stream Deck device's lcd strip/screen as region.
319    /// Only Stream Deck Plus supports writing LCD regions, for Stream Deck Neo use write_lcd_fill
320    pub fn write_lcd(&self, x: u16, y: u16, rect: &ImageRect) -> Result<(), StreamDeckError> {
321        match self.kind {
322            Kind::Plus => (),
323            _ => return Err(StreamDeckError::UnsupportedOperation),
324        }
325
326        self.write_image_data_reports(
327            rect.data.as_slice(),
328            WriteImageParameters {
329                image_report_length: 1024,
330                image_report_payload_length: 1024 - 16,
331            },
332            |page_number, this_length, last_package| {
333                vec![
334                    0x02,
335                    0x0c,
336                    (x & 0xff) as u8,
337                    (x >> 8) as u8,
338                    (y & 0xff) as u8,
339                    (y >> 8) as u8,
340                    (rect.w & 0xff) as u8,
341                    (rect.w >> 8) as u8,
342                    (rect.h & 0xff) as u8,
343                    (rect.h >> 8) as u8,
344                    if last_package { 1 } else { 0 },
345                    (page_number & 0xff) as u8,
346                    (page_number >> 8) as u8,
347                    (this_length & 0xff) as u8,
348                    (this_length >> 8) as u8,
349                    0,
350                ]
351            },
352        )
353    }
354
355    /// Writes image data to Stream Deck device's lcd strip/screen as full fill
356    ///
357    /// You can convert your images into proper image_data like this:
358    /// ```
359    /// use elgato_streamdeck::images::convert_image_with_format;
360    /// let image_data = convert_image_with_format(device.kind().lcd_image_format(), image).unwrap();
361    /// device.write_lcd_fill(&image_data);
362    /// ```
363    pub fn write_lcd_fill(&self, image_data: &[u8]) -> Result<(), StreamDeckError> {
364        match self.kind {
365            Kind::Neo => self.write_image_data_reports(
366                image_data,
367                WriteImageParameters {
368                    image_report_length: 1024,
369                    image_report_payload_length: 1024 - 8,
370                },
371                |page_number, this_length, last_package| {
372                    vec![
373                        0x02,
374                        0x0b,
375                        0,
376                        if last_package { 1 } else { 0 },
377                        (this_length & 0xff) as u8,
378                        (this_length >> 8) as u8,
379                        (page_number & 0xff) as u8,
380                        (page_number >> 8) as u8,
381                    ]
382                },
383            ),
384
385            Kind::Plus => {
386                let (w, h) = self.kind.lcd_strip_size().unwrap();
387
388                self.write_image_data_reports(
389                    image_data,
390                    WriteImageParameters {
391                        image_report_length: 1024,
392                        image_report_payload_length: 1024 - 16,
393                    },
394                    |page_number, this_length, last_package| {
395                        vec![
396                            0x02,
397                            0x0c,
398                            0,
399                            0,
400                            0,
401                            0,
402                            (w & 0xff) as u8,
403                            (w >> 8) as u8,
404                            (h & 0xff) as u8,
405                            (h >> 8) as u8,
406                            if last_package { 1 } else { 0 },
407                            (page_number & 0xff) as u8,
408                            (page_number >> 8) as u8,
409                            (this_length & 0xff) as u8,
410                            (this_length >> 8) as u8,
411                            0,
412                        ]
413                    },
414                )
415            }
416
417            _ => Err(StreamDeckError::UnsupportedOperation),
418        }
419    }
420
421    /// Sets button's image to blank, changes must be flushed with `.flush()` before
422    /// they will appear on the device!
423    pub fn clear_button_image(&self, key: u8) -> Result<(), StreamDeckError> {
424        self.send_image(key, &self.kind.blank_image())
425    }
426
427    /// Sets blank images to every button, changes must be flushed with `.flush()` before
428    /// they will appear on the device!
429    pub fn clear_all_button_images(&self) -> Result<(), StreamDeckError> {
430        for i in 0..self.kind.key_count() {
431            self.clear_button_image(i)?
432        }
433        Ok(())
434    }
435
436    /// Sets specified button's image, changes must be flushed with `.flush()` before
437    /// they will appear on the device!
438    pub fn set_button_image(&self, key: u8, image: DynamicImage) -> Result<(), StreamDeckError> {
439        let image_data = convert_image(self.kind, image)?;
440        self.write_image(key, &image_data)?;
441        Ok(())
442    }
443
444    /// Sets specified touch point's led strip color
445    pub fn set_touchpoint_color(&self, point: u8, red: u8, green: u8, blue: u8) -> Result<(), StreamDeckError> {
446        if point >= self.kind.touchpoint_count() {
447            return Err(StreamDeckError::InvalidTouchPointIndex);
448        }
449
450        let mut buf = vec![0x03, 0x06];
451
452        let touchpoint_index: u8 = point + self.kind.key_count();
453        buf.extend(vec![touchpoint_index]);
454        buf.extend(vec![red, green, blue]);
455
456        Ok(send_feature_report(&self.device, buf.as_slice())?)
457    }
458
459    /// Flushes the button's image to the device
460    pub fn flush(&self) -> Result<(), StreamDeckError> {
461        if self.image_cache.read()?.is_empty() {
462            return Ok(());
463        }
464
465        for image in self.image_cache.read()?.iter() {
466            self.send_image(image.key, &image.image_data)?;
467        }
468
469        self.image_cache.write()?.clear();
470
471        Ok(())
472    }
473
474    /// Returns button state reader for this device
475    pub fn get_reader(self: &Arc<Self>) -> Arc<DeviceStateReader> {
476        #[allow(clippy::arc_with_non_send_sync)]
477        Arc::new(DeviceStateReader {
478            device: self.clone(),
479            states: Mutex::new(DeviceState {
480                buttons: vec![false; self.kind.key_count() as usize + self.kind.touchpoint_count() as usize],
481                encoders: vec![false; self.kind.encoder_count() as usize],
482            }),
483        })
484    }
485
486    fn write_image_data_reports<T>(&self, image_data: &[u8], parameters: WriteImageParameters, header_fn: T) -> Result<(), StreamDeckError>
487    where
488        T: Fn(usize, usize, bool) -> Vec<u8>,
489    {
490        let image_report_length = parameters.image_report_length;
491        let image_report_payload_length = parameters.image_report_payload_length;
492
493        let mut page_number = 0;
494        let mut bytes_remaining = image_data.len();
495
496        while bytes_remaining > 0 {
497            let this_length = bytes_remaining.min(image_report_payload_length);
498            let bytes_sent = page_number * image_report_payload_length;
499
500            // Selecting header based on device
501            let mut buf: Vec<u8> = header_fn(page_number, this_length, this_length == bytes_remaining);
502
503            buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
504
505            // Adding padding
506            buf.extend(vec![0u8; image_report_length - buf.len()]);
507
508            write_data(&self.device, &buf)?;
509
510            bytes_remaining -= this_length;
511            page_number += 1;
512        }
513
514        Ok(())
515    }
516}
517
518#[derive(Clone, Copy)]
519struct WriteImageParameters {
520    pub image_report_length: usize,
521    pub image_report_payload_length: usize,
522}
523
524impl WriteImageParameters {
525    pub fn for_key(kind: Kind, image_data_len: usize) -> Self {
526        let image_report_length = match kind {
527            Kind::Original => 8191,
528            _ => 1024,
529        };
530
531        let image_report_header_length = match kind {
532            Kind::Original | Kind::Mini | Kind::MiniMk2 | Kind::MiniDiscord | Kind::MiniMk2Module => 16,
533            _ => 8,
534        };
535
536        let image_report_payload_length = match kind {
537            Kind::Original => image_data_len / 2,
538            _ => image_report_length - image_report_header_length,
539        };
540
541        Self {
542            image_report_length,
543            image_report_payload_length,
544        }
545    }
546}
547
548/// Errors that can occur while working with Stream Decks
549#[derive(Debug)]
550pub enum StreamDeckError {
551    /// HidApi error
552    HidError(HidError),
553
554    /// Failed to convert bytes into string
555    Utf8Error(Utf8Error),
556
557    /// Failed to encode image
558    ImageError(ImageError),
559
560    #[cfg(feature = "async")]
561    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
562    /// Tokio join error
563    JoinError(tokio::task::JoinError),
564
565    /// Reader mutex was poisoned
566    PoisonError,
567
568    /// There's literally nowhere to write the image
569    NoScreen,
570
571    /// Key index is invalid
572    InvalidKeyIndex,
573
574    /// Key index is invalid
575    InvalidTouchPointIndex,
576
577    /// Unrecognized Product ID
578    UnrecognizedPID,
579
580    /// The device doesn't support doing that
581    UnsupportedOperation,
582
583    /// Stream Deck sent unexpected data
584    BadData,
585}
586
587impl Display for StreamDeckError {
588    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
589        write!(f, "{:?}", self)
590    }
591}
592
593impl Error for StreamDeckError {}
594
595impl From<HidError> for StreamDeckError {
596    fn from(e: HidError) -> Self {
597        Self::HidError(e)
598    }
599}
600
601impl From<Utf8Error> for StreamDeckError {
602    fn from(e: Utf8Error) -> Self {
603        Self::Utf8Error(e)
604    }
605}
606
607impl From<ImageError> for StreamDeckError {
608    fn from(e: ImageError) -> Self {
609        Self::ImageError(e)
610    }
611}
612
613#[cfg(feature = "async")]
614impl From<tokio::task::JoinError> for StreamDeckError {
615    fn from(e: tokio::task::JoinError) -> Self {
616        Self::JoinError(e)
617    }
618}
619
620impl<T> From<PoisonError<T>> for StreamDeckError {
621    fn from(_value: PoisonError<T>) -> Self {
622        Self::PoisonError
623    }
624}
625
626/// Tells what changed in button states
627#[derive(Copy, Clone, Debug, Hash)]
628pub enum DeviceStateUpdate {
629    /// Button got pressed down
630    ButtonDown(u8),
631
632    /// Button got released
633    ButtonUp(u8),
634
635    /// Encoder got pressed down
636    EncoderDown(u8),
637
638    /// Encoder was released from being pressed down
639    EncoderUp(u8),
640
641    /// Encoder was twisted
642    EncoderTwist(u8, i8),
643
644    /// Touch Point got pressed down
645    TouchPointDown(u8),
646
647    /// Touch Point got released
648    TouchPointUp(u8),
649
650    /// Touch screen received short press
651    TouchScreenPress(u16, u16),
652
653    /// Touch screen received long press
654    TouchScreenLongPress(u16, u16),
655
656    /// Touch screen received a swipe
657    TouchScreenSwipe((u16, u16), (u16, u16)),
658}
659
660#[derive(Default)]
661struct DeviceState {
662    /// Buttons include Touch Points state
663    pub buttons: Vec<bool>,
664    pub encoders: Vec<bool>,
665}
666
667/// Button reader that keeps state of the Stream Deck and returns events instead of full states
668pub struct DeviceStateReader {
669    device: Arc<StreamDeck>,
670    states: Mutex<DeviceState>,
671}
672
673impl DeviceStateReader {
674    /// Reads states and returns updates
675    pub fn read(&self, timeout: Option<Duration>) -> Result<Vec<DeviceStateUpdate>, StreamDeckError> {
676        let input = self.device.read_input(timeout)?;
677        let mut my_states = self.states.lock()?;
678
679        let mut updates = vec![];
680
681        match input {
682            StreamDeckInput::ButtonStateChange(buttons) => {
683                for (index, (their, mine)) in zip(buttons.iter(), my_states.buttons.iter()).enumerate() {
684                    if their != mine {
685                        let key_count = self.device.kind.key_count();
686                        if index < key_count as usize {
687                            if *their {
688                                updates.push(DeviceStateUpdate::ButtonDown(index as u8));
689                            } else {
690                                updates.push(DeviceStateUpdate::ButtonUp(index as u8));
691                            }
692                        } else if *their {
693                            updates.push(DeviceStateUpdate::TouchPointDown(index as u8 - key_count));
694                        } else {
695                            updates.push(DeviceStateUpdate::TouchPointUp(index as u8 - key_count));
696                        }
697                    }
698                }
699
700                my_states.buttons = buttons;
701            }
702
703            StreamDeckInput::EncoderStateChange(encoders) => {
704                for (index, (their, mine)) in zip(encoders.iter(), my_states.encoders.iter()).enumerate() {
705                    if *their != *mine {
706                        if *their {
707                            updates.push(DeviceStateUpdate::EncoderDown(index as u8));
708                        } else {
709                            updates.push(DeviceStateUpdate::EncoderUp(index as u8));
710                        }
711                    }
712                }
713
714                my_states.encoders = encoders;
715            }
716
717            StreamDeckInput::EncoderTwist(twist) => {
718                for (index, change) in twist.iter().enumerate() {
719                    if *change != 0 {
720                        updates.push(DeviceStateUpdate::EncoderTwist(index as u8, *change));
721                    }
722                }
723            }
724
725            StreamDeckInput::TouchScreenPress(x, y) => {
726                updates.push(DeviceStateUpdate::TouchScreenPress(x, y));
727            }
728
729            StreamDeckInput::TouchScreenLongPress(x, y) => {
730                updates.push(DeviceStateUpdate::TouchScreenLongPress(x, y));
731            }
732
733            StreamDeckInput::TouchScreenSwipe(s, e) => {
734                updates.push(DeviceStateUpdate::TouchScreenSwipe(s, e));
735            }
736
737            _ => {}
738        }
739
740        drop(my_states);
741
742        Ok(updates)
743    }
744}