1use serde::Deserialize;
14use std::borrow::Cow;
15use web_time::{SystemTime, UNIX_EPOCH};
16
17use crate::{
18 IdeviceError, ReadWrite, RemoteXpcClient, RsdService, obf,
19 services::core_device::CoreDeviceError,
20 xpc::{Dictionary, XPCObject},
21};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ButtonState {
25 Down,
26 Up,
27}
28
29impl ButtonState {
30 pub fn raw(self) -> u64 {
31 match self {
32 ButtonState::Down => 1,
33 ButtonState::Up => 2,
34 }
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum DigitizerEventType {
40 Start,
41 Position,
42 End,
43}
44
45impl DigitizerEventType {
46 pub fn raw(self) -> u64 {
47 match self {
48 DigitizerEventType::Start => 0,
49 DigitizerEventType::Position => 1,
50 DigitizerEventType::End => 2,
51 }
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum DigitizerEdge {
57 None,
58 Top,
59 Left,
60 Bottom,
61 Right,
62}
63
64impl DigitizerEdge {
65 pub fn raw(self) -> u64 {
66 match self {
67 DigitizerEdge::None => 0,
68 DigitizerEdge::Top => 1,
69 DigitizerEdge::Left => 2,
70 DigitizerEdge::Bottom => 3,
71 DigitizerEdge::Right => 4,
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum DigitizerTarget {
78 MainScreen,
79 Display(u64),
80}
81
82impl DigitizerTarget {
83 pub fn raw(self) -> u64 {
84 match self {
85 DigitizerTarget::MainScreen => 0,
86 DigitizerTarget::Display(n) => n,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum ScrollTarget {
93 DigitalCrown,
94 Dial,
95}
96
97impl ScrollTarget {
98 pub fn raw(self) -> u64 {
99 match self {
100 ScrollTarget::DigitalCrown => 0,
101 ScrollTarget::Dial => 1,
102 }
103 }
104}
105
106pub mod scroll_phase {
107 pub const UNDEFINED: u64 = 0x0;
108 pub const BEGAN: u64 = 0x1;
109 pub const CHANGED: u64 = 0x2;
110 pub const ENDED: u64 = 0x4;
111 pub const CANCELLED: u64 = 0x8;
112 pub const MAY_BEGIN: u64 = 0x80;
113}
114
115pub mod scroll_momentum {
116 pub const UNDEFINED: u64 = 0x0;
117 pub const CONTINUE: u64 = 0x1;
118 pub const START: u64 = 0x2;
119 pub const END: u64 = 0x4;
120 pub const WILL_BEGIN: u64 = 0x8;
121 pub const INTERRUPTED: u64 = 0x10;
122}
123
124pub const DIGITIZER_REPORT_ID: u8 = 0x13;
125pub const TOUCHSCREEN_REPORT_ID: u8 = 0x09;
126pub const TOUCHSCREEN_STATE_CONTACT: u8 = 0xC2;
127pub const TOUCHSCREEN_STATE_RELEASE: u8 = 0x02;
128
129pub const DIGITIZER_SURFACE_MAIN_TOUCHSCREEN: u64 = 257;
130pub const DIGITIZER_SURFACE_TOUCHSCREEN_GESTURE: u64 = 1281;
131
132fn default_timestamp() -> u64 {
136 let nanos = SystemTime::now()
137 .duration_since(UNIX_EPOCH)
138 .map(|d| d.as_nanos() as u64)
139 .unwrap_or(0);
140 nanos & ((1u64 << 48) - 1)
141}
142
143pub fn build_digitizer_report(x: i32, y: i32, timestamp: Option<u64>) -> Vec<u8> {
150 let ts = timestamp.unwrap_or_else(default_timestamp) & ((1u64 << 48) - 1);
151 let mut r = Vec::with_capacity(19);
152 r.push(DIGITIZER_REPORT_ID);
153 r.extend_from_slice(&x.to_le_bytes());
154 r.extend_from_slice(&y.to_le_bytes());
155 r.extend_from_slice(&[0, 0]);
156 r.extend_from_slice(&ts.to_le_bytes()[..6]);
157 r.extend_from_slice(&[0, 0]);
158 r
159}
160
161pub fn build_touchscreen_report(state: u8, x: u16, y: u16, timestamp: Option<u64>) -> Vec<u8> {
169 let ts = timestamp.unwrap_or_else(default_timestamp) & ((1u64 << 48) - 1);
170 let mut r = Vec::with_capacity(58);
171 r.extend_from_slice(&[TOUCHSCREEN_REPORT_ID, 0x01, 0x05, state]);
172 r.extend_from_slice(&x.to_le_bytes());
173 r.extend_from_slice(&y.to_le_bytes());
174 r.extend_from_slice(&[0u8; 32]);
175 r.extend_from_slice(&[0x02, 0x00, 0x00, 0x00]);
176 r.extend_from_slice(&ts.to_le_bytes()[..6]);
177 r.extend_from_slice(&[0u8; 8]);
178 r
179}
180
181#[derive(Debug)]
185pub struct IndigoHidClient<R: ReadWrite> {
186 inner: RemoteXpcClient<R>,
187}
188
189impl RsdService for IndigoHidClient<Box<dyn ReadWrite>> {
190 fn rsd_service_name() -> Cow<'static, str> {
191 obf!("com.apple.coredevice.hid.indigo")
192 }
193
194 async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
195 let mut inner = RemoteXpcClient::new(stream).await?;
196 inner.do_handshake().await?;
197 Ok(Self { inner })
198 }
199}
200
201impl<R: ReadWrite> IndigoHidClient<R> {
202 pub fn new(inner: RemoteXpcClient<R>) -> Self {
203 Self { inner }
204 }
205
206 async fn send_event(
210 &mut self,
211 message_type: &str,
212 feature_identifier: Cow<'static, str>,
213 payload: Dictionary,
214 ) -> Result<(), IdeviceError> {
215 let mut msg = Dictionary::new();
216 msg.insert(
217 "messageType".into(),
218 XPCObject::String(message_type.to_string()),
219 );
220 msg.insert("payload".into(), XPCObject::Dictionary(payload));
221 msg.insert(
222 "featureIdentifier".into(),
223 XPCObject::String(feature_identifier.into()),
224 );
225 self.inner.send_object(msg, false).await
226 }
227
228 pub async fn send_button(
235 &mut self,
236 usage_page: u64,
237 usage_code: u64,
238 state: ButtonState,
239 ) -> Result<(), IdeviceError> {
240 let mut payload = Dictionary::new();
241 payload.insert("state".into(), XPCObject::UInt64(state.raw()));
242 payload.insert("usagePage".into(), XPCObject::UInt64(usage_page));
243 payload.insert("usageCode".into(), XPCObject::UInt64(usage_code));
244 self.send_event(
245 "IndigoButtonEvent",
246 obf!("com.apple.coredevice.feature.remote.hid.button"),
247 payload,
248 )
249 .await
250 }
251
252 pub async fn send_keyboard(
264 &mut self,
265 usage_code: u64,
266 state: ButtonState,
267 ) -> Result<(), IdeviceError> {
268 let mut payload = Dictionary::new();
269 payload.insert("usageCode".into(), XPCObject::UInt64(usage_code));
270 payload.insert("state".into(), XPCObject::UInt64(state.raw()));
271 self.send_event(
272 "IndigoKeyboardButtonEvent",
273 obf!("com.apple.coredevice.feature.remote.hid.keyboard"),
274 payload,
275 )
276 .await
277 }
278
279 pub async fn send_digitizer(
287 &mut self,
288 point_one: (f64, f64),
289 point_two: Option<(f64, f64)>,
290 event_type: DigitizerEventType,
291 edge: DigitizerEdge,
292 target: DigitizerTarget,
293 ) -> Result<(), IdeviceError> {
294 fn point(x: f64, y: f64) -> XPCObject {
295 let mut p = Dictionary::new();
296 p.insert("x".into(), XPCObject::Double(x));
297 p.insert("y".into(), XPCObject::Double(y));
298 XPCObject::Dictionary(p)
299 }
300
301 let mut payload = Dictionary::new();
302 payload.insert("pointOne".into(), point(point_one.0, point_one.1));
303 if let Some((x, y)) = point_two {
306 payload.insert("pointTwo".into(), point(x, y));
307 }
308 payload.insert("eventType".into(), XPCObject::UInt64(event_type.raw()));
309 payload.insert("edge".into(), XPCObject::UInt64(edge.raw()));
310 payload.insert("target".into(), XPCObject::UInt64(target.raw()));
311 self.send_event(
312 "IndigoDigitizerEvent",
313 obf!("com.apple.coredevice.feature.remote.hid.digitizer"),
314 payload,
315 )
316 .await
317 }
318
319 pub async fn send_scroll(
326 &mut self,
327 point: (f64, f64, f64),
328 phase: u64,
329 momentum: u64,
330 target: ScrollTarget,
331 ) -> Result<(), IdeviceError> {
332 let mut p = Dictionary::new();
333 p.insert("x".into(), XPCObject::Double(point.0));
334 p.insert("y".into(), XPCObject::Double(point.1));
335 p.insert("z".into(), XPCObject::Double(point.2));
336
337 let mut payload = Dictionary::new();
338 payload.insert("point".into(), XPCObject::Dictionary(p));
339 payload.insert("phase".into(), XPCObject::UInt64(phase));
340 payload.insert("momentum".into(), XPCObject::UInt64(momentum));
341 payload.insert("target".into(), XPCObject::UInt64(target.raw()));
342 self.send_event(
343 "IndigoScrollEvent",
344 obf!("com.apple.coredevice.feature.remote.hid.scroll"),
345 payload,
346 )
347 .await
348 }
349
350 pub async fn send_vendor_defined(
357 &mut self,
358 usage_page: u64,
359 usage: u64,
360 version: u64,
361 data: Vec<u8>,
362 ) -> Result<(), IdeviceError> {
363 let mut payload = Dictionary::new();
364 payload.insert("usagePage".into(), XPCObject::UInt64(usage_page));
365 payload.insert("usage".into(), XPCObject::UInt64(usage));
366 payload.insert("version".into(), XPCObject::UInt64(version));
367 payload.insert("data".into(), XPCObject::Data(data));
368 self.send_event(
369 "IndigoVendorDefinedEvent",
370 obf!("com.apple.coredevice.feature.remote.hid.vendordefined"),
371 payload,
372 )
373 .await
374 }
375}
376
377#[derive(Debug, Clone, Deserialize)]
382pub struct HidSurface {
383 #[serde(rename = "_ServiceID")]
386 pub service_id: u64,
387 #[serde(rename = "Product")]
389 pub product: Option<String>,
390 #[serde(rename = "PrimaryUsage")]
392 pub primary_usage: Option<u64>,
393 #[serde(rename = "PrimaryUsagePage")]
395 pub primary_usage_page: Option<u64>,
396}
397
398#[derive(Debug)]
400pub struct UniversalHidServiceClient<R: ReadWrite> {
401 inner: RemoteXpcClient<R>,
402}
403
404impl RsdService for UniversalHidServiceClient<Box<dyn ReadWrite>> {
405 fn rsd_service_name() -> Cow<'static, str> {
406 obf!("com.apple.coredevice.hid.universalhidservice")
407 }
408
409 async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
410 let mut inner = RemoteXpcClient::new(stream).await?;
411 inner.do_handshake().await?;
412 Ok(Self { inner })
413 }
414}
415
416impl<R: ReadWrite> UniversalHidServiceClient<R> {
417 pub fn new(inner: RemoteXpcClient<R>) -> Self {
418 Self { inner }
419 }
420
421 fn request(payload: Dictionary) -> Dictionary {
424 let mut msg = Dictionary::new();
425 let universal_hid_feature: Cow<'static, str> =
426 obf!("com.apple.coredevice.feature.remote.universalhidservice");
427 msg.insert(
428 "featureIdentifier".into(),
429 XPCObject::String(universal_hid_feature.into()),
430 );
431 msg.insert("messageType".into(), XPCObject::String("Request".into()));
432 msg.insert("payload".into(), XPCObject::Dictionary(payload));
433 msg
434 }
435
436 pub async fn list_connected_services(&mut self) -> Result<Vec<HidSurface>, IdeviceError> {
438 let mut payload = Dictionary::new();
439 payload.insert(
440 "connectedServices".into(),
441 XPCObject::Dictionary(Dictionary::new()),
442 );
443 let msg = Self::request(payload);
444 self.inner.send_object(msg, true).await?;
445 let res = self.inner.recv().await?;
446
447 let services = res
448 .as_dictionary()
449 .and_then(|d| d.get("connectedServices"))
450 .ok_or(CoreDeviceError::MissingField("connectedServices"))?;
451 plist::from_value(services)
452 .map_err(|_| CoreDeviceError::MalformedField("connectedServices").into())
453 }
454
455 pub async fn send_report(
457 &mut self,
458 service_id: u64,
459 report: Vec<u8>,
460 ) -> Result<(), IdeviceError> {
461 let payload = crate::xpc!({
463 "send": {
464 "_0": report,
465 "_1": service_id
466 }
467 })
468 .to_dictionary()
469 .unwrap();
470
471 let msg = Self::request(payload);
472 self.inner.send_object(msg, false).await
473 }
474
475 pub async fn send_digitizer(
479 &mut self,
480 x: i32,
481 y: i32,
482 service_id: u64,
483 timestamp: Option<u64>,
484 ) -> Result<(), IdeviceError> {
485 self.send_report(service_id, build_digitizer_report(x, y, timestamp))
486 .await
487 }
488
489 pub async fn send_touchscreen(
493 &mut self,
494 state: u8,
495 x: u16,
496 y: u16,
497 timestamp: Option<u64>,
498 ) -> Result<(), IdeviceError> {
499 self.send_report(
500 DIGITIZER_SURFACE_MAIN_TOUCHSCREEN,
501 build_touchscreen_report(state, x, y, timestamp),
502 )
503 .await
504 }
505
506 pub async fn tap(&mut self, x: u16, y: u16) -> Result<(), IdeviceError> {
509 self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x, y, None)
510 .await?;
511 crate::time::sleep(std::time::Duration::from_millis(50)).await;
512 self.send_touchscreen(TOUCHSCREEN_STATE_RELEASE, x, y, None)
513 .await
514 }
515
516 pub async fn drag(
523 &mut self,
524 x1: u16,
525 y1: u16,
526 x2: u16,
527 y2: u16,
528 steps: u32,
529 delay_ms: u64,
530 ) -> Result<(), IdeviceError> {
531 let steps = steps.max(1);
532 for i in 0..steps {
533 let t = i as f64 / steps as f64;
534 let x = (x1 as f64 + (x2 as f64 - x1 as f64) * t).round() as u16;
535 let y = (y1 as f64 + (y2 as f64 - y1 as f64) * t).round() as u16;
536 self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x, y, None)
537 .await?;
538 if delay_ms > 0 {
539 crate::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
540 }
541 }
542 self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x2, y2, None)
543 .await?;
544 self.send_touchscreen(TOUCHSCREEN_STATE_RELEASE, x2, y2, None)
545 .await
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn digitizer_report_layout() {
555 let r = build_digitizer_report(100, -50, Some(0x0102030405));
556 assert_eq!(r.len(), 19);
557 assert_eq!(r[0], DIGITIZER_REPORT_ID);
558 assert_eq!(&r[1..5], &100i32.to_le_bytes());
559 assert_eq!(&r[5..9], &(-50i32).to_le_bytes());
560 assert_eq!(&r[9..11], &[0, 0]);
561 assert_eq!(&r[11..17], &0x0102030405u64.to_le_bytes()[..6]);
562 assert_eq!(&r[17..19], &[0, 0]);
563 }
564
565 #[test]
566 fn touchscreen_report_layout() {
567 let r = build_touchscreen_report(TOUCHSCREEN_STATE_CONTACT, 375, 812, Some(0xAABBCCDD));
568 assert_eq!(r.len(), 58);
569 assert_eq!(&r[0..4], &[0x09, 0x01, 0x05, 0xC2]);
570 assert_eq!(&r[4..6], &375u16.to_le_bytes());
571 assert_eq!(&r[6..8], &812u16.to_le_bytes());
572 assert_eq!(&r[8..40], &[0u8; 32]);
573 assert_eq!(&r[40..44], &[0x02, 0x00, 0x00, 0x00]);
574 assert_eq!(&r[44..50], &0xAABBCCDDu64.to_le_bytes()[..6]);
575 assert_eq!(&r[50..58], &[0u8; 8]);
576 }
577
578 #[test]
579 fn timestamp_is_truncated_to_48_bits() {
580 let r = build_digitizer_report(0, 0, Some(u64::MAX));
582 assert_eq!(&r[11..17], &[0xFF; 6]);
583 }
584}