Private
Public Access
1
0

first pass at xpc impl

This commit is contained in:
James Magahern
2025-08-01 12:26:17 -07:00
parent 43b668e9a2
commit 911454aafb
29 changed files with 761 additions and 141 deletions

View File

@@ -29,3 +29,8 @@ dbus-tree = "0.9.2"
# D-Bus codegen only on Linux
[target.'cfg(target_os = "linux")'.build-dependencies]
dbus-codegen = "0.10.0"
# XPC (libxpc) interface only on macOS
[target.'cfg(target_os = "macos")'.dependencies]
futures-preview = "=0.2.2"
xpc-connection = { git = "https://github.com/dfrankland/xpc-connection-rs.git", rev = "cd4fb3d", package = "xpc-connection" }

View File

@@ -16,11 +16,10 @@ fn main() {
..Default::default()
};
let xml = std::fs::read_to_string(KORDOPHONE_XML)
.expect("Error reading server dbus interface");
let xml = std::fs::read_to_string(KORDOPHONE_XML).expect("Error reading server dbus interface");
let output = dbus_codegen::generate(&xml, &opts)
.expect("Error generating client dbus interface");
let output =
dbus_codegen::generate(&xml, &opts).expect("Error generating client dbus interface");
std::fs::write(out_path, output).expect("Error writing client dbus code");

View File

@@ -6,6 +6,9 @@ use clap::Subcommand;
#[cfg(target_os = "linux")]
mod dbus;
#[cfg(target_os = "macos")]
mod xpc;
#[async_trait]
pub trait DaemonInterface {
async fn print_version(&mut self) -> Result<()>;
@@ -40,40 +43,72 @@ impl StubDaemonInterface {
#[async_trait]
impl DaemonInterface for StubDaemonInterface {
async fn print_version(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn print_conversations(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn sync_conversations(&mut self, _conversation_id: Option<String>) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn sync_conversations_list(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn print_messages(&mut self, _conversation_id: String, _last_message_id: Option<String>) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
async fn print_messages(
&mut self,
_conversation_id: String,
_last_message_id: Option<String>,
) -> Result<()> {
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn enqueue_outgoing_message(&mut self, _conversation_id: String, _text: String) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
async fn enqueue_outgoing_message(
&mut self,
_conversation_id: String,
_text: String,
) -> Result<()> {
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn wait_for_signals(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn config(&mut self, _cmd: ConfigCommands) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn delete_all_conversations(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn download_attachment(&mut self, _attachment_id: String) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn upload_attachment(&mut self, _path: String) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
async fn mark_conversation_as_read(&mut self, _conversation_id: String) -> Result<()> {
Err(anyhow::anyhow!("Daemon interface not implemented on this platform"))
Err(anyhow::anyhow!(
"Daemon interface not implemented on this platform"
))
}
}
@@ -82,7 +117,11 @@ pub fn new_daemon_interface() -> Result<Box<dyn DaemonInterface>> {
{
Ok(Box::new(dbus::DBusDaemonInterface::new()?))
}
#[cfg(not(target_os = "linux"))]
#[cfg(target_os = "macos")]
{
Ok(Box::new(xpc::XpcDaemonInterface::new()?))
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
Ok(Box::new(StubDaemonInterface::new()?))
}
@@ -162,7 +201,9 @@ impl Commands {
conversation_id,
last_message_id,
} => {
client.print_messages(conversation_id, last_message_id).await
client
.print_messages(conversation_id, last_message_id)
.await
}
Commands::DeleteAllConversations => client.delete_all_conversations().await,
Commands::SendMessage {

100
kpcli/src/daemon/xpc.rs Normal file
View File

@@ -0,0 +1,100 @@
#![cfg(target_os = "macos")]
//! macOS XPC implementation of the DaemonInterface for kpcli.
use super::{ConfigCommands, DaemonInterface};
use anyhow::Result;
use async_trait::async_trait;
use futures::stream::StreamExt;
use futures::executor::block_on;
use std::collections::HashMap;
use xpc_connection::{Message, XpcConnection};
const SERVICE_NAME: &str = "net.buzzert.kordophonecd\0";
const GET_VERSION_METHOD: &str = "GetVersion\0";
/// XPC-based implementation of DaemonInterface that sends method calls to the daemon over libxpc.
pub struct XpcDaemonInterface;
impl XpcDaemonInterface {
/// Create a new XpcDaemonInterface. No state is held.
pub fn new() -> Result<Self> {
Ok(Self)
}
}
#[async_trait]
impl DaemonInterface for XpcDaemonInterface {
async fn print_version(&mut self) -> Result<()> {
// Open an XPC connection to the daemon service
let mut conn = XpcConnection::new(SERVICE_NAME);
let mut incoming = conn.connect();
// Send a GetVersion request as a dictionary message
let mut dict = HashMap::new();
dict.insert(
GET_VERSION_METHOD.to_string(),
Message::String(String::new()),
);
conn.send_message(Message::Dictionary(dict));
// Wait for a single string reply (futures-preview StreamFuture returns (Option<Item>, Stream))
let (opt_msg, _) = match block_on(incoming.next()) {
Ok(pair) => pair,
Err(e) => {
eprintln!("Error reading XPC reply: {:?}", e);
return Ok(());
}
};
if let Some(Message::String(ver_raw)) = opt_msg {
// Trim the trailing NUL if present
let version = ver_raw.trim_end_matches('\0');
println!("Server version: {}", version);
} else {
eprintln!("Unexpected XPC reply for GetVersion");
}
Ok(())
}
// Remaining methods unimplemented on macOS
async fn print_conversations(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn sync_conversations(&mut self, _conversation_id: Option<String>) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn sync_conversations_list(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn print_messages(
&mut self,
_conversation_id: String,
_last_message_id: Option<String>,
) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn enqueue_outgoing_message(
&mut self,
_conversation_id: String,
_text: String,
) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn wait_for_signals(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn config(&mut self, _cmd: ConfigCommands) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn delete_all_conversations(&mut self) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn download_attachment(&mut self, _attachment_id: String) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn upload_attachment(&mut self, _path: String) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
async fn mark_conversation_as_read(&mut self, _conversation_id: String) -> Result<()> {
Err(anyhow::anyhow!("Feature not implemented for XPC"))
}
}