use std::time::Duration;
use anyhow::{Context, Result};
use serde_json::{Value, json};
use ff_rdp_core::RdpTransport;
use super::process;
use super::registry::{self, DaemonInfo};
pub(crate) enum ConnectionTarget {
Daemon { port: u16 },
Direct,
}
pub(crate) fn find_running_daemon(
firefox_host: &str,
firefox_port: u16,
) -> Result<Option<DaemonInfo>> {
let Some(info) = registry::read_registry()? else {
return Ok(None);
};
if info.firefox_host != firefox_host || info.firefox_port != firefox_port {
return Ok(None);
}
if !process::is_process_alive(info.pid) {
eprintln!(
"daemon: cleaning up stale registry (PID {} is dead)",
info.pid
);
registry::remove_registry().ok();
return Ok(None);
}
Ok(Some(info))
}
pub(crate) fn resolve_connection_target(
firefox_host: &str,
firefox_port: u16,
daemon_timeout_secs: u64,
no_daemon: bool,
) -> ConnectionTarget {
if no_daemon {
return ConnectionTarget::Direct;
}
match find_running_daemon(firefox_host, firefox_port) {
Ok(Some(info)) => {
return ConnectionTarget::Daemon {
port: info.proxy_port,
};
}
Ok(None) => {} Err(e) => {
eprintln!(
"warning: failed to check daemon status: {e:#}{}",
log_path_hint()
);
return ConnectionTarget::Direct;
}
}
let exe_path = match std::env::current_exe() {
Ok(p) => p,
Err(e) => {
eprintln!("warning: cannot determine executable path: {e}, connecting directly");
return ConnectionTarget::Direct;
}
};
if let Err(e) =
process::spawn_daemon(&exe_path, firefox_host, firefox_port, daemon_timeout_secs)
{
eprintln!(
"warning: failed to start daemon: {e:#}, connecting directly{}",
log_path_hint()
);
return ConnectionTarget::Direct;
}
match process::wait_for_registry(Duration::from_secs(5), firefox_host, firefox_port) {
Ok(info) => ConnectionTarget::Daemon {
port: info.proxy_port,
},
Err(e) => {
eprintln!(
"warning: daemon started but registry not found: {e:#}, connecting directly{}",
log_path_hint()
);
ConnectionTarget::Direct
}
}
}
pub(crate) fn drain_daemon_events(
transport: &mut RdpTransport,
resource_type: &str,
) -> Result<Vec<Value>> {
let msg = json!({
"to": "daemon",
"type": "drain",
"resourceType": resource_type,
});
transport
.send(&msg)
.context("sending drain request to daemon")?;
for _ in 0..64 {
let response = transport
.recv()
.context("receiving drain response from daemon")?;
if response.get("from").and_then(Value::as_str) == Some("daemon") {
if let Some(err) = response.get("error").and_then(Value::as_str) {
anyhow::bail!("daemon drain error: {err}");
}
let events = response
.get("events")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
return Ok(events);
}
}
anyhow::bail!("did not receive daemon drain response within 64 frames")
}
pub(crate) fn start_daemon_stream(transport: &mut RdpTransport, resource_type: &str) -> Result<()> {
let msg = json!({
"to": "daemon",
"type": "stream",
"resourceType": resource_type,
});
transport
.send(&msg)
.context("sending stream request to daemon")?;
recv_daemon_ack(transport, "stream").map(|_leftovers| ())
}
pub(crate) fn stop_daemon_stream(transport: &mut RdpTransport, resource_type: &str) -> Result<()> {
let msg = json!({
"to": "daemon",
"type": "stop-stream",
"resourceType": resource_type,
});
transport
.send(&msg)
.context("sending stop-stream request to daemon")?;
recv_daemon_ack(transport, "stop-stream").map(|_leftovers| ())
}
pub(crate) fn stop_daemon_stream_draining(
transport: &mut RdpTransport,
resource_type: &str,
) -> Result<Vec<Value>> {
let msg = json!({
"to": "daemon",
"type": "stop-stream",
"resourceType": resource_type,
});
transport
.send(&msg)
.context("sending stop-stream request to daemon")?;
recv_daemon_ack(transport, "stop-stream")
}
fn recv_daemon_ack(transport: &mut RdpTransport, context: &str) -> Result<Vec<Value>> {
let mut leftovers: Vec<Value> = Vec::new();
for _ in 0..64 {
let response = transport
.recv()
.with_context(|| format!("receiving {context} response from daemon"))?;
if response.get("from").and_then(Value::as_str) == Some("daemon") {
if let Some(err) = response.get("error").and_then(Value::as_str) {
anyhow::bail!("daemon {context} error: {err}");
}
return Ok(leftovers);
}
leftovers.push(response);
}
anyhow::bail!("did not receive daemon ack for {context} within 64 frames")
}
fn log_path_hint() -> String {
match super::registry::log_path() {
Ok(p) => format!(" (check {} for details)", p.display()),
Err(_) => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_daemon_flag_always_returns_direct() {
let target = resolve_connection_target("localhost", 6000, 300, true);
assert!(matches!(target, ConnectionTarget::Direct));
}
}