use serde::Deserialize;
use std::borrow::Cow;
use web_time::{SystemTime, UNIX_EPOCH};
use crate::{
IdeviceError, ReadWrite, RemoteXpcClient, RsdService, obf,
services::core_device::CoreDeviceError,
xpc::{Dictionary, XPCObject},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ButtonState {
Down,
Up,
}
impl ButtonState {
pub fn raw(self) -> u64 {
match self {
ButtonState::Down => 1,
ButtonState::Up => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DigitizerEventType {
Start,
Position,
End,
}
impl DigitizerEventType {
pub fn raw(self) -> u64 {
match self {
DigitizerEventType::Start => 0,
DigitizerEventType::Position => 1,
DigitizerEventType::End => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DigitizerEdge {
None,
Top,
Left,
Bottom,
Right,
}
impl DigitizerEdge {
pub fn raw(self) -> u64 {
match self {
DigitizerEdge::None => 0,
DigitizerEdge::Top => 1,
DigitizerEdge::Left => 2,
DigitizerEdge::Bottom => 3,
DigitizerEdge::Right => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DigitizerTarget {
MainScreen,
Display(u64),
}
impl DigitizerTarget {
pub fn raw(self) -> u64 {
match self {
DigitizerTarget::MainScreen => 0,
DigitizerTarget::Display(n) => n,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollTarget {
DigitalCrown,
Dial,
}
impl ScrollTarget {
pub fn raw(self) -> u64 {
match self {
ScrollTarget::DigitalCrown => 0,
ScrollTarget::Dial => 1,
}
}
}
pub mod scroll_phase {
pub const UNDEFINED: u64 = 0x0;
pub const BEGAN: u64 = 0x1;
pub const CHANGED: u64 = 0x2;
pub const ENDED: u64 = 0x4;
pub const CANCELLED: u64 = 0x8;
pub const MAY_BEGIN: u64 = 0x80;
}
pub mod scroll_momentum {
pub const UNDEFINED: u64 = 0x0;
pub const CONTINUE: u64 = 0x1;
pub const START: u64 = 0x2;
pub const END: u64 = 0x4;
pub const WILL_BEGIN: u64 = 0x8;
pub const INTERRUPTED: u64 = 0x10;
}
pub const DIGITIZER_REPORT_ID: u8 = 0x13;
pub const TOUCHSCREEN_REPORT_ID: u8 = 0x09;
pub const TOUCHSCREEN_STATE_CONTACT: u8 = 0xC2;
pub const TOUCHSCREEN_STATE_RELEASE: u8 = 0x02;
pub const DIGITIZER_SURFACE_MAIN_TOUCHSCREEN: u64 = 257;
pub const DIGITIZER_SURFACE_TOUCHSCREEN_GESTURE: u64 = 1281;
fn default_timestamp() -> u64 {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
nanos & ((1u64 << 48) - 1)
}
pub fn build_digitizer_report(x: i32, y: i32, timestamp: Option<u64>) -> Vec<u8> {
let ts = timestamp.unwrap_or_else(default_timestamp) & ((1u64 << 48) - 1);
let mut r = Vec::with_capacity(19);
r.push(DIGITIZER_REPORT_ID);
r.extend_from_slice(&x.to_le_bytes());
r.extend_from_slice(&y.to_le_bytes());
r.extend_from_slice(&[0, 0]);
r.extend_from_slice(&ts.to_le_bytes()[..6]);
r.extend_from_slice(&[0, 0]);
r
}
pub fn build_touchscreen_report(state: u8, x: u16, y: u16, timestamp: Option<u64>) -> Vec<u8> {
let ts = timestamp.unwrap_or_else(default_timestamp) & ((1u64 << 48) - 1);
let mut r = Vec::with_capacity(58);
r.extend_from_slice(&[TOUCHSCREEN_REPORT_ID, 0x01, 0x05, state]);
r.extend_from_slice(&x.to_le_bytes());
r.extend_from_slice(&y.to_le_bytes());
r.extend_from_slice(&[0u8; 32]);
r.extend_from_slice(&[0x02, 0x00, 0x00, 0x00]);
r.extend_from_slice(&ts.to_le_bytes()[..6]);
r.extend_from_slice(&[0u8; 8]);
r
}
#[derive(Debug)]
pub struct IndigoHidClient<R: ReadWrite> {
inner: RemoteXpcClient<R>,
}
impl RsdService for IndigoHidClient<Box<dyn ReadWrite>> {
fn rsd_service_name() -> Cow<'static, str> {
obf!("com.apple.coredevice.hid.indigo")
}
async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
let mut inner = RemoteXpcClient::new(stream).await?;
inner.do_handshake().await?;
Ok(Self { inner })
}
}
impl<R: ReadWrite> IndigoHidClient<R> {
pub fn new(inner: RemoteXpcClient<R>) -> Self {
Self { inner }
}
async fn send_event(
&mut self,
message_type: &str,
feature_identifier: Cow<'static, str>,
payload: Dictionary,
) -> Result<(), IdeviceError> {
let mut msg = Dictionary::new();
msg.insert(
"messageType".into(),
XPCObject::String(message_type.to_string()),
);
msg.insert("payload".into(), XPCObject::Dictionary(payload));
msg.insert(
"featureIdentifier".into(),
XPCObject::String(feature_identifier.into()),
);
self.inner.send_object(msg, false).await
}
pub async fn send_button(
&mut self,
usage_page: u64,
usage_code: u64,
state: ButtonState,
) -> Result<(), IdeviceError> {
let mut payload = Dictionary::new();
payload.insert("state".into(), XPCObject::UInt64(state.raw()));
payload.insert("usagePage".into(), XPCObject::UInt64(usage_page));
payload.insert("usageCode".into(), XPCObject::UInt64(usage_code));
self.send_event(
"IndigoButtonEvent",
obf!("com.apple.coredevice.feature.remote.hid.button"),
payload,
)
.await
}
pub async fn send_keyboard(
&mut self,
usage_code: u64,
state: ButtonState,
) -> Result<(), IdeviceError> {
let mut payload = Dictionary::new();
payload.insert("usageCode".into(), XPCObject::UInt64(usage_code));
payload.insert("state".into(), XPCObject::UInt64(state.raw()));
self.send_event(
"IndigoKeyboardButtonEvent",
obf!("com.apple.coredevice.feature.remote.hid.keyboard"),
payload,
)
.await
}
pub async fn send_digitizer(
&mut self,
point_one: (f64, f64),
point_two: Option<(f64, f64)>,
event_type: DigitizerEventType,
edge: DigitizerEdge,
target: DigitizerTarget,
) -> Result<(), IdeviceError> {
fn point(x: f64, y: f64) -> XPCObject {
let mut p = Dictionary::new();
p.insert("x".into(), XPCObject::Double(x));
p.insert("y".into(), XPCObject::Double(y));
XPCObject::Dictionary(p)
}
let mut payload = Dictionary::new();
payload.insert("pointOne".into(), point(point_one.0, point_one.1));
if let Some((x, y)) = point_two {
payload.insert("pointTwo".into(), point(x, y));
}
payload.insert("eventType".into(), XPCObject::UInt64(event_type.raw()));
payload.insert("edge".into(), XPCObject::UInt64(edge.raw()));
payload.insert("target".into(), XPCObject::UInt64(target.raw()));
self.send_event(
"IndigoDigitizerEvent",
obf!("com.apple.coredevice.feature.remote.hid.digitizer"),
payload,
)
.await
}
pub async fn send_scroll(
&mut self,
point: (f64, f64, f64),
phase: u64,
momentum: u64,
target: ScrollTarget,
) -> Result<(), IdeviceError> {
let mut p = Dictionary::new();
p.insert("x".into(), XPCObject::Double(point.0));
p.insert("y".into(), XPCObject::Double(point.1));
p.insert("z".into(), XPCObject::Double(point.2));
let mut payload = Dictionary::new();
payload.insert("point".into(), XPCObject::Dictionary(p));
payload.insert("phase".into(), XPCObject::UInt64(phase));
payload.insert("momentum".into(), XPCObject::UInt64(momentum));
payload.insert("target".into(), XPCObject::UInt64(target.raw()));
self.send_event(
"IndigoScrollEvent",
obf!("com.apple.coredevice.feature.remote.hid.scroll"),
payload,
)
.await
}
pub async fn send_vendor_defined(
&mut self,
usage_page: u64,
usage: u64,
version: u64,
data: Vec<u8>,
) -> Result<(), IdeviceError> {
let mut payload = Dictionary::new();
payload.insert("usagePage".into(), XPCObject::UInt64(usage_page));
payload.insert("usage".into(), XPCObject::UInt64(usage));
payload.insert("version".into(), XPCObject::UInt64(version));
payload.insert("data".into(), XPCObject::Data(data));
self.send_event(
"IndigoVendorDefinedEvent",
obf!("com.apple.coredevice.feature.remote.hid.vendordefined"),
payload,
)
.await
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct HidSurface {
#[serde(rename = "_ServiceID")]
pub service_id: u64,
#[serde(rename = "Product")]
pub product: Option<String>,
#[serde(rename = "PrimaryUsage")]
pub primary_usage: Option<u64>,
#[serde(rename = "PrimaryUsagePage")]
pub primary_usage_page: Option<u64>,
}
#[derive(Debug)]
pub struct UniversalHidServiceClient<R: ReadWrite> {
inner: RemoteXpcClient<R>,
}
impl RsdService for UniversalHidServiceClient<Box<dyn ReadWrite>> {
fn rsd_service_name() -> Cow<'static, str> {
obf!("com.apple.coredevice.hid.universalhidservice")
}
async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
let mut inner = RemoteXpcClient::new(stream).await?;
inner.do_handshake().await?;
Ok(Self { inner })
}
}
impl<R: ReadWrite> UniversalHidServiceClient<R> {
pub fn new(inner: RemoteXpcClient<R>) -> Self {
Self { inner }
}
fn request(payload: Dictionary) -> Dictionary {
let mut msg = Dictionary::new();
let universal_hid_feature: Cow<'static, str> =
obf!("com.apple.coredevice.feature.remote.universalhidservice");
msg.insert(
"featureIdentifier".into(),
XPCObject::String(universal_hid_feature.into()),
);
msg.insert("messageType".into(), XPCObject::String("Request".into()));
msg.insert("payload".into(), XPCObject::Dictionary(payload));
msg
}
pub async fn list_connected_services(&mut self) -> Result<Vec<HidSurface>, IdeviceError> {
let mut payload = Dictionary::new();
payload.insert(
"connectedServices".into(),
XPCObject::Dictionary(Dictionary::new()),
);
let msg = Self::request(payload);
self.inner.send_object(msg, true).await?;
let res = self.inner.recv().await?;
let services = res
.as_dictionary()
.and_then(|d| d.get("connectedServices"))
.ok_or(CoreDeviceError::MissingField("connectedServices"))?;
plist::from_value(services)
.map_err(|_| CoreDeviceError::MalformedField("connectedServices").into())
}
pub async fn send_report(
&mut self,
service_id: u64,
report: Vec<u8>,
) -> Result<(), IdeviceError> {
let payload = crate::xpc!({
"send": {
"_0": report,
"_1": service_id
}
})
.to_dictionary()
.unwrap();
let msg = Self::request(payload);
self.inner.send_object(msg, false).await
}
pub async fn send_digitizer(
&mut self,
x: i32,
y: i32,
service_id: u64,
timestamp: Option<u64>,
) -> Result<(), IdeviceError> {
self.send_report(service_id, build_digitizer_report(x, y, timestamp))
.await
}
pub async fn send_touchscreen(
&mut self,
state: u8,
x: u16,
y: u16,
timestamp: Option<u64>,
) -> Result<(), IdeviceError> {
self.send_report(
DIGITIZER_SURFACE_MAIN_TOUCHSCREEN,
build_touchscreen_report(state, x, y, timestamp),
)
.await
}
pub async fn tap(&mut self, x: u16, y: u16) -> Result<(), IdeviceError> {
self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x, y, None)
.await?;
crate::time::sleep(std::time::Duration::from_millis(50)).await;
self.send_touchscreen(TOUCHSCREEN_STATE_RELEASE, x, y, None)
.await
}
pub async fn drag(
&mut self,
x1: u16,
y1: u16,
x2: u16,
y2: u16,
steps: u32,
delay_ms: u64,
) -> Result<(), IdeviceError> {
let steps = steps.max(1);
for i in 0..steps {
let t = i as f64 / steps as f64;
let x = (x1 as f64 + (x2 as f64 - x1 as f64) * t).round() as u16;
let y = (y1 as f64 + (y2 as f64 - y1 as f64) * t).round() as u16;
self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x, y, None)
.await?;
if delay_ms > 0 {
crate::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
}
}
self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x2, y2, None)
.await?;
self.send_touchscreen(TOUCHSCREEN_STATE_RELEASE, x2, y2, None)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn digitizer_report_layout() {
let r = build_digitizer_report(100, -50, Some(0x0102030405));
assert_eq!(r.len(), 19);
assert_eq!(r[0], DIGITIZER_REPORT_ID);
assert_eq!(&r[1..5], &100i32.to_le_bytes());
assert_eq!(&r[5..9], &(-50i32).to_le_bytes());
assert_eq!(&r[9..11], &[0, 0]);
assert_eq!(&r[11..17], &0x0102030405u64.to_le_bytes()[..6]);
assert_eq!(&r[17..19], &[0, 0]);
}
#[test]
fn touchscreen_report_layout() {
let r = build_touchscreen_report(TOUCHSCREEN_STATE_CONTACT, 375, 812, Some(0xAABBCCDD));
assert_eq!(r.len(), 58);
assert_eq!(&r[0..4], &[0x09, 0x01, 0x05, 0xC2]);
assert_eq!(&r[4..6], &375u16.to_le_bytes());
assert_eq!(&r[6..8], &812u16.to_le_bytes());
assert_eq!(&r[8..40], &[0u8; 32]);
assert_eq!(&r[40..44], &[0x02, 0x00, 0x00, 0x00]);
assert_eq!(&r[44..50], &0xAABBCCDDu64.to_le_bytes()[..6]);
assert_eq!(&r[50..58], &[0u8; 8]);
}
#[test]
fn timestamp_is_truncated_to_48_bits() {
let r = build_digitizer_report(0, 0, Some(u64::MAX));
assert_eq!(&r[11..17], &[0xFF; 6]);
}
}