diff --git a/kpcli/src/daemon/mod.rs b/kpcli/src/daemon/mod.rs index 04d0130..872ab1b 100644 --- a/kpcli/src/daemon/mod.rs +++ b/kpcli/src/daemon/mod.rs @@ -9,7 +9,8 @@ mod dbus; #[cfg(target_os = "macos")] mod xpc; -#[async_trait] +#[cfg_attr(target_os = "macos", async_trait(?Send))] +#[cfg_attr(not(target_os = "macos"), async_trait)] pub trait DaemonInterface { async fn print_version(&mut self) -> Result<()>; async fn print_conversations(&mut self) -> Result<()>; @@ -40,7 +41,8 @@ impl StubDaemonInterface { } } -#[async_trait] +#[cfg_attr(target_os = "macos", async_trait(?Send))] +#[cfg_attr(not(target_os = "macos"), async_trait)] impl DaemonInterface for StubDaemonInterface { async fn print_version(&mut self) -> Result<()> { Err(anyhow::anyhow!( diff --git a/kpcli/src/daemon/xpc.rs b/kpcli/src/daemon/xpc.rs index 21fd956..1308bfe 100644 --- a/kpcli/src/daemon/xpc.rs +++ b/kpcli/src/daemon/xpc.rs @@ -111,49 +111,55 @@ impl XpcDaemonInterface { pub fn new() -> Result { Ok(Self) } + + fn build_service_name() -> Result { + let service_name = SERVICE_NAME.trim_end_matches('\0'); + Ok(CString::new(service_name)?) + } + + fn build_request(method: &str, args: Option>) -> HashMap { + let mut request = HashMap::new(); + request.insert(CString::new("method").unwrap(), Message::String(CString::new(method).unwrap())); + if let Some(arguments) = args { + request.insert(CString::new("arguments").unwrap(), Message::Dictionary(arguments)); + } + request + } + + async fn call_method(&self, client: &mut XPCClient, method: &str, args: Option>) -> anyhow::Result> { + let request = Self::build_request(method, args); + client.send_message(Message::Dictionary(request)); + + match client.next().await { + Some(Message::Dictionary(map)) => Ok(map), + Some(other) => Err(anyhow::anyhow!("Unexpected XPC reply: {:?}", other)), + None => Err(anyhow::anyhow!("No reply received from XPC daemon")), + } + } + + fn get_string<'a>(map: &'a HashMap, key: &str) -> Option<&'a CStr> { + map.get(&CString::new(key).ok()?).and_then(|v| match v { Message::String(s) => Some(s.as_c_str()), _ => None }) + } } -#[async_trait] +#[async_trait(?Send)] impl DaemonInterface for XpcDaemonInterface { async fn print_version(&mut self) -> Result<()> { - // Build service name CString (trim trailing NUL from const) - let service_name = SERVICE_NAME.trim_end_matches('\0'); - let mach_port_name = CString::new(service_name)?; - - // Open an XPC connection to the daemon service + // Build service name and connect + let mach_port_name = Self::build_service_name()?; let mut client = XPCClient::connect(&mach_port_name); - // Send a GetVersion request as a dictionary message: { method: "GetVersion" } - { - let mut request = HashMap::new(); - request.insert( - CString::new("method").unwrap(), - Message::String(CString::new(GET_VERSION_METHOD).unwrap()), - ); - client.send_message(Message::Dictionary(request)); + // Call generic method and parse reply + let map = self.call_method(&mut client, GET_VERSION_METHOD, None).await?; + if let Some(ver) = Self::get_string(&map, "version") { + println!("Server version: {}", ver.to_string_lossy()); + Ok(()) + } else if let Some(ty) = Self::get_string(&map, "type") { + println!("XPC replied with type: {}", ty.to_string_lossy()); + Ok(()) + } else { + Err(anyhow::anyhow!("Unexpected XPC reply payload for GetVersion")) } - - // Await a single reply and print the version - match client.next().await { - Some(Message::Dictionary(map)) => { - if let Some(Message::String(ver)) = map.get(&CString::new("version").unwrap()) { - println!("Server version: {}", ver.to_string_lossy()); - } else if let Some(Message::String(ty)) = map.get(&CString::new("type").unwrap()) - { - println!("XPC replied with type: {}", ty.to_string_lossy()); - } else { - eprintln!("Unexpected XPC reply payload for GetVersion"); - } - } - Some(other) => { - eprintln!("Unexpected XPC reply: {:?}", other); - } - None => { - eprintln!("No reply received from XPC daemon"); - } - } - - Ok(()) } // Remaining methods unimplemented on macOS