xpc-sys

Object lifecycle

XPCObject wraps xpc_object_t in an Arc. Drop will invoke xpc_release() on objects being dropped with no other strong refs.

NOTE: When using Objective-C blocks with the block crate (e.g. looping over an array), make sure to invoke xpc_retain() on any object you wish to keep after the closure is dropped, or else the XPC objects in the closure will be dropped as well! See the XPCDictionary implementation for more details. xpc-sys handles this for you for its conversions.

XPCDictionary and QueryBuilder

While we can go from HashMap<&str, XPCObject> to XPCObject, it can be a little verbose. A QueryBuilder trait exposes some builder methods to make building an XPC dictionary a little easier (without all of the into()s, and some additional error checking).

To write the query for launchctl list:

```rust let LIST_SERVICES: XPCDictionary = XPCDictionary::new() // "list com.apple.Spotlight" (if specified) // .entry("name", "com.apple.Spotlight"); .entry("subsystem", 3 as u64) .entry("handle", 0 as u64) .entry("routine", 815 as u64) .entry("legacy", true);

let reply: Result<XPCDictionary, XPCError> = XPCDictionary::new()
    // LIST_SERVICES is a proto 
    .extend(&LIST_SERVICES)
    // Specify the domain type, or fall back on requester domain
    .with_domain_type_or_default(Some(domain_type))
    .entry_if_present("name", name)
    .pipe_routine_with_error_handling();

```

In addition to checking errno is 0, pipe_routine_with_error_handling also looks for possible error and errors keys in the response dictionary and provides an Err() with xpc_strerror contents.

Rust to XPC

Conversions to/from Rust/XPC objects uses the xpc.h functions documented on Apple Developer using the From trait.

| Rust | XPC | |----------------------------------------|----------------------------| | i64 | xpctypeint64 | | u64 | _xpctypeuint64 | | f64 | _xpctypedouble | | bool | _xpcbooltrue/false | | Into | _xpctypestring | | HashMap, Into> | _xpctypedictionary | | Vec> | _xpctypearray | | std::os::unix::prelude::RawFd | _xpctypefd | | (MachPortType::Send, machportt) | _xpctypemachsend | | (MachPortType::Recv, machportt) | xpctypemachrecv |

Make XPC objects for anything with From<T>. Make sure to use the correct type for file descriptors and Mach ports: ```rust let mut message: HashMap<&str, XPCObject> = HashMap::new();

message.insert( "domain-port", XPCObject::from((MachPortType::Send, getbootstrapport() as machportt)), ); ```

Go from an XPC object to value via the TryXPCValue trait. It checks your object's type via xpc_get_type() and yields a clear error if you're using the wrong type: ```rust

[test]

fn deserializeaswrongtype() { let ani64: XPCObject = XPCObject::from(42 as i64); let asu64: Result = ani64.xpcvalue(); asserteq!( asu64.err().unwrap(), XPCValueError("Cannot get int64 as uint64".tostring()) ); } ```

XPC Dictionaries

Go from a HashMap to xpc_object_t with the XPCObject type:

```rust let mut message: HashMap<&str, XPCObject> = HashMap::new(); message.insert("type", XPCObject::from(1 as u64)); message.insert("handle", XPCObject::from(0 as u64)); message.insert("subsystem", XPCObject::from(3 as u64)); message.insert("routine", XPCObject::from(815 as u64)); message.insert("legacy", XPCObject::from(true));

let xpc_object: XPCObject = message.into(); ```

Call xpc_pipe_routine and receive Result<XPCObject, XPCError>:

```rust let xpc_object: XPCObject = message.into();

match xpcobject.piperoutine() { Ok(xpc_object) => { /* do stuff and things / }, Err(XPCError::PipeError(err)) => { / err is a string w/strerror(errno) */ } } ```

The response is likely an XPC dictionary -- go back to a HashMap:

```rust let xpcobject: XPCObject = message.into(); let response: Result = xpcobject .piperoutine() .andthen(|r| r.try_into());

let XPCDictionary(hm) = response.unwrap(); let whatever = hm.get("..."); ```

Response dictionaries can be nested, so XPCDictionary has a helper included for this scenario:

```rust let xpc_object: XPCObject = message.into();

// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" let response: Result = xpcobject .piperoutine() .andthen(|r: XPCObject| r.tryinto()); .andthen(|d: XPCDictionary| d.get(&["service", "LimitLoadToSessionType"]) .andthen(|lltst: XPCObject| lltst.xpc_value()); ```

Or, retrieve the service key (a child XPC Dictionary) from this response:

```rust let xpc_object: XPCObject = message.into();

// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" let response: Result = xpcobject .piperoutine() .andthen(|r: XPCObject| r.tryinto()); .andthen(|d: XPCDictionary| d.getas_dictionary(&["service"]);

let XPCDictionary(hm) = response.unwrap(); let whatever = hm.get("..."); ```

XPC Arrays

An XPC array can be made from either Vec<XPCObject> or Vec<Into<XPCObject>>:

```rust let xpc_array = XPCObject::from(vec![XPCObject::from("eins"), XPCObject::from("zwei"), XPCObject::from("polizei")]);

let xpc_array = XPCObject::from(vec!["eins", "zwei", "polizei"]); ```

Go back to Vec using xpc_value:

rust let rs_vec: Vec<XPCObject> = xpc_array.xpc_value().unwrap();