diff --git a/crates/popcorn-d/Cargo.toml b/crates/popcorn-d/Cargo.toml index b711b84..5b31bb7 100644 --- a/crates/popcorn-d/Cargo.toml +++ b/crates/popcorn-d/Cargo.toml @@ -21,6 +21,7 @@ toml = "0.9.8" popcorn-conf = { path = "../popcorn-conf" } serde = { version = "1.0.228", features = ["derive"] } popcorn-proto = { path = "../popcorn-proto" } +bincode = "1.3.3" [build-dependencies] slint-build = "1.14.1" diff --git a/crates/popcorn-d/src/ipc.rs b/crates/popcorn-d/src/ipc.rs new file mode 100644 index 0000000..5fda48d --- /dev/null +++ b/crates/popcorn-d/src/ipc.rs @@ -0,0 +1,56 @@ +use std::fs; +use std::io::Read; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::path::PathBuf; +use std::sync::mpsc::Sender; + +use dirs::runtime_dir; +use popcorn_proto::OsdArgs; + +/// Start a Unix socket server and forward received messages to the UI thread. +pub fn start_ipc(tx: Sender) -> std::io::Result<()> { + let sock_path = socket_path(); + + // Remove leftover socket from a previous run. + let _ = fs::remove_file(&sock_path); + + // Create parent directory if missing. + if let Some(parent) = sock_path.parent() { + fs::create_dir_all(parent)?; + } + + // Bind the Unix domain socket. + let listener = UnixListener::bind(&sock_path)?; + + // Run the IPC accept loop in a background thread. + std::thread::spawn(move || { + // Accept short-lived connections. + for stream in listener.incoming() { + if let Ok(stream) = stream { + // Read one message per connection. + if let Ok(args) = read_message(stream) { + // Forward to UI thread via channel. + let _ = tx.send(args); + } + } + } + }); + + Ok(()) +} + +/// Read a single serialized OsdArgs message from a socket. +fn read_message(mut stream: UnixStream) -> bincode::Result { + let mut buf = Vec::new(); + // Read until client closes the connection. + stream.read_to_end(&mut buf)?; + // Deserialize binary message. + bincode::deserialize(&buf) +} + +/// Compute the per-user runtime socket path. +fn socket_path() -> PathBuf { + // Prefer XDG runtime dir. Fallback to /tmp. + let base = runtime_dir().unwrap_or_else(|| PathBuf::from("/tmp")); + base.join("candywidgets").join("popcorn.sock") +} diff --git a/crates/popcorn-d/src/main.rs b/crates/popcorn-d/src/main.rs index 7049e2c..d428524 100644 --- a/crates/popcorn-d/src/main.rs +++ b/crates/popcorn-d/src/main.rs @@ -1,18 +1,38 @@ +mod ipc; mod osd; use osd::OsdUi; use popcorn_conf::PopcornConfig; +use popcorn_proto::OsdArgs; + +use std::sync::mpsc; fn main() -> Result<(), Box> { + // Load configuration once at startup. let cfg = PopcornConfig::load_or_default(); + + // Create the OSD UI on the main (UI) thread. let osd = OsdUi::new(&cfg)?; - // start IPC loop here - // for each message: - // osd.update(&args, &cfg) - // reset timer + // Channel used to send IPC messages to the UI thread. + let (tx, rx) = mpsc::channel::(); - let _ = slint::run_event_loop(); + // Start IPC server in a background thread. + ipc::start_ipc(tx)?; + // Poll IPC messages on the UI thread. + slint::Timer::default().start( + slint::TimerMode::Repeated, + std::time::Duration::from_millis(16), + move || { + // Process all pending messages. + while let Ok(args) = rx.try_recv() { + let _ = osd.update(&args, &cfg); + } + }, + ); + + // Run the Slint event loop. + slint::run_event_loop(); Ok(()) } diff --git a/crates/popcorn/Cargo.toml b/crates/popcorn/Cargo.toml index 84a150c..f488ebf 100644 --- a/crates/popcorn/Cargo.toml +++ b/crates/popcorn/Cargo.toml @@ -12,5 +12,6 @@ keywords = ["OSD", "popup", "desktop", "cli"] license = "GPL-3+" [dependencies] +bincode = "1.3.3" clap = { version = "4.5.53", features = ["derive"] } -popcorn-proto = { path = "../popcorn-proto" } \ No newline at end of file +popcorn-proto = { path = "../popcorn-proto" } diff --git a/crates/popcorn/src/main.rs b/crates/popcorn/src/main.rs index 9009e6e..d2a5690 100644 --- a/crates/popcorn/src/main.rs +++ b/crates/popcorn/src/main.rs @@ -1,8 +1,8 @@ mod args; use args::CliArgs; -use popcorn_proto::OsdArgs; use clap::Parser; +use popcorn_proto::OsdArgs; fn main() { let cli = CliArgs::parse();