haptics/
lib.rs

1// SPDX-FileCopyrightText: 2024 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3pub mod messages;
4
5use std::time::Duration;
6
7use num_derive::{FromPrimitive, ToPrimitive};
8use num_traits::{FromPrimitive, ToPrimitive};
9use server::{AsScalar, CheckedConn, CheckedPermissions, FromScalar, MessageAllowed};
10
11use crate::messages::*;
12
13#[macro_export]
14macro_rules! use_api {
15    () => {
16        mod haptics_permissions {
17            use haptics::messages::*;
18            #[derive(Clone, Default, server::Permissions)]
19            #[server_name = "os/haptics"]
20            pub struct HapticsPermissions;
21        }
22        type HapticsApi = haptics::HapticsApi<haptics_permissions::HapticsPermissions>;
23    };
24}
25
26#[derive(Debug, Copy, Clone, ToPrimitive, FromPrimitive)]
27pub enum HapticPattern {
28    Click,
29    StrongClick,
30    DoubleClick,
31    StrongClick100,
32    StrongClick60,
33    StrongClick30,
34    SharpClick100,
35    SharpClick60,
36    SharpClick30,
37    SoftBump100,
38    SoftBump60,
39    SoftBump30,
40    DoubleClick100,
41    DoubleClick60,
42    TripleClick100,
43    SoftFuzz60,
44    StrongBuzz100,
45    Alert750ms,
46    Alert1000ms,
47    StrongClickOne100,
48    StrongClickTwo80,
49    StrongClickThree60,
50    StrongClickFour30,
51    MediumClickOne100,
52    MediumClickTwo80,
53    MediumClickThree60,
54    SharpTickOne100,
55    SharpTickTwo80,
56    SharpTickThree60,
57    ShortDoubleClickStrongOne100,
58    ShortDoubleClickStrongTwo80,
59    ShortDoubleClickStrongThree60,
60    ShortDoubleClickStrongFour30,
61    ShortDoubleClickMediumOne100,
62    ShortDoubleClickMediumTwo80,
63    ShortDoubleClickMediumThree60,
64    ShortDoubleSharpTickOne100,
65    ShortDoubleSharpTickTwo80,
66    ShortDoubleSharpTickThree60,
67    LongDoubleSharpClickStrongOne100,
68    LongDoubleSharpClickStrongTwo80,
69    LongDoubleSharpClickStrongThree60,
70    LongDoubleSharpClickStrongFour30,
71    LongDoubleSharpClickMediumOne100,
72    LongDoubleSharpClickMediumTwo80,
73    LongDoubleSharpClickMediumThree60,
74    LongDoubleSharpTickOne100,
75    LongDoubleSharpTickTwo80,
76    LongDoubleSharpTickThree60,
77    BuzzOne100,
78    BuzzTwo80,
79    BuzzThree60,
80    BuzzFour40,
81    BuzzFive20,
82    PulsingStrongOne100,
83    PulsingStrongTwo60,
84    PulsingMediumOne100,
85    PulsingMediumTwo60,
86    PulsingSharpOne100,
87    PulsingSharpTwo60,
88    TransitionClickOne100,
89    TransitionClickTwo80,
90    TransitionClickThree60,
91    TransitionClickFour40,
92    TransitionClickFive20,
93    TransitionClickSix10,
94    TransitionHumOne100,
95    TransitionHumTwo80,
96    TransitionHumThree60,
97    TransitionHumFour40,
98    TransitionHumFive20,
99    TransitionHumSix10,
100    TransitionRampDownLongSmoothOne100to0,
101    TransitionRampDownLongSmoothTwo100to0,
102    TransitionRampDownMediumSmoothOne100to0,
103    TransitionRampDownMediumSmoothTwo100to0,
104    TransitionRampDownShortSmoothOne100to0,
105    TransitionRampDownShortSmoothTwo100to0,
106    TransitionRampDownLongSharpOne100to0,
107    TransitionRampDownLongSharpTwo100to0,
108    TransitionRampDownMediumSharpOne100to0,
109    TransitionRampDownMediumSharpTwo100to0,
110    TransitionRampDownShortSharpOne100to0,
111    TransitionRampDownShortSharpTwo100to0,
112    TransitionRampUpLongSmoothOne0to100,
113    TransitionRampUpLongSmoothTwo0to100,
114    TransitionRampUpMediumSmoothOne0to100,
115    TransitionRampUpMediumSmoothTwo0to100,
116    TransitionRampUpShortSmoothOne0to100,
117    TransitionRampUpShortSmoothTwo0to100,
118    TransitionRampUpLongSharpOne0to100,
119    TransitionRampUpLongSharpTwo0to100,
120    TransitionRampUpMediumSharpOne0to100,
121    TransitionRampUpMediumSharpTwo0to100,
122    TransitionRampUpShortSharpOne0to100,
123    TransitionRampUpShortSharpTwo0to100,
124    TransitionRampDownLongSmoothOne50to0,
125    TransitionRampDownLongSmoothTwo50to0,
126    TransitionRampDownMediumSmoothOne50to0,
127    TransitionRampDownMediumSmoothTwo50to0,
128    TransitionRampDownShortSmoothOne50to0,
129    TransitionRampDownShortSmoothTwo50to0,
130    TransitionRampDownLongSharpOne50to0,
131    TransitionRampDownLongSharpTwo50to0,
132    TransitionRampDownMediumSharpOne50to0,
133    TransitionRampDownMediumSharpTwo50to0,
134    TransitionRampDownShortSharpOne50to0,
135    TransitionRampDownShortSharpTwo50to0,
136    TransitionRampUpLongSmoothOne0to50,
137    TransitionRampUpLongSmoothTwo0to50,
138    TransitionRampUpMediumSmoothOne0to50,
139    TransitionRampUpMediumSmoothTwo0to50,
140    TransitionRampUpShortSmoothOne0to50,
141    TransitionRampUpShortSmoothTwo0to50,
142    TransitionRampUpLongSharpOne0to50,
143    TransitionRampUpLongSharpTwo0to50,
144    TransitionRampUpMediumSharpOne0to50,
145    TransitionRampUpMediumSharpTwo0to50,
146    TransitionRampUpShortSharpOne0to50,
147    TransitionRampUpShortSharpTwo0to50,
148    LongBuzzForProgrammaticStopping100,
149    SmoothHumOne50,
150    SmoothHumTwo40,
151    SmoothHumThree30,
152    SmoothHumFour20,
153    SmoothHumFive10,
154}
155
156impl HapticPattern {
157    pub fn from_string(s: &str) -> Option<HapticPattern> {
158        match s {
159            "Click" => Some(HapticPattern::Click),
160            "StrongClick" => Some(HapticPattern::StrongClick),
161            "DoubleClick" => Some(HapticPattern::DoubleClick),
162            "StrongClick100" => Some(HapticPattern::StrongClick100),
163            "StrongClick60" => Some(HapticPattern::StrongClick60),
164            "StrongClick30" => Some(HapticPattern::StrongClick30),
165            "SharpClick100" => Some(HapticPattern::SharpClick100),
166            "SharpClick60" => Some(HapticPattern::SharpClick60),
167            "SharpClick30" => Some(HapticPattern::SharpClick30),
168            "SoftBump100" => Some(HapticPattern::SoftBump100),
169            "SoftBump60" => Some(HapticPattern::SoftBump60),
170            "SoftBump30" => Some(HapticPattern::SoftBump30),
171            "DoubleClick100" => Some(HapticPattern::DoubleClick100),
172            "DoubleClick60" => Some(HapticPattern::DoubleClick60),
173            "TripleClick100" => Some(HapticPattern::TripleClick100),
174            "SoftFuzz60" => Some(HapticPattern::SoftFuzz60),
175            "StrongBuzz100" => Some(HapticPattern::StrongBuzz100),
176            "Alert750ms" => Some(HapticPattern::Alert750ms),
177            "Alert1000ms" => Some(HapticPattern::Alert1000ms),
178            "StrongClickOne100" => Some(HapticPattern::StrongClickOne100),
179            "StrongClickTwo80" => Some(HapticPattern::StrongClickTwo80),
180            "StrongClickThree60" => Some(HapticPattern::StrongClickThree60),
181            "StrongClickFour30" => Some(HapticPattern::StrongClickFour30),
182            "MediumClickOne100" => Some(HapticPattern::MediumClickOne100),
183            "MediumClickTwo80" => Some(HapticPattern::MediumClickTwo80),
184            "MediumClickThree60" => Some(HapticPattern::MediumClickThree60),
185            "SharpTickOne100" => Some(HapticPattern::SharpTickOne100),
186            "SharpTickTwo80" => Some(HapticPattern::SharpTickTwo80),
187            "SharpTickThree60" => Some(HapticPattern::SharpTickThree60),
188            "ShortDoubleClickStrongOne100" => Some(HapticPattern::ShortDoubleClickStrongOne100),
189            "ShortDoubleClickStrongTwo80" => Some(HapticPattern::ShortDoubleClickStrongTwo80),
190            "ShortDoubleClickStrongThree60" => Some(HapticPattern::ShortDoubleClickStrongThree60),
191            "ShortDoubleClickStrongFour30" => Some(HapticPattern::ShortDoubleClickStrongFour30),
192            "ShortDoubleClickMediumOne100" => Some(HapticPattern::ShortDoubleClickMediumOne100),
193            "ShortDoubleClickMediumTwo80" => Some(HapticPattern::ShortDoubleClickMediumTwo80),
194            "ShortDoubleClickMediumThree60" => Some(HapticPattern::ShortDoubleClickMediumThree60),
195            "ShortDoubleSharpTickOne100" => Some(HapticPattern::ShortDoubleSharpTickOne100),
196            "ShortDoubleSharpTickTwo80" => Some(HapticPattern::ShortDoubleSharpTickTwo80),
197            "ShortDoubleSharpTickThree60" => Some(HapticPattern::ShortDoubleSharpTickThree60),
198            "LongDoubleSharpClickStrongOne100" => Some(HapticPattern::LongDoubleSharpClickStrongOne100),
199            "LongDoubleSharpClickStrongTwo80" => Some(HapticPattern::LongDoubleSharpClickStrongTwo80),
200            "LongDoubleSharpClickStrongThree60" => Some(HapticPattern::LongDoubleSharpClickStrongThree60),
201            "LongDoubleSharpClickStrongFour30" => Some(HapticPattern::LongDoubleSharpClickStrongFour30),
202            "LongDoubleSharpClickMediumOne100" => Some(HapticPattern::LongDoubleSharpClickMediumOne100),
203            "LongDoubleSharpClickMediumTwo80" => Some(HapticPattern::LongDoubleSharpClickMediumTwo80),
204            "LongDoubleSharpClickMediumThree60" => Some(HapticPattern::LongDoubleSharpClickMediumThree60),
205            "LongDoubleSharpTickOne100" => Some(HapticPattern::LongDoubleSharpTickOne100),
206            "LongDoubleSharpTickTwo80" => Some(HapticPattern::LongDoubleSharpTickTwo80),
207            "LongDoubleSharpTickThree60" => Some(HapticPattern::LongDoubleSharpTickThree60),
208            "BuzzOne100" => Some(HapticPattern::BuzzOne100),
209            "BuzzTwo80" => Some(HapticPattern::BuzzTwo80),
210            "BuzzThree60" => Some(HapticPattern::BuzzThree60),
211            "BuzzFour40" => Some(HapticPattern::BuzzFour40),
212            "BuzzFive20" => Some(HapticPattern::BuzzFive20),
213            "PulsingStrongOne100" => Some(HapticPattern::PulsingStrongOne100),
214            "PulsingStrongTwo60" => Some(HapticPattern::PulsingStrongTwo60),
215            "PulsingMediumOne100" => Some(HapticPattern::PulsingMediumOne100),
216            "PulsingMediumTwo60" => Some(HapticPattern::PulsingMediumTwo60),
217            "PulsingSharpOne100" => Some(HapticPattern::PulsingSharpOne100),
218            "PulsingSharpTwo60" => Some(HapticPattern::PulsingSharpTwo60),
219            "TransitionClickOne100" => Some(HapticPattern::TransitionClickOne100),
220            "TransitionClickTwo80" => Some(HapticPattern::TransitionClickTwo80),
221            "TransitionClickThree60" => Some(HapticPattern::TransitionClickThree60),
222            "TransitionClickFour40" => Some(HapticPattern::TransitionClickFour40),
223            "TransitionClickFive20" => Some(HapticPattern::TransitionClickFive20),
224            "TransitionClickSix10" => Some(HapticPattern::TransitionClickSix10),
225            "TransitionHumOne100" => Some(HapticPattern::TransitionHumOne100),
226            "TransitionHumTwo80" => Some(HapticPattern::TransitionHumTwo80),
227            "TransitionHumThree60" => Some(HapticPattern::TransitionHumThree60),
228            "TransitionHumFour40" => Some(HapticPattern::TransitionHumFour40),
229            "TransitionHumFive20" => Some(HapticPattern::TransitionHumFive20),
230            "TransitionHumSix10" => Some(HapticPattern::TransitionHumSix10),
231            "TransitionRampDownLongSmoothOne100to0" => {
232                Some(HapticPattern::TransitionRampDownLongSmoothOne100to0)
233            }
234            "TransitionRampDownLongSmoothTwo100to0" => {
235                Some(HapticPattern::TransitionRampDownLongSmoothTwo100to0)
236            }
237            "TransitionRampDownMediumSmoothOne100to0" => {
238                Some(HapticPattern::TransitionRampDownMediumSmoothOne100to0)
239            }
240            "TransitionRampDownMediumSmoothTwo100to0" => {
241                Some(HapticPattern::TransitionRampDownMediumSmoothTwo100to0)
242            }
243            "TransitionRampDownShortSmoothOne100to0" => {
244                Some(HapticPattern::TransitionRampDownShortSmoothOne100to0)
245            }
246            "TransitionRampDownShortSmoothTwo100to0" => {
247                Some(HapticPattern::TransitionRampDownShortSmoothTwo100to0)
248            }
249            "TransitionRampDownLongSharpOne100to0" => {
250                Some(HapticPattern::TransitionRampDownLongSharpOne100to0)
251            }
252            "TransitionRampDownLongSharpTwo100to0" => {
253                Some(HapticPattern::TransitionRampDownLongSharpTwo100to0)
254            }
255            "TransitionRampDownMediumSharpOne100to0" => {
256                Some(HapticPattern::TransitionRampDownMediumSharpOne100to0)
257            }
258            "TransitionRampDownMediumSharpTwo100to0" => {
259                Some(HapticPattern::TransitionRampDownMediumSharpTwo100to0)
260            }
261            "TransitionRampDownShortSharpOne100to0" => {
262                Some(HapticPattern::TransitionRampDownShortSharpOne100to0)
263            }
264            "TransitionRampDownShortSharpTwo100to0" => {
265                Some(HapticPattern::TransitionRampDownShortSharpTwo100to0)
266            }
267            "TransitionRampUpLongSmoothOne0to100" => Some(HapticPattern::TransitionRampUpLongSmoothOne0to100),
268            "TransitionRampUpLongSmoothTwo0to100" => Some(HapticPattern::TransitionRampUpLongSmoothTwo0to100),
269            "TransitionRampUpMediumSmoothOne0to100" => {
270                Some(HapticPattern::TransitionRampUpMediumSmoothOne0to100)
271            }
272            "TransitionRampUpMediumSmoothTwo0to100" => {
273                Some(HapticPattern::TransitionRampUpMediumSmoothTwo0to100)
274            }
275            "TransitionRampUpShortSmoothOne0to100" => {
276                Some(HapticPattern::TransitionRampUpShortSmoothOne0to100)
277            }
278            "TransitionRampUpShortSmoothTwo0to100" => {
279                Some(HapticPattern::TransitionRampUpShortSmoothTwo0to100)
280            }
281            "TransitionRampUpLongSharpOne0to100" => Some(HapticPattern::TransitionRampUpLongSharpOne0to100),
282            "TransitionRampUpLongSharpTwo0to100" => Some(HapticPattern::TransitionRampUpLongSharpTwo0to100),
283            "TransitionRampUpMediumSharpOne0to100" => {
284                Some(HapticPattern::TransitionRampUpMediumSharpOne0to100)
285            }
286            "TransitionRampUpMediumSharpTwo0to100" => {
287                Some(HapticPattern::TransitionRampUpMediumSharpTwo0to100)
288            }
289            "TransitionRampUpShortSharpOne0to100" => Some(HapticPattern::TransitionRampUpShortSharpOne0to100),
290            "TransitionRampUpShortSharpTwo0to100" => Some(HapticPattern::TransitionRampUpShortSharpTwo0to100),
291            "TransitionRampDownLongSmoothOne50to0" => {
292                Some(HapticPattern::TransitionRampDownLongSmoothOne50to0)
293            }
294            "TransitionRampDownLongSmoothTwo50to0" => {
295                Some(HapticPattern::TransitionRampDownLongSmoothTwo50to0)
296            }
297            "TransitionRampDownMediumSmoothOne50to0" => {
298                Some(HapticPattern::TransitionRampDownMediumSmoothOne50to0)
299            }
300            "TransitionRampDownMediumSmoothTwo50to0" => {
301                Some(HapticPattern::TransitionRampDownMediumSmoothTwo50to0)
302            }
303            "TransitionRampDownShortSmoothOne50to0" => {
304                Some(HapticPattern::TransitionRampDownShortSmoothOne50to0)
305            }
306            "TransitionRampDownShortSmoothTwo50to0" => {
307                Some(HapticPattern::TransitionRampDownShortSmoothTwo50to0)
308            }
309            "TransitionRampDownLongSharpOne50to0" => Some(HapticPattern::TransitionRampDownLongSharpOne50to0),
310            "TransitionRampDownLongSharpTwo50to0" => Some(HapticPattern::TransitionRampDownLongSharpTwo50to0),
311            "TransitionRampDownMediumSharpOne50to0" => {
312                Some(HapticPattern::TransitionRampDownMediumSharpOne50to0)
313            }
314            "TransitionRampDownMediumSharpTwo50to0" => {
315                Some(HapticPattern::TransitionRampDownMediumSharpTwo50to0)
316            }
317            "TransitionRampDownShortSharpOne50to0" => {
318                Some(HapticPattern::TransitionRampDownShortSharpOne50to0)
319            }
320            "TransitionRampDownShortSharpTwo50to0" => {
321                Some(HapticPattern::TransitionRampDownShortSharpTwo50to0)
322            }
323            "TransitionRampUpLongSmoothOne0to50" => Some(HapticPattern::TransitionRampUpLongSmoothOne0to50),
324            "TransitionRampUpLongSmoothTwo0to50" => Some(HapticPattern::TransitionRampUpLongSmoothTwo0to50),
325            "TransitionRampUpMediumSmoothOne0to50" => {
326                Some(HapticPattern::TransitionRampUpMediumSmoothOne0to50)
327            }
328            "TransitionRampUpMediumSmoothTwo0to50" => {
329                Some(HapticPattern::TransitionRampUpMediumSmoothTwo0to50)
330            }
331            "TransitionRampUpShortSmoothOne0to50" => Some(HapticPattern::TransitionRampUpShortSmoothOne0to50),
332            "TransitionRampUpShortSmoothTwo0to50" => Some(HapticPattern::TransitionRampUpShortSmoothTwo0to50),
333            "TransitionRampUpLongSharpOne0to50" => Some(HapticPattern::TransitionRampUpLongSharpOne0to50),
334            "TransitionRampUpLongSharpTwo0to50" => Some(HapticPattern::TransitionRampUpLongSharpTwo0to50),
335            "TransitionRampUpMediumSharpOne0to50" => Some(HapticPattern::TransitionRampUpMediumSharpOne0to50),
336            "TransitionRampUpMediumSharpTwo0to50" => Some(HapticPattern::TransitionRampUpMediumSharpTwo0to50),
337            "TransitionRampUpShortSharpOne0to50" => Some(HapticPattern::TransitionRampUpShortSharpOne0to50),
338            "TransitionRampUpShortSharpTwo0to50" => Some(HapticPattern::TransitionRampUpShortSharpTwo0to50),
339            "LongBuzzForProgrammaticStopping100" => Some(HapticPattern::LongBuzzForProgrammaticStopping100),
340            "SmoothHumOne50" => Some(HapticPattern::SmoothHumOne50),
341            "SmoothHumTwo40" => Some(HapticPattern::SmoothHumTwo40),
342            "SmoothHumThree30" => Some(HapticPattern::SmoothHumThree30),
343            "SmoothHumFour20" => Some(HapticPattern::SmoothHumFour20),
344            "SmoothHumFive10" => Some(HapticPattern::SmoothHumFive10),
345            _ => None, // Return None if the string doesn't match any known pattern
346        }
347    }
348}
349
350#[derive(Default)]
351pub struct HapticsApi<P: CheckedPermissions> {
352    conn: CheckedConn<P>,
353}
354
355impl<P: CheckedPermissions> HapticsApi<P> {
356    pub fn try_new_with_timeout(timeout: Duration) -> Option<Self> {
357        Some(Self { conn: CheckedConn::try_connect_with_timeout(timeout)? })
358    }
359
360    pub fn click(&self)
361    where
362        P: MessageAllowed<Vibrate>,
363    {
364        self.vibrate(HapticPattern::Click);
365    }
366
367    pub fn triple_click(&self)
368    where
369        P: MessageAllowed<Vibrate>,
370    {
371        self.vibrate(HapticPattern::TripleClick100);
372    }
373
374    pub fn strong_click(&self)
375    where
376        P: MessageAllowed<Vibrate>,
377    {
378        self.vibrate(HapticPattern::StrongClick);
379    }
380
381    pub fn double_click(&self)
382    where
383        P: MessageAllowed<Vibrate>,
384    {
385        self.vibrate(HapticPattern::DoubleClick);
386    }
387
388    pub fn vibrate(&self, haptic_pattern: HapticPattern)
389    where
390        P: MessageAllowed<Vibrate>,
391    {
392        self.conn.try_send_scalar(Vibrate(haptic_pattern)).ok();
393    }
394}
395
396impl AsScalar<1> for HapticPattern {
397    fn as_scalar(&self) -> [u32; 1] { [self.to_u32().unwrap()] }
398}
399
400impl FromScalar<1> for HapticPattern {
401    fn from_scalar([value]: [u32; 1]) -> Self { Self::from_u32(value).unwrap_or(HapticPattern::Click) }
402}