Private
Public Access
1
0

xpc: implement signals

This commit is contained in:
2025-08-24 10:36:39 -07:00
parent 16db2caacc
commit da813806bb
2 changed files with 145 additions and 16 deletions

View File

@@ -6,7 +6,7 @@ use std::collections::HashMap;
use std::ffi::CString;
use std::sync::Arc;
use std::thread;
use tokio::sync::{mpsc, oneshot, Mutex};
use tokio::sync::{broadcast, mpsc, oneshot, Mutex};
use xpc_connection::{Message, MessageError, XpcClient, XpcListener};
static LOG_TARGET: &str = "xpc";
@@ -47,10 +47,31 @@ impl XpcAgent {
service_name
);
// Broadcast channel for signals to all connected clients
let (signal_tx, _signal_rx) = broadcast::channel::<Signal>(64);
// Spawn a single distributor task that forwards daemon signals to broadcast
{
let receiver_arc = self.signal_receiver.clone();
let signal_tx_clone = signal_tx.clone();
tokio::spawn(async move {
let mut receiver = receiver_arc
.lock()
.await
.take()
.expect("Signal receiver already taken");
while let Some(signal) = receiver.recv().await {
let _ = signal_tx_clone.send(signal);
}
});
}
let mut listener = XpcListener::listen(&mach_port_name);
while let Some(client) = listener.next().await {
let agent = self.clone();
let signal_rx = signal_tx.subscribe();
thread::spawn(move || {
let rt = match tokio::runtime::Builder::new_current_thread()
.enable_all()
@@ -62,7 +83,7 @@ impl XpcAgent {
return;
}
};
rt.block_on(handle_client(agent, client));
rt.block_on(handle_client(agent, client, signal_rx));
});
}
@@ -383,27 +404,76 @@ async fn dispatch(agent: &XpcAgent, root: &HashMap<CString, Message>) -> Message
}
}
// No-op used by clients to ensure the connection is established and subscribed
"SubscribeSignals" => {
make_ok_reply()
}
// Unknown method fallback
other => make_error_reply("UnknownMethod", other),
}
}
async fn handle_client(agent: XpcAgent, mut client: XpcClient) {
fn signal_to_message(signal: Signal) -> Message {
let mut root: XpcMap = HashMap::new();
let mut args: XpcMap = HashMap::new();
match signal {
Signal::ConversationsUpdated => {
dict_put_str(&mut root, "name", "ConversationsUpdated");
}
Signal::MessagesUpdated(conversation_id) => {
dict_put_str(&mut root, "name", "MessagesUpdated");
dict_put_str(&mut args, "conversation_id", &conversation_id);
}
Signal::AttachmentDownloaded(attachment_id) => {
dict_put_str(&mut root, "name", "AttachmentDownloadCompleted");
dict_put_str(&mut args, "attachment_id", &attachment_id);
}
Signal::AttachmentUploaded(upload_guid, attachment_guid) => {
dict_put_str(&mut root, "name", "AttachmentUploadCompleted");
dict_put_str(&mut args, "upload_guid", &upload_guid);
dict_put_str(&mut args, "attachment_guid", &attachment_guid);
}
Signal::UpdateStreamReconnected => {
dict_put_str(&mut root, "name", "UpdateStreamReconnected");
}
}
if !args.is_empty() { root.insert(cstr("arguments"), Message::Dictionary(args)); }
Message::Dictionary(root)
}
async fn handle_client(agent: XpcAgent, mut client: XpcClient, mut signal_rx: broadcast::Receiver<Signal>) {
log::info!(target: LOG_TARGET, "New XPC connection");
while let Some(message) = client.next().await {
match message {
Message::Error(MessageError::ConnectionInterrupted) => {
log::warn!(target: LOG_TARGET, "XPC connection interrupted");
loop {
tokio::select! {
maybe_msg = client.next() => {
match maybe_msg {
Some(Message::Error(MessageError::ConnectionInterrupted)) => {
log::warn!(target: LOG_TARGET, "XPC connection interrupted");
}
Some(Message::Dictionary(map)) => {
let response = dispatch(&agent, &map).await;
client.send_message(response);
}
Some(other) => {
log::info!(target: LOG_TARGET, "Echoing message: {:?}", other);
client.send_message(other);
}
None => break,
}
}
Message::Dictionary(map) => {
let response = dispatch(&agent, &map).await;
client.send_message(response);
}
other => {
// For now just echo any non-dictionary messages (useful for testing).
log::info!(target: LOG_TARGET, "Echoing message: {:?}", other);
client.send_message(other);
recv = signal_rx.recv() => {
match recv {
Ok(signal) => {
let msg = signal_to_message(signal);
client.send_message(msg);
}
Err(broadcast::error::RecvError::Closed) => break,
Err(broadcast::error::RecvError::Lagged(_)) => {
log::warn!(target: LOG_TARGET, "Lagged behind on signals; dropping some events for this client");
}
}
}
}
}