first pass at xpc impl
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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
100
kpcli/src/daemon/xpc.rs
Normal 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"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user