diff --git a/kordophoned/src/lib.rs b/kordophoned/src/lib.rs index 3c76722..d6bbb01 100644 --- a/kordophoned/src/lib.rs +++ b/kordophoned/src/lib.rs @@ -1,3 +1 @@ pub mod daemon; - - diff --git a/kordophoned/src/main.rs b/kordophoned/src/main.rs index bf2e037..3f1e8dd 100644 --- a/kordophoned/src/main.rs +++ b/kordophoned/src/main.rs @@ -35,7 +35,8 @@ async fn start_ipc_agent(daemon: &mut Daemon) { #[cfg(target_os = "macos")] async fn start_ipc_agent(daemon: &mut Daemon) { // Start the macOS XPC agent (events in, signals out) on a dedicated thread. - let agent = xpc::agent::XpcAgent::new(daemon.event_sender.clone(), daemon.obtain_signal_receiver()); + let agent = + xpc::agent::XpcAgent::new(daemon.event_sender.clone(), daemon.obtain_signal_receiver()); std::thread::spawn(move || { // Use a single-threaded Tokio runtime for the XPC agent. let rt = tokio::runtime::Builder::new_current_thread() diff --git a/kordophoned/src/xpc/agent.rs b/kordophoned/src/xpc/agent.rs index e337c08..89dd083 100644 --- a/kordophoned/src/xpc/agent.rs +++ b/kordophoned/src/xpc/agent.rs @@ -1,14 +1,13 @@ -use kordophoned::daemon::{events::Event, signals::Signal, DaemonResult}; use crate::xpc::interface::SERVICE_NAME; use futures_util::StreamExt; +use kordophoned::daemon::{events::Event, signals::Signal, DaemonResult}; use std::collections::HashMap; use std::ffi::CString; use std::sync::Arc; -use tokio::sync::{mpsc, oneshot, Mutex}; use std::thread; +use tokio::sync::{mpsc, oneshot, Mutex}; use xpc_connection::{Message, MessageError, XpcClient, XpcListener}; - static LOG_TARGET: &str = "xpc"; /// XPC IPC agent that forwards daemon events and signals over libxpc. @@ -83,7 +82,9 @@ impl XpcAgent { } } -fn cstr(s: &str) -> CString { CString::new(s).unwrap_or_else(|_| CString::new("").unwrap()) } +fn cstr(s: &str) -> CString { + CString::new(s).unwrap_or_else(|_| CString::new("").unwrap()) +} fn get_string_field(map: &HashMap, key: &str) -> Option { let k = CString::new(key).ok()?; @@ -93,7 +94,10 @@ fn get_string_field(map: &HashMap, key: &str) -> Option(map: &'a HashMap, key: &str) -> Option<&'a HashMap> { +fn get_dictionary_field<'a>( + map: &'a HashMap, + key: &str, +) -> Option<&'a HashMap> { let k = CString::new(key).ok()?; map.get(&k).and_then(|v| match v { Message::Dictionary(d) => Some(d), @@ -106,7 +110,7 @@ fn make_error_reply(code: &str, message: &str) -> Message { reply.insert(cstr("type"), Message::String(cstr("Error"))); reply.insert(cstr("error"), Message::String(cstr(code))); reply.insert(cstr("message"), Message::String(cstr(message))); - + Message::Dictionary(reply) } @@ -151,17 +155,15 @@ async fn dispatch(agent: &XpcAgent, root: &HashMap) -> Message match method.as_str() { // Example implemented method: GetVersion - "GetVersion" => { - match agent.send_event(Event::GetVersion).await { - Ok(version) => { - let mut reply: XpcMap = HashMap::new(); - dict_put_str(&mut reply, "type", "GetVersionResponse"); - dict_put_str(&mut reply, "version", &version); - Message::Dictionary(reply) - } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + "GetVersion" => match agent.send_event(Event::GetVersion).await { + Ok(version) => { + let mut reply: XpcMap = HashMap::new(); + dict_put_str(&mut reply, "type", "GetVersionResponse"); + dict_put_str(&mut reply, "version", &version); + Message::Dictionary(reply) } - } + Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + }, "GetConversations" => { // Defaults @@ -169,20 +171,35 @@ async fn dispatch(agent: &XpcAgent, root: &HashMap) -> Message let mut offset: i32 = 0; if let Some(args) = get_dictionary_field(root, "arguments") { - if let Some(v) = dict_get_i64_from_str(args, "limit") { limit = v as i32; } - if let Some(v) = dict_get_i64_from_str(args, "offset") { offset = v as i32; } + if let Some(v) = dict_get_i64_from_str(args, "limit") { + limit = v as i32; + } + if let Some(v) = dict_get_i64_from_str(args, "offset") { + offset = v as i32; + } } - match agent.send_event(|r| Event::GetAllConversations(limit, offset, r)).await { + match agent + .send_event(|r| Event::GetAllConversations(limit, offset, r)) + .await + { Ok(conversations) => { // Build array of conversation dictionaries let mut items: Vec = Vec::with_capacity(conversations.len()); for conv in conversations { let mut m: XpcMap = HashMap::new(); dict_put_str(&mut m, "guid", &conv.guid); - dict_put_str(&mut m, "display_name", &conv.display_name.unwrap_or_default()); + dict_put_str( + &mut m, + "display_name", + &conv.display_name.unwrap_or_default(), + ); dict_put_i64_as_str(&mut m, "unread_count", conv.unread_count as i64); - dict_put_str(&mut m, "last_message_preview", &conv.last_message_preview.unwrap_or_default()); + dict_put_str( + &mut m, + "last_message_preview", + &conv.last_message_preview.unwrap_or_default(), + ); // participants -> array of strings let participant_names: Vec = conv diff --git a/kordophoned/src/xpc/interface.rs b/kordophoned/src/xpc/interface.rs index 23fa648..9418b17 100644 --- a/kordophoned/src/xpc/interface.rs +++ b/kordophoned/src/xpc/interface.rs @@ -3,6 +3,3 @@ /// Mach service name for the XPC interface (must include trailing NUL). pub const SERVICE_NAME: &str = "net.buzzert.kordophonecd\0"; - -/// Method names for the XPC interface (must include trailing NUL). -pub const GET_VERSION_METHOD: &str = "GetVersion\0"; diff --git a/kpcli/src/daemon/xpc.rs b/kpcli/src/daemon/xpc.rs index 3595651..12851a2 100644 --- a/kpcli/src/daemon/xpc.rs +++ b/kpcli/src/daemon/xpc.rs @@ -14,8 +14,8 @@ use futures::{ Stream, }; - const SERVICE_NAME: &str = "net.buzzert.kordophonecd\0"; + const GET_VERSION_METHOD: &str = "GetVersion"; const GET_CONVERSATIONS_METHOD: &str = "GetConversations"; @@ -32,12 +32,16 @@ impl XPCClient { pub fn connect(name: impl AsRef) -> Self { use block::ConcreteBlock; use xpc_connection::xpc_object_to_message; - use xpc_connection_sys::xpc_connection_set_event_handler; use xpc_connection_sys::xpc_connection_resume; + use xpc_connection_sys::xpc_connection_set_event_handler; let name = name.as_ref(); let connection = unsafe { - xpc_connection_sys::xpc_connection_create_mach_service(name.as_ptr(), std::ptr::null_mut(), 0) + xpc_connection_sys::xpc_connection_create_mach_service( + name.as_ptr(), + std::ptr::null_mut(), + 0, + ) }; let (sender, receiver) = unbounded_channel(); @@ -78,8 +82,8 @@ impl XPCClient { impl Drop for XPCClient { fn drop(&mut self) { - use xpc_connection_sys::xpc_release; use xpc_connection_sys::xpc_object_t; + use xpc_connection_sys::xpc_release; unsafe { xpc_release(self.connection as xpc_object_t) }; } @@ -118,16 +122,30 @@ impl XpcDaemonInterface { Ok(CString::new(service_name)?) } - fn build_request(method: &str, args: Option>) -> HashMap { + 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())); + 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.insert( + CString::new("arguments").unwrap(), + Message::Dictionary(arguments), + ); } request } - async fn call_method(&self, client: &mut XPCClient, method: &str, args: Option>) -> anyhow::Result> { + 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)); @@ -138,10 +156,15 @@ impl XpcDaemonInterface { } } - fn key(k: &str) -> CString { CString::new(k).unwrap() } + fn key(k: &str) -> CString { + CString::new(k).unwrap() + } fn get_string<'a>(map: &'a HashMap, key: &str) -> Option<&'a CStr> { - map.get(&Self::key(key)).and_then(|v| match v { Message::String(s) => Some(s.as_c_str()), _ => None }) + map.get(&Self::key(key)).and_then(|v| match v { + Message::String(s) => Some(s.as_c_str()), + _ => None, + }) } fn get_i64_from_str(map: &HashMap, key: &str) -> Option { @@ -157,7 +180,9 @@ impl DaemonInterface for XpcDaemonInterface { let mut client = XPCClient::connect(&mach_port_name); // Call generic method and parse reply - let map = self.call_method(&mut client, GET_VERSION_METHOD, None).await?; + 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(()) @@ -165,7 +190,9 @@ impl DaemonInterface for XpcDaemonInterface { println!("XPC replied with type: {}", ty.to_string_lossy()); Ok(()) } else { - Err(anyhow::anyhow!("Unexpected XPC reply payload for GetVersion")) + Err(anyhow::anyhow!( + "Unexpected XPC reply payload for GetVersion" + )) } } @@ -177,8 +204,14 @@ impl DaemonInterface for XpcDaemonInterface { // Build arguments: limit=100, offset=0 (string-encoded for portability) let mut args = HashMap::new(); - args.insert(CString::new("limit").unwrap(), Message::String(CString::new("100").unwrap())); - args.insert(CString::new("offset").unwrap(), Message::String(CString::new("0").unwrap())); + args.insert( + CString::new("limit").unwrap(), + Message::String(CString::new("100").unwrap()), + ); + args.insert( + CString::new("offset").unwrap(), + Message::String(CString::new("0").unwrap()), + ); // Call let reply = self @@ -193,15 +226,26 @@ impl DaemonInterface for XpcDaemonInterface { for item in items { if let Message::Dictionary(map) = item { // Convert to PrintableConversation - let guid = Self::get_string(map, "guid").map(|s| s.to_string_lossy().into_owned()).unwrap_or_default(); - let display_name = Self::get_string(map, "display_name").map(|s| s.to_string_lossy().into_owned()); - let last_preview = Self::get_string(map, "last_message_preview").map(|s| s.to_string_lossy().into_owned()); + let guid = Self::get_string(map, "guid") + .map(|s| s.to_string_lossy().into_owned()) + .unwrap_or_default(); + let display_name = Self::get_string(map, "display_name") + .map(|s| s.to_string_lossy().into_owned()); + let last_preview = Self::get_string(map, "last_message_preview") + .map(|s| s.to_string_lossy().into_owned()); - let unread_count = Self::get_i64_from_str(map, "unread_count").unwrap_or(0) as i32; + let unread_count = + Self::get_i64_from_str(map, "unread_count").unwrap_or(0) as i32; let date_ts: i64 = Self::get_i64_from_str(map, "date").unwrap_or(0); let participants: Vec = match map.get(&Self::key("participants")) { - Some(Message::Array(arr)) => arr.iter().filter_map(|m| match m { Message::String(s) => Some(s.to_string_lossy().into_owned()), _ => None }).collect(), + Some(Message::Array(arr)) => arr + .iter() + .filter_map(|m| match m { + Message::String(s) => Some(s.to_string_lossy().into_owned()), + _ => None, + }) + .collect(), _ => Vec::new(), }; @@ -211,7 +255,8 @@ impl DaemonInterface for XpcDaemonInterface { display_name, last_message_preview: last_preview, unread_count, - date: time::OffsetDateTime::from_unix_timestamp(date_ts).unwrap_or_else(|_| time::OffsetDateTime::UNIX_EPOCH), + date: time::OffsetDateTime::from_unix_timestamp(date_ts) + .unwrap_or_else(|_| time::OffsetDateTime::UNIX_EPOCH), participants, }; @@ -220,7 +265,10 @@ impl DaemonInterface for XpcDaemonInterface { } Ok(()) } - Some(other) => Err(anyhow::anyhow!("Unexpected conversations payload: {:?}", other)), + Some(other) => Err(anyhow::anyhow!( + "Unexpected conversations payload: {:?}", + other + )), None => Err(anyhow::anyhow!("Missing conversations in reply")), } }