elgato_streamdeck/
asynchronous.rs

1//! Code from this module is using [block_in_place](tokio::task::block_in_place),
2//! and so they cannot be used in [current_thread](tokio::runtime::Builder::new_current_thread) runtimes
3
4use std::iter::zip;
5use std::sync::Arc;
6use std::time::Duration;
7
8use hidapi::{HidApi, HidResult};
9use image::DynamicImage;
10use tokio::sync::Mutex;
11use tokio::task::block_in_place;
12use tokio::time::sleep;
13
14use crate::{DeviceState, DeviceStateUpdate, Kind, list_devices, StreamDeck, StreamDeckError, StreamDeckInput};
15use crate::images::{convert_image_async, ImageRect};
16
17/// Actually refreshes the device list, can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
18pub fn refresh_device_list_async(hidapi: &mut HidApi) -> HidResult<()> {
19    block_in_place(move || hidapi.refresh_devices())
20}
21
22/// Returns a list of devices as (Kind, Serial Number) that could be found using HidApi,
23/// can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
24///
25/// **WARNING:** To refresh the list, use [refresh_device_list]
26pub fn list_devices_async(hidapi: &HidApi) -> Vec<(Kind, String)> {
27    block_in_place(move || list_devices(&hidapi))
28}
29
30/// Stream Deck interface suitable to be used in async, uses [block_in_place](block_in_place)
31/// so this wrapper cannot be used in [current_thread](tokio::runtime::Builder::new_current_thread) runtimes
32#[derive(Clone)]
33pub struct AsyncStreamDeck {
34    kind: Kind,
35    device: Arc<Mutex<StreamDeck>>,
36}
37
38/// Static functions of the struct
39impl AsyncStreamDeck {
40    /// Attempts to connect to the device, can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
41    pub fn connect(hidapi: &HidApi, kind: Kind, serial: &str) -> Result<AsyncStreamDeck, StreamDeckError> {
42        let device = block_in_place(move || StreamDeck::connect(hidapi, kind, serial))?;
43
44        Ok(AsyncStreamDeck {
45            kind,
46            device: Arc::new(Mutex::new(device)),
47        })
48    }
49}
50
51/// Instance methods of the struct
52impl AsyncStreamDeck {
53    /// Returns kind of the Stream Deck
54    pub fn kind(&self) -> Kind {
55        self.kind
56    }
57
58    /// Returns manufacturer string of the device
59    pub async fn manufacturer(&self) -> Result<String, StreamDeckError> {
60        let device = self.device.lock().await;
61        Ok(block_in_place(move || device.manufacturer())?)
62    }
63
64    /// Returns product string of the device
65    pub async fn product(&self) -> Result<String, StreamDeckError> {
66        let device = self.device.lock().await;
67        Ok(block_in_place(move || device.product())?)
68    }
69
70    /// Returns serial number of the device
71    pub async fn serial_number(&self) -> Result<String, StreamDeckError> {
72        let device = self.device.lock().await;
73        Ok(block_in_place(move || device.serial_number())?)
74    }
75
76    /// Returns firmware version of the StreamDeck
77    pub async fn firmware_version(&self) -> Result<String, StreamDeckError> {
78        let device = self.device.lock().await;
79        Ok(block_in_place(move || device.firmware_version())?)
80    }
81
82    /// Reads button states, awaits until there's data.
83    /// Poll rate determines how often button state gets checked
84    pub async fn read_input(&self, poll_rate: f32) -> Result<StreamDeckInput, StreamDeckError> {
85        loop {
86            let device = self.device.lock().await;
87            let data = block_in_place(move || device.read_input(None))?;
88
89            if !data.is_empty() {
90                return Ok(data);
91            }
92
93            sleep(Duration::from_secs_f32(1.0 / poll_rate)).await;
94        }
95    }
96
97    /// Resets the device
98    pub async fn reset(&self) -> Result<(), StreamDeckError> {
99        let device = self.device.lock().await;
100        Ok(block_in_place(move || device.reset())?)
101    }
102
103    /// Sets brightness of the device, value range is 0 - 100
104    pub async fn set_brightness(&self, percent: u8) -> Result<(), StreamDeckError> {
105        let device = self.device.lock().await;
106        Ok(block_in_place(move || device.set_brightness(percent))?)
107    }
108
109    /// Writes image data to Stream Deck device, changes must be flushed with `.flush()` before
110    /// they will appear on the device!
111    pub async fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), StreamDeckError> {
112        let device = self.device.lock().await;
113        Ok(block_in_place(move || device.write_image(key, image_data))?)
114    }
115
116    /// Writes image data to Stream Deck device's lcd strip/screen as region.
117    /// Only Stream Deck Plus supports writing LCD regions, for Stream Deck Neo use write_lcd_fill
118    pub async fn write_lcd(&self, x: u16, y: u16, rect: &ImageRect) -> Result<(), StreamDeckError> {
119        let device = self.device.lock().await;
120        Ok(block_in_place(move || device.write_lcd(x, y, rect))?)
121    }
122
123    /// Writes image data to Stream Deck device's lcd strip/screen as full fill
124    ///
125    /// You can convert your images into proper image_data like this:
126    /// ```
127    /// use elgato_streamdeck::images::{convert_image_with_format_async};
128    /// let image_data = convert_image_with_format_async(device.kind().lcd_image_format(), image).await.unwrap();
129    /// device.write_lcd_fill(&image_data).await;
130    /// ```
131    pub async fn write_lcd_fill(&self, image_data: &[u8]) -> Result<(), StreamDeckError> {
132        let device = self.device.lock().await;
133        Ok(block_in_place(move || device.write_lcd_fill(image_data))?)
134    }
135
136    /// Sets button's image to blank, changes must be flushed with `.flush()` before
137    /// they will appear on the device!
138    pub async fn clear_button_image(&self, key: u8) -> Result<(), StreamDeckError> {
139        let device = self.device.lock().await;
140        Ok(block_in_place(move || device.clear_button_image(key))?)
141    }
142
143    /// Sets blank images to every button, changes must be flushed with `.flush()` before
144    /// they will appear on the device!
145    pub async fn clear_all_button_images(&self) -> Result<(), StreamDeckError> {
146        let device = self.device.lock().await;
147        Ok(block_in_place(move || device.clear_all_button_images())?)
148    }
149
150    /// Sets specified button's image, changes must be flushed with `.flush()` before
151    /// they will appear on the device!
152    pub async fn set_button_image(&self, key: u8, image: DynamicImage) -> Result<(), StreamDeckError> {
153        let image = convert_image_async(self.kind, image)?;
154
155        let device = self.device.lock().await;
156        Ok(block_in_place(move || device.write_image(key, &image))?)
157    }
158
159    /// Set logo image
160    pub async fn set_logo_image(&self, image: DynamicImage) -> Result<(), StreamDeckError> {
161        let device = self.device.lock().await;
162        Ok(block_in_place(move || device.set_logo_image(image))?)
163    }
164
165    /// Sets specified touch point's led strip color
166    pub async fn set_touchpoint_color(&self, point: u8, red: u8, green: u8, blue: u8) -> Result<(), StreamDeckError> {
167        let device = self.device.lock().await;
168        Ok(block_in_place(move || device.set_touchpoint_color(point, red, green, blue))?)
169    }
170
171    /// Sleeps the device
172    pub async fn sleep(&self) -> Result<(), StreamDeckError> {
173        let device = self.device.lock().await;
174        Ok(block_in_place(move || device.sleep())?)
175    }
176
177    /// Make periodic events to the device, to keep it alive
178    pub async fn keep_alive(&self) -> Result<(), StreamDeckError> {
179        let device = self.device.lock().await;
180        Ok(block_in_place(move || device.keep_alive())?)
181    }
182
183    /// Shutdown the device
184    pub async fn shutdown(&self) -> Result<(), StreamDeckError> {
185        let device = self.device.lock().await;
186        Ok(block_in_place(move || device.shutdown())?)
187    }
188
189    /// Flushes the button's image to the device
190    pub async fn flush(&self) -> Result<(), StreamDeckError> {
191        let device = self.device.lock().await;
192        Ok(block_in_place(move || device.flush())?)
193    }
194
195    /// Returns button state reader for this device
196    pub fn get_reader(&self) -> Arc<AsyncDeviceStateReader> {
197        Arc::new(AsyncDeviceStateReader {
198            device: self.clone(),
199            states: Mutex::new(DeviceState {
200                buttons: vec![false; self.kind.key_count() as usize + self.kind.touchpoint_count() as usize],
201                encoders: vec![false; self.kind.encoder_count() as usize],
202            }),
203        })
204    }
205}
206
207/// Button reader that keeps state of the Stream Deck and returns events instead of full states
208pub struct AsyncDeviceStateReader {
209    device: AsyncStreamDeck,
210    states: Mutex<DeviceState>,
211}
212
213impl AsyncDeviceStateReader {
214    /// Reads states and returns updates
215    pub async fn read(&self, poll_rate: f32) -> Result<Vec<DeviceStateUpdate>, StreamDeckError> {
216        let input = self.device.read_input(poll_rate).await?;
217        let mut my_states = self.states.lock().await;
218
219        let mut updates = vec![];
220
221        match input {
222            StreamDeckInput::ButtonStateChange(buttons) => {
223                for (index, (their, mine)) in zip(buttons.iter(), my_states.buttons.iter()).enumerate() {
224                    match self.device.kind {
225                        Kind::Akp153 | Kind::Akp153E | Kind::Akp815 | Kind::MiraBoxHSV293S => {
226                            if *their {
227                                updates.push(DeviceStateUpdate::ButtonDown(index as u8));
228                                updates.push(DeviceStateUpdate::ButtonUp(index as u8));
229                            }
230                        }
231                        _ => {
232                            if *their != *mine {
233                                if index < self.device.kind.key_count() as usize {
234                                    if *their {
235                                        updates.push(DeviceStateUpdate::ButtonDown(index as u8));
236                                    } else {
237                                        updates.push(DeviceStateUpdate::ButtonUp(index as u8));
238                                    }
239                                } else {
240                                    if *their {
241                                        updates.push(DeviceStateUpdate::TouchPointDown(index as u8 - self.device.kind.key_count()));
242                                    } else {
243                                        updates.push(DeviceStateUpdate::TouchPointUp(index as u8 - self.device.kind.key_count()));
244                                    }
245                                }
246                            }
247                        }
248                    }
249                }
250
251                my_states.buttons = buttons;
252            }
253
254            StreamDeckInput::EncoderStateChange(encoders) => {
255                for (index, (their, mine)) in zip(encoders.iter(), my_states.encoders.iter()).enumerate() {
256                    if *their != *mine {
257                        if *their {
258                            updates.push(DeviceStateUpdate::EncoderDown(index as u8));
259                        } else {
260                            updates.push(DeviceStateUpdate::EncoderUp(index as u8));
261                        }
262                    }
263                }
264
265                my_states.encoders = encoders;
266            }
267
268            StreamDeckInput::EncoderTwist(twist) => {
269                for (index, change) in twist.iter().enumerate() {
270                    if *change != 0 {
271                        updates.push(DeviceStateUpdate::EncoderTwist(index as u8, *change));
272                    }
273                }
274            }
275
276            StreamDeckInput::TouchScreenPress(x, y) => {
277                updates.push(DeviceStateUpdate::TouchScreenPress(x, y));
278            }
279
280            StreamDeckInput::TouchScreenLongPress(x, y) => {
281                updates.push(DeviceStateUpdate::TouchScreenLongPress(x, y));
282            }
283
284            StreamDeckInput::TouchScreenSwipe(s, e) => {
285                updates.push(DeviceStateUpdate::TouchScreenSwipe(s, e));
286            }
287
288            _ => {}
289        }
290
291        drop(my_states);
292
293        Ok(updates)
294    }
295}