Skip to main content

streamdeck_rs/
lib.rs

1#[cfg(feature = "logging")]
2pub mod logging;
3pub mod registration;
4pub mod socket;
5
6pub use crate::registration::RegistrationInfo;
7pub use crate::socket::StreamDeckSocket;
8
9use serde::{de, ser};
10use serde_derive::{Deserialize, Serialize};
11use serde_json::Value;
12use serde_repr::{Deserialize_repr, Serialize_repr};
13use std::fmt;
14
15/// A message received from the Stream Deck software.
16///
17/// - `G` represents the global settings that are persisted within the Stream Deck software.
18/// - `S` represents the settings that are persisted within the Stream Deck software.
19/// - `M` represents the messages that are received from the property inspector.
20///
21/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/)
22#[derive(Debug, Deserialize, Serialize)]
23#[serde(tag = "event", rename_all = "camelCase")]
24pub enum Message<G, S, M> {
25    /// A key has been pressed.
26    ///
27    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#keydown)
28    #[serde(rename_all = "camelCase")]
29    KeyDown {
30        /// The uuid of the action.
31        action: String,
32        /// The instance of the action (key or part of a multiaction).
33        context: String,
34        /// The device where the key was pressed.
35        device: String,
36        /// Additional information about the key press.
37        payload: KeyPayload<S>,
38    },
39    /// A key has been released.
40    ///
41    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#keyup)
42    #[serde(rename_all = "camelCase")]
43    KeyUp {
44        /// The uuid of the action.
45        action: String,
46        /// The instance of the action (key or part of a multiaction).
47        context: String,
48        /// The device where the key was pressed.
49        device: String,
50        /// Additional information about the key press.
51        payload: KeyPayload<S>,
52    },
53    /// An instance of the action has been added to the display.
54    ///
55    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#willappear)
56    #[serde(rename_all = "camelCase")]
57    WillAppear {
58        /// The uuid of the action.
59        action: String,
60        /// The instance of the action (key or part of a multiaction).
61        context: String,
62        /// The device where the action will appear, or None if it does not appear on a device.
63        device: Option<String>,
64        /// Additional information about the action's appearance.
65        payload: VisibilityPayload<S>,
66    },
67    /// An instance of the action has been removed from the display.
68    ///
69    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#willdisappear)
70    #[serde(rename_all = "camelCase")]
71    WillDisappear {
72        /// The uuid of the action.
73        action: String,
74        /// The instance of the action (key or part of a multiaction).
75        context: String,
76        /// The device where the action was visible, or None if it was not on a device.
77        device: Option<String>,
78        /// Additional information about the action's appearance.
79        payload: VisibilityPayload<S>,
80    },
81    /// The title has changed for an instance of an action.
82    ///
83    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#titleparametersdidchange)
84    #[serde(rename_all = "camelCase")]
85    TitleParametersDidChange {
86        /// The uuid of the action.
87        action: String,
88        /// The instance of the action (key or part of a multiaction).
89        context: String,
90        /// The device where the action is visible, or None if it is not on a device.
91        device: Option<String>,
92        /// Additional information about the new title.
93        payload: TitleParametersPayload<S>,
94    },
95    /// A device has connected.
96    ///
97    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#devicedidconnect)
98    #[serde(rename_all = "camelCase")]
99    DeviceDidConnect {
100        /// The ID of the device that has connected.
101        device: String,
102        /// Information about the device.
103        device_info: DeviceInfo,
104    },
105    /// A device has disconnected.
106    ///
107    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#devicediddisconnect)
108    #[serde(rename_all = "camelCase")]
109    DeviceDidDisconnect {
110        /// The ID of the device that has disconnected.
111        device: String,
112    },
113    /// An application monitored by the manifest file has launched.
114    ///
115    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#applicationdidlaunch)
116    #[serde(rename_all = "camelCase")]
117    ApplicationDidLaunch {
118        /// Information about the launched application.
119        payload: ApplicationPayload,
120    },
121    /// An application monitored by the manifest file has terminated.
122    ///
123    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#applicationdidterminate)
124    #[serde(rename_all = "camelCase")]
125    ApplicationDidTerminate {
126        /// Information about the terminated application.
127        payload: ApplicationPayload,
128    },
129    /// The property inspector has sent data.
130    ///
131    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#sendtoplugin)
132    #[serde(rename_all = "camelCase")]
133    SendToPlugin {
134        /// The uuid of the action.
135        action: String,
136        /// The instance of the action (key or part of a multiaction).
137        context: String,
138        /// Information sent from the property inspector.
139        payload: M,
140    },
141    /// The application has sent settings for an action.
142    ///
143    /// This message is sent in response to GetSettings, but also after the
144    /// property inspector changes the settings.
145    ///
146    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#didreceivesettings)
147    #[serde(rename_all = "camelCase")]
148    DidReceiveSettings {
149        /// The uuid of the action.
150        action: String,
151        /// The instance of the action (key or part of a multiaction).
152        context: String,
153        /// The device where the action exists.
154        device: String,
155        /// The current settings for the action.
156        payload: KeyPayload<S>,
157    },
158    /// The property inspector for an action has become visible.
159    ///
160    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#propertyinspectordidappear)
161    #[serde(rename_all = "camelCase")]
162    PropertyInspectorDidAppear {
163        /// The uuid of the action.
164        action: String,
165        /// The instance of the action (key or part of a multiaction).
166        context: String,
167        /// The device where the action exists.
168        device: String,
169    },
170    /// The property inspector for an action is no longer visible.
171    ///
172    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#propertyinspectordiddisappear)
173    #[serde(rename_all = "camelCase")]
174    PropertyInspectorDidDisappear {
175        /// The uuid of the action.
176        action: String,
177        /// The instance of the action (key or part of a multiaction).
178        context: String,
179        /// The device where the action exists.
180        device: String,
181    },
182    /// The application has sent settings for an action.
183    ///
184    /// This message is sent in response to GetGlobalSettings, but also after
185    /// the property inspector changes the settings.
186    ///
187    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#didreceiveglobalsettings)
188    #[serde(rename_all = "camelCase")]
189    DidReceiveGlobalSettings {
190        /// The current settings for the action.
191        payload: GlobalSettingsPayload<G>,
192    },
193    /// The computer has resumed from sleep.
194    ///
195    /// Added in Stream Deck software version 4.3.
196    ///
197    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#systemdidwakeup)
198    SystemDidWakeUp,
199
200    /// The touchscreen has been tapped.
201    ///
202    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received#touchtap-sd)
203    #[serde(rename_all = "camelCase")]
204    TouchTap {
205        /// The uuid of the action.
206        action: String,
207        /// The instance of the action (key or part of a multiaction).
208        context: String,
209        /// The device where the action exists.
210        device: String,
211        /// Additional information about the touch event.
212        payload: TouchTapPayload<S>,
213    },
214
215    /// An encoder has been pressed.
216    ///
217    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received#dialdown-sd)
218    #[serde(rename_all = "camelCase")]
219    DialDown {
220        /// The uuid of the action.
221        action: String,
222        /// The instance of the action (key or part of a multiaction).
223        context: String,
224        /// The device where the action exists.
225        device: String,
226        /// Additional information about the press event.
227        payload: DialDownPayload<S>,
228    },
229
230    /// An encoder has been released.
231    ///
232    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received#dialup-sd)
233    #[serde(rename_all = "camelCase")]
234    DialUp {
235        /// The uuid of the action.
236        action: String,
237        /// The instance of the action (key or part of a multiaction).
238        context: String,
239        /// The device where the action exists.
240        device: String,
241        /// Additional information about the release event.
242        payload: DialUpPayload<S>,
243    },
244
245    /// An encoder has been rotated.
246    ///
247    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received#dialrotate-sd)
248    #[serde(rename_all = "camelCase")]
249    DialRotate {
250        /// The uuid of the action.
251        action: String,
252        /// The instance of the action (key or part of a multiaction).
253        context: String,
254        /// The device where the action exists.
255        device: String,
256        /// Additional information about the rotate event.
257        payload: DialRotatePayload<S>,
258    },
259
260    /// An event from an unsupported version of the Stream Deck software.
261    ///
262    /// This occurs when the Stream Deck software sends an event that is not
263    /// understood. Usually this will be because the Stream Deck software is
264    /// newer than the plugin, and it should be safe to ignore these.
265    #[serde(other)]
266    Unknown,
267}
268
269/// A message to be sent to the Stream Deck software.
270///
271/// - `G` represents the global settings that are persisted within the Stream Deck software.
272/// - `S` represents the action settings that are persisted within the Stream Deck software.
273/// - `M` represents the messages that are sent to the property inspector.
274///
275/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/)
276#[derive(Debug, Deserialize, Serialize)]
277#[serde(tag = "event", rename_all = "camelCase")]
278pub enum MessageOut<G, S, M> {
279    /// Set the title of an action instance.
280    ///
281    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#settitle)
282    #[serde(rename_all = "camelCase")]
283    SetTitle {
284        /// The instance of the action (key or part of a multiaction).
285        context: String,
286        /// The title to set.
287        payload: TitlePayload,
288    },
289    /// Set the image of an action instance.
290    ///
291    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#setimage)
292    #[serde(rename_all = "camelCase")]
293    SetImage {
294        /// The instance of the action (key or part of a multiaction).
295        context: String,
296        /// The image to set.
297        payload: ImagePayload,
298    },
299    /// Temporarily overlay the key image with an alert icon.
300    ///
301    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#showalert)
302    #[serde(rename_all = "camelCase")]
303    ShowAlert {
304        /// The instance of the action (key or part of a multiaction).
305        context: String,
306    },
307    /// Temporarily overlay the key image with a checkmark.
308    ///
309    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#showok)
310    #[serde(rename_all = "camelCase")]
311    ShowOk {
312        /// The instance of the action (key or part of a multiaction).
313        context: String,
314    },
315    /// Retrieve settings for an instance of an action via DidReceiveSettings.
316    ///
317    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#getsettings)
318    #[serde(rename_all = "camelCase")]
319    GetSettings {
320        /// The instance of the action (key or part of a multiaction).
321        context: String,
322    },
323    /// Store settings for an instance of an action.
324    ///
325    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#setsettings)
326    #[serde(rename_all = "camelCase")]
327    SetSettings {
328        /// The instance of the action (key or part of a multiaction).
329        context: String,
330        /// The settings to save.
331        payload: S,
332    },
333    /// Set the state of an action.
334    ///
335    /// Normally, Stream Deck changes the state of an action automatically when the key is pressed.
336    ///
337    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#setstate)
338    #[serde(rename_all = "camelCase")]
339    SetState {
340        /// The instance of the action (key or part of a multiaction).
341        context: String,
342        /// The desired state.
343        payload: StatePayload,
344    },
345    /// Send data to the property inspector.
346    ///
347    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#sendtopropertyinspector)
348    #[serde(rename_all = "camelCase")]
349    SendToPropertyInspector {
350        /// The uuid of the action.
351        action: String,
352        /// The instance of the action (key or part of a multiaction).
353        context: String,
354        /// The message to send.
355        payload: M,
356    },
357    /// Select a new profile.
358    ///
359    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#switchtoprofile)
360    #[serde(rename_all = "camelCase")]
361    SwitchToProfile {
362        /// The instance of the action (key or part of a multiaction).
363        context: String,
364        /// The device to change the profile of.
365        device: String,
366        /// The profile to activate.
367        payload: ProfilePayload,
368    },
369    /// Open a URL in the default browser.
370    ///
371    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#openurl)
372    #[serde(rename_all = "camelCase")]
373    OpenUrl {
374        /// The url to open.
375        payload: UrlPayload,
376    },
377    /// Retrieve plugin settings for via DidReceiveGlobalSettings.
378    ///
379    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#getglobalsettings)
380    #[serde(rename_all = "camelCase")]
381    GetGlobalSettings {
382        /// The instance of the action (key or part of a multiaction).
383        context: String,
384    },
385    /// Store plugin settings.
386    ///
387    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#setglobalsettings)
388    #[serde(rename_all = "camelCase")]
389    SetGlobalSettings {
390        /// The instance of the action (key or part of a multiaction).
391        context: String,
392        /// The settings to save.
393        payload: G,
394    },
395    /// Write to the log.
396    ///
397    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#logmessage)
398    #[serde(rename_all = "camelCase")]
399    LogMessage {
400        /// The message to log.
401        payload: LogMessagePayload,
402    },
403    /// Set feedback.
404    ///
405    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#setfeedback-sd)
406    #[serde(rename_all = "camelCase")]
407    SetFeedback {
408        /// The instance of the action (key or part of a multiaction).
409        context: String,
410        /// The data to send to the display.
411        payload: Value,
412    },
413    /// Set feedback layout.
414    ///
415    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#setfeedbacklayout-sd)
416    #[serde(rename_all = "camelCase")]
417    SetFeedbackLayout {
418        /// The instance of the action (key or part of a multiaction).
419        context: String,
420        /// The data to send to the display.
421        payload: SetFeedbackLayoutPayload,
422    },
423    /// Set trigger description.
424    ///
425    /// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#settriggerdescription-sd)
426    #[serde(rename_all = "camelCase")]
427    SetTriggerDescription {
428        /// The instance of the action (key or part of a multiaction).
429        context: String,
430        /// The data to send to the display.
431        payload: SetTriggerDescriptionPayload,
432    },
433}
434
435/// The target of a command.
436#[derive(Debug, Deserialize_repr, Serialize_repr)]
437#[repr(u8)]
438pub enum Target {
439    /// Both the device and a the display within the Stream Deck software.
440    Both = 0,
441    /// Only the device.
442    Hardware = 1,
443    /// Only the display within the Stream Deck software.
444    Software = 2,
445}
446
447/// The title to set as part of a [SetTitle](enum.MessageOut.html#variant.SetTitle) message.
448///
449/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#settitle)
450#[derive(Debug, Deserialize, Serialize)]
451#[serde(rename_all = "camelCase")]
452pub struct TitlePayload {
453    /// The new title.
454    pub title: Option<String>,
455    /// The target displays.
456    pub target: Target,
457    /// The state to set the title for. If not set, it is set for all states.
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub state: Option<u8>,
460}
461
462/// The image to set as part of a [SetImage](enum.MessageOut.html#variant.SetImage) message.
463///
464/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#setimage)
465#[derive(Debug, Deserialize, Serialize)]
466#[serde(rename_all = "camelCase")]
467pub struct ImagePayload {
468    /// An image in the form of a data URI.
469    pub image: Option<String>,
470    /// The target displays.
471    pub target: Target,
472    /// The state to set the image for. If not set, it is set for all states.
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub state: Option<u8>,
475}
476
477/// The state to set as part of a [SetState](enum.MessageOut.html#variant.SetState) message.
478///
479/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#setstate)
480#[derive(Debug, Deserialize, Serialize)]
481#[serde(rename_all = "camelCase")]
482pub struct StatePayload {
483    /// The new state.
484    pub state: u8,
485}
486
487/// The profile to activate as part of a [SwitchToProfile](enum.MessageOut.html#variant.SwitchToProfile) message.
488///
489/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#SwitchToProfile)
490#[derive(Debug, Deserialize, Serialize)]
491#[serde(rename_all = "camelCase")]
492pub struct ProfilePayload {
493    /// The name of the profile to activate.
494    pub profile: String,
495}
496
497/// The URL to launch as part of a [OpenUrl](enum.MessageOut.html#variant.OpenUrl) message.
498///
499/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent/#openurl)
500#[derive(Debug, Deserialize, Serialize)]
501#[serde(rename_all = "camelCase")]
502pub struct UrlPayload {
503    /// The URL to launch.
504    pub url: String,
505}
506
507/// Additional information about the key pressed.
508#[derive(Debug, Deserialize, Serialize)]
509#[serde(rename_all = "camelCase")]
510pub struct KeyPayload<S> {
511    /// The stored settings for the action instance.
512    pub settings: S,
513    /// The location of the key that was pressed, or None if this action instance is part of a multi action.
514    pub coordinates: Option<Coordinates>,
515    /// The current state of the action instance.
516    pub state: Option<u8>,
517    /// The desired state of the action instance (if this instance is part of a multi action).
518    pub user_desired_state: Option<u8>,
519    //TODO: is_in_multi_action ignored. replace coordinates with enum Location { Coordinates, MultiAction }.
520}
521
522/// Additional information about a key's appearance.
523#[derive(Debug, Deserialize, Serialize)]
524#[serde(rename_all = "camelCase")]
525pub struct VisibilityPayload<S> {
526    /// The stored settings for the action instance.
527    pub settings: S,
528    /// The location of the key, or None if this action instance is part of a multi action.
529    pub coordinates: Option<Coordinates>,
530    /// The state of the action instance.
531    pub state: Option<u8>,
532    //TODO: is_in_multi_action ignored. replace coordinates with enum Location { Coordinates, MultiAction }.
533}
534
535/// The new title of a key.
536#[derive(Debug, Deserialize, Serialize)]
537#[serde(rename_all = "camelCase")]
538pub struct TitleParametersPayload<S> {
539    /// The stored settings for the action instance.
540    pub settings: S,
541    /// The location of the key, or None if this action instance is part of a multi action.
542    pub coordinates: Coordinates,
543    /// The state of the action instance.
544    pub state: Option<u8>,
545    /// The new title.
546    pub title: String,
547    /// Additional parameters for the display of the title.
548    pub title_parameters: TitleParameters,
549}
550
551/// The new global settings.
552#[derive(Debug, Deserialize, Serialize)]
553#[serde(rename_all = "camelCase")]
554pub struct GlobalSettingsPayload<G> {
555    /// The stored settings for the plugin.
556    pub settings: G,
557}
558
559/// A log message.
560#[derive(Debug, Deserialize, Serialize)]
561#[serde(rename_all = "camelCase")]
562pub struct LogMessagePayload {
563    /// The log message text.
564    pub message: String,
565}
566
567/// A layout update message.
568///
569/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent#setfeedbacklayout-sd)
570#[derive(Debug, Deserialize, Serialize)]
571#[serde(rename_all = "camelCase")]
572pub struct SetFeedbackLayoutPayload {
573    /// A predefined layout identifier or the relative path to a JSON file that contains a custom layout.
574    pub layout: String,
575}
576
577/// A trigger description update message.
578///
579/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-sent#settriggerdescription-sd)
580#[derive(Debug, Deserialize, Serialize)]
581#[serde(rename_all = "camelCase")]
582pub struct SetTriggerDescriptionPayload {
583    /// A value that describes the long-touch interaction with the touch display.
584    pub long_touch: Option<String>,
585    /// A value that describes the push interaction with the dial.
586    pub push: Option<String>,
587    /// A value that describes the rotate interaction with the dial.
588    pub rotate: Option<String>,
589    /// A value that describes the touch interaction with the touch display.
590    pub touch: Option<String>,
591}
592
593/// Additional information about a touch tap event.
594///
595/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received#touchtap-sd)
596#[derive(Debug, Deserialize, Serialize)]
597#[serde(rename_all = "camelCase")]
598pub struct TouchTapPayload<S> {
599    /// The stored settings for the action instance.
600    pub settings: S,
601    /// The location of the action triggered.
602    pub coordinates: Option<Coordinates>,
603    /// The coordinates of the touch event within the LCD slot associated with the action.
604    pub tap_pos: (u8, u8),
605    /// Whether the tap was long.
606    pub hold: bool,
607}
608
609/// Additional information about an encoder press event.
610///
611/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received#dialdown-sd)
612#[derive(Debug, Deserialize, Serialize)]
613#[serde(rename_all = "camelCase")]
614pub struct DialDownPayload<S> {
615    /// The stored settings for the action instance.
616    pub settings: S,
617    /// The location of the action triggered.
618    pub coordinates: Option<Coordinates>,
619}
620
621/// Additional information about an encoder release event.
622///
623/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received#dialup-sd)
624#[derive(Debug, Deserialize, Serialize)]
625#[serde(rename_all = "camelCase")]
626pub struct DialUpPayload<S> {
627    /// The stored settings for the action instance.
628    pub settings: S,
629    /// The location of the action triggered.
630    pub coordinates: Option<Coordinates>,
631}
632
633/// Additional information about an encoder rotate event.
634///
635/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received#dialrotate-sd)
636#[derive(Debug, Deserialize, Serialize)]
637#[serde(rename_all = "camelCase")]
638pub struct DialRotatePayload<S> {
639    /// The stored settings for the action instance.
640    pub settings: S,
641    /// The location of the action triggered.
642    pub coordinates: Option<Coordinates>,
643    /// The number of ticks of the rotation (positive values are clockwise).
644    pub ticks: i64,
645    /// Whether the encoder was being pressed down during the rotation.
646    pub pressed: bool,
647}
648
649/// Information about a hardware device.
650///
651/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#devicedidconnect)
652#[derive(Debug, Deserialize, Serialize)]
653#[serde(rename_all = "camelCase")]
654pub struct DeviceInfo {
655    /// The user-provided name of the device.
656    ///
657    /// Added in Stream Deck software version 4.3.
658    pub name: Option<String>,
659    /// The size of the device.
660    pub size: DeviceSize,
661    /// The type of the device, or None if the Stream Deck software is running with no device attached.
662    #[serde(rename = "type")]
663    pub _type: Option<DeviceType>,
664}
665
666/// Information about a monitored application that has launched or terminated.
667#[derive(Debug, Deserialize, Serialize)]
668#[serde(rename_all = "camelCase")]
669pub struct ApplicationPayload {
670    /// The name of the application.
671    pub application: String,
672}
673
674/// The location of a key on a device.
675///
676/// Locations are specified using zero-indexed values starting from the top left corner of the device.
677#[derive(Debug, Deserialize, Serialize)]
678#[serde(rename_all = "camelCase")]
679pub struct Coordinates {
680    /// The x coordinate of the key.
681    pub column: u8,
682    /// The y-coordinate of the key.
683    pub row: u8,
684}
685
686/// The vertical alignment of a title.
687///
688/// Titles are always centered horizontally.
689#[derive(Debug, Deserialize, Serialize)]
690#[serde(rename_all = "camelCase")]
691pub enum Alignment {
692    /// The title should appear at the top of the key.
693    Top,
694    /// The title should appear in the middle of the key.
695    Middle,
696    /// The title should appear at the bottom of the key.
697    Bottom,
698}
699
700/// Style information for a title.
701///
702/// [Official Documentation](https://docs.elgato.com/sdk/plugins/events-received/#titleparametersdidchange)
703#[derive(Debug, Deserialize, Serialize)]
704#[serde(rename_all = "camelCase")]
705pub struct TitleParameters {
706    /// The name of the font family.
707    pub font_family: String,
708    /// The font size.
709    pub font_size: u8,
710    /// Whether the font is bold and/or italic.
711    pub font_style: String,
712    /// Whether the font is underlined.
713    pub font_underline: bool,
714    /// Whether the title is displayed.
715    pub show_title: bool,
716    /// The vertical alignment of the title.
717    pub title_alignment: Alignment,
718    /// The color of the title.
719    pub title_color: String,
720}
721
722/// The size of a device in keys.
723#[derive(Debug, Deserialize, Serialize)]
724#[serde(rename_all = "camelCase")]
725pub struct DeviceSize {
726    /// The number of key columns on the device.
727    pub columns: u8,
728    /// The number of key rows on the device.
729    pub rows: u8,
730}
731
732/// The type of connected hardware device.
733///
734/// [Official Documentation](https://docs.elgato.com/sdk/plugins/manifest/#profiles)
735#[derive(Debug)]
736pub enum DeviceType {
737    /// The [Stream Deck](https://www.elgato.com/en/gaming/stream-deck).
738    StreamDeck, // 0
739    /// The [Stream Deck Mini](https://www.elgato.com/en/gaming/stream-deck-mini).
740    StreamDeckMini, // 1
741    /// The [Stream Deck XL](https://www.elgato.com/en/gaming/stream-deck-xl).
742    ///
743    /// Added in Stream Deck software version 4.3.
744    StreamDeckXl, // 2
745    /// The [Stream Deck Mobile](https://www.elgato.com/en/gaming/stream-deck-mobile) app.
746    ///
747    /// Added in Stream Deck software version 4.3.
748    StreamDeckMobile, // 3
749    /// The G-keys in Corsair keyboards
750    ///
751    /// Added in Stream Deck software version 4.7
752    CorsairGKeys, // 4
753    /// The [Stream Deck Pedal](https://www.elgato.com/en/stream-deck-pedal).
754    ///
755    /// Added in Stream Deck software version 5.2
756    StreamDeckPedal, // 5
757    /// The [Corsair Voyager Streaming Laptop](https://www.corsair.com/us/en/voyager-a1600-gaming-streaming-pc-laptop).
758    ///
759    /// Added in Stream Deck software version 5.3
760    CorsairVoyager, // 6
761    /// The [Stream Deck +](https://www.elgato.com/en/stream-deck-plus)
762    ///
763    /// Added in Stream Deck software version 6.0
764    StreamDeckPlus, // 7
765    /// A device not documented in the 6.0 SDK.
766    Unknown(u64),
767}
768
769impl ser::Serialize for DeviceType {
770    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
771    where
772        S: ser::Serializer,
773    {
774        serializer.serialize_u64(match self {
775            DeviceType::StreamDeck => 0,
776            DeviceType::StreamDeckMini => 1,
777            DeviceType::StreamDeckXl => 2,
778            DeviceType::StreamDeckMobile => 3,
779            DeviceType::CorsairGKeys => 4,
780            DeviceType::StreamDeckPedal => 5,
781            DeviceType::CorsairVoyager => 6,
782            DeviceType::StreamDeckPlus => 7,
783            DeviceType::Unknown(value) => *value,
784        })
785    }
786}
787
788impl<'de> de::Deserialize<'de> for DeviceType {
789    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
790    where
791        D: de::Deserializer<'de>,
792    {
793        struct Visitor;
794
795        impl<'de> de::Visitor<'de> for Visitor {
796            type Value = DeviceType;
797
798            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
799                formatter.write_str("an integer")
800            }
801
802            fn visit_u64<E>(self, value: u64) -> Result<DeviceType, E>
803            where
804                E: de::Error,
805            {
806                Ok(match value {
807                    0 => DeviceType::StreamDeck,
808                    1 => DeviceType::StreamDeckMini,
809                    2 => DeviceType::StreamDeckXl,
810                    3 => DeviceType::StreamDeckMobile,
811                    4 => DeviceType::CorsairGKeys,
812                    5 => DeviceType::StreamDeckPedal,
813                    6 => DeviceType::CorsairVoyager,
814                    7 => DeviceType::StreamDeckPlus,
815                    value => DeviceType::Unknown(value),
816                })
817            }
818        }
819
820        deserializer.deserialize_u64(Visitor)
821    }
822}
823
824#[derive(Clone, Debug, Eq, PartialEq)]
825pub enum Color {
826    Rgb { r: u8, g: u8, b: u8 },
827    Rgba { r: u8, g: u8, b: u8, a: u8 },
828}
829
830impl ser::Serialize for Color {
831    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
832    where
833        S: ser::Serializer,
834    {
835        let html_color = match self {
836            Color::Rgb { r, g, b } => format!("#{:02x}{:02x}{:02x}", r, g, b),
837            Color::Rgba { r, g, b, a } => format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a),
838        };
839        serializer.serialize_str(&html_color)
840    }
841}
842
843impl<'de> de::Deserialize<'de> for Color {
844    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
845    where
846        D: de::Deserializer<'de>,
847    {
848        struct Visitor;
849
850        impl<'de> de::Visitor<'de> for Visitor {
851            type Value = Color;
852
853            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
854                formatter.write_str("a hex color")
855            }
856
857            fn visit_str<E>(self, value: &str) -> Result<Color, E>
858            where
859                E: de::Error,
860            {
861                let parse_component = |value: &str| {
862                    u8::from_str_radix(value, 16)
863                        .map_err(|_| E::invalid_value(de::Unexpected::Str(value), &self))
864                };
865
866                let parse_rgb = |value: &str| {
867                    if &value[0..1] != "#" {
868                        return Err(E::custom("expected string to begin with '#'"));
869                    }
870
871                    let r = parse_component(&value[1..3])?;
872                    let g = parse_component(&value[3..5])?;
873                    let b = parse_component(&value[5..7])?;
874
875                    Ok((r, g, b))
876                };
877
878                match value.len() {
879                    7 => {
880                        let (r, g, b) = parse_rgb(value)?;
881                        Ok(Color::Rgb { r, g, b })
882                    }
883                    9 => {
884                        let (r, g, b) = parse_rgb(value)?;
885                        let a = parse_component(&value[7..9])?;
886                        Ok(Color::Rgba { r, g, b, a })
887                    }
888                    _ => Err(E::invalid_length(value.len(), &self)),
889                }
890            }
891        }
892
893        deserializer.deserialize_str(Visitor)
894    }
895}
896
897#[cfg(test)]
898mod test {
899    use super::Color;
900
901    #[test]
902    fn color() {
903        let color_a = Color::Rgb {
904            r: 0x12,
905            g: 0x34,
906            b: 0x56,
907        };
908        let color_b = Color::Rgba {
909            r: 0x12,
910            g: 0x12,
911            b: 0x12,
912            a: 0x12,
913        };
914
915        let as_json = r##"["#123456","#12121212"]"##;
916        let colors: Vec<Color> = serde_json::from_str(as_json).expect("array of colors");
917
918        assert_eq!(2, colors.len());
919        assert_eq!(color_a, colors[0]);
920        assert_eq!(color_b, colors[1]);
921
922        let json_str: String = serde_json::to_string(&vec![color_a, color_b]).expect("JSON array");
923        assert_eq!(as_json, json_str);
924    }
925}