Skip to main content

ios_core/services/instruments/
graphics.rs

1use tokio::io::{AsyncRead, AsyncWrite};
2
3use crate::services::dtx::codec::{DtxConnection, DtxError};
4use crate::services::dtx::primitive_enc::archived_object;
5use crate::services::dtx::types::{DtxMessage, DtxPayload};
6
7#[derive(Debug, Clone, PartialEq, serde::Serialize)]
8pub struct GraphicsSample {
9    pub payload: serde_json::Value,
10}
11
12pub struct GraphicsMonitorClient<S> {
13    conn: DtxConnection<S>,
14    channel_code: i32,
15}
16
17impl<S: AsyncRead + AsyncWrite + Unpin + Send> GraphicsMonitorClient<S> {
18    pub async fn connect(stream: S) -> Result<Self, DtxError> {
19        let mut conn = DtxConnection::new(stream);
20        let channel_code = conn.request_channel(super::GRAPHICS_MONITOR_SVC).await?;
21        conn.method_call(
22            channel_code,
23            "startSamplingAtTimeInterval:",
24            &[archived_object(
25                crate::proto::nskeyedarchiver_encode::archive_float(0.0),
26            )],
27        )
28        .await?;
29        Ok(Self { conn, channel_code })
30    }
31
32    pub async fn next_sample(&mut self) -> Result<GraphicsSample, DtxError> {
33        loop {
34            let msg = self.conn.recv().await?;
35            if msg.expects_reply {
36                self.conn.send_ack(&msg).await?;
37            }
38            if let Some(sample) = parse_graphics_message(&msg)? {
39                return Ok(sample);
40            }
41        }
42    }
43
44    pub async fn stop(&mut self) -> Result<(), DtxError> {
45        self.conn
46            .method_call_async(self.channel_code, "stopSampling", &[])
47            .await
48    }
49}
50
51fn parse_graphics_message(msg: &DtxMessage) -> Result<Option<GraphicsSample>, DtxError> {
52    let payload = match &msg.payload {
53        DtxPayload::Response(value) => value.clone(),
54        DtxPayload::Raw(bytes) => match super::unarchive_raw_payload(bytes) {
55            Some(value) => value,
56            None => return Ok(None),
57        },
58        DtxPayload::MethodInvocation { args, .. } if !args.is_empty() => {
59            let payload = if args.len() == 1 {
60                super::nsobject_to_json(&args[0])
61            } else {
62                serde_json::Value::Array(args.iter().map(super::nsobject_to_json).collect())
63            };
64            if is_capabilities_payload(&payload) {
65                return Ok(None);
66            }
67            return Ok(Some(GraphicsSample { payload }));
68        }
69        _ => return Ok(None),
70    };
71
72    let payload = super::nsobject_to_json(&payload);
73    if is_capabilities_payload(&payload) {
74        return Ok(None);
75    }
76
77    Ok(Some(GraphicsSample { payload }))
78}
79
80fn is_capabilities_payload(payload: &serde_json::Value) -> bool {
81    if let Some(object) = payload.as_object() {
82        return object.keys().any(|key| key.starts_with("com.apple."));
83    }
84    let Some(items) = payload.as_array() else {
85        return false;
86    };
87    let Some(first) = items.first().and_then(serde_json::Value::as_object) else {
88        return false;
89    };
90    first.keys().any(|key| key.starts_with("com.apple."))
91}
92
93#[cfg(test)]
94mod tests {
95    use indexmap::IndexMap;
96
97    use super::*;
98    use crate::services::dtx::types::NSObject;
99
100    #[test]
101    fn parses_graphics_response_payload() {
102        let msg = DtxMessage {
103            identifier: 1,
104            conversation_idx: 0,
105            channel_code: 9,
106            expects_reply: false,
107            payload: DtxPayload::Response(NSObject::Dict(IndexMap::from_iter([
108                ("fps".to_string(), NSObject::Double(59.8)),
109                ("renderer".to_string(), NSObject::String("AGX".into())),
110            ]))),
111        };
112
113        let sample = parse_graphics_message(&msg).unwrap().unwrap();
114        assert_eq!(sample.payload["fps"], serde_json::json!(59.8));
115        assert_eq!(sample.payload["renderer"], serde_json::json!("AGX"));
116    }
117}