diff --git a/kordophoned/src/xpc/agent.rs b/kordophoned/src/xpc/agent.rs index 3e4baaf..409eb04 100644 --- a/kordophoned/src/xpc/agent.rs +++ b/kordophoned/src/xpc/agent.rs @@ -113,12 +113,47 @@ impl XpcAgent { let response = rt_conn.block_on(super::rpc::dispatch(&agent_conn, &conns_for_handler, client, &map)); let reply = xpc_sys::xpc_dictionary_create_reply(msg); if !reply.is_null() { + use std::ffi::CString as StdCString; + use std::os::fd::AsRawFd; + + // Precompute optional fd_path instruction from the message map + let mut maybe_fd_path: Option = None; + if let Message::Dictionary(ref resp_map) = response { + let attach = super::util::dict_get_str(resp_map, "attach_fd").unwrap_or_default() == "true"; + if attach { + maybe_fd_path = super::util::dict_get_str(resp_map, "fd_path"); + } + } + let payload = message_to_xpc_object(response); let apply_block = ConcreteBlock::new(move |key: *const c_char, value: xpc_sys::xpc_object_t| { xpc_sys::xpc_dictionary_set_value(reply, key, value); }) .copy(); + xpc_sys::xpc_dictionary_apply(payload, apply_block.deref() as *const _ as *mut _); + + // Optional FD attachment if requested by response + if let Some(fd_path) = maybe_fd_path { + match std::fs::OpenOptions::new().read(true).open(&fd_path) { + Ok(file) => { + let raw_fd = file.as_raw_fd(); + unsafe { + let fd_obj = xpc_sys::xpc_fd_create(raw_fd); + let key = StdCString::new("fd").unwrap(); + xpc_sys::xpc_dictionary_set_value(reply, key.as_ptr(), fd_obj); + // fd_obj is retained by reply; release our reference + xpc_sys::xpc_release(fd_obj); + } + // Keep file alive until after send + std::mem::forget(file); + } + Err(e) => { + log::warn!(target: LOG_TARGET, "Failed to open fd_path '{}': {}", fd_path, e); + } + } + } + xpc_sys::xpc_connection_send_message(client, reply); xpc_sys::xpc_release(payload); xpc_sys::xpc_release(reply); diff --git a/kordophoned/src/xpc/rpc.rs b/kordophoned/src/xpc/rpc.rs index 904e87f..bcdc4f1 100644 --- a/kordophoned/src/xpc/rpc.rs +++ b/kordophoned/src/xpc/rpc.rs @@ -303,6 +303,39 @@ pub async fn dispatch( } } + // OpenAttachmentFd (attach file descriptor in reply) + "OpenAttachmentFd" => { + let args = match get_dictionary_field(root, "arguments") { + Some(a) => a, + None => return make_error_reply("InvalidRequest", "Missing arguments"), + }; + let attachment_id = match dict_get_str(args, "attachment_id") { + Some(v) => v, + None => return make_error_reply("InvalidRequest", "Missing attachment_id"), + }; + let preview = dict_get_str(args, "preview") + .map(|s| s == "true") + .unwrap_or(false); + + match agent + .send_event(|r| Event::GetAttachment(attachment_id, r)) + .await + { + Ok(attachment) => { + let path = attachment.get_path_for_preview(preview); + let mut reply: XpcMap = HashMap::new(); + + // The agent resolves fd_path to a file descriptor and returns it in the reply + dict_put_str(&mut reply, "type", "OpenAttachmentFdResponse"); + dict_put_str(&mut reply, "fd_path", &path.to_string_lossy()); + dict_put_str(&mut reply, "attach_fd", "true"); + + Message::Dictionary(reply) + } + Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + } + } + // DownloadAttachment "DownloadAttachment" => { let args = match get_dictionary_field(root, "arguments") {