fix(daemon, ipc): initialize ipc listener on boot, standardize socket paths, and add stream map buffers

This commit is contained in:
katR 2026-09-04 23:59:53 +05:30
parent 3047809224
commit 18c74bc12a
4 changed files with 150 additions and 132 deletions

View file

@ -72,10 +72,146 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Err(_) => None,
}
};
let initial_config = vrec::config::VrecConfig::load();
// Automatically register dynamic keybindings on Hyprland if active (no config edits needed)
vrec::hyprland_binds::register_hyprland_binds(&initial_config);
vrec::hyprland_binds::spawn_hyprland_reload_watcher();
let (frame_tx, frame_rx) = bounded::<Frame>(5);
let (cmd_tx, cmd_rx) = bounded::<Command>(32);
let (mux_tx, mux_rx) = bounded::<Vec<Packet>>(1);
let (audio_tx, audio_rx) = bounded::<Vec<f32>>(500);
let is_recording_state = Arc::new(AtomicBool::new(false));
let record_start_state = Arc::new(AtomicU64::new(0));
let replay_enabled_state = Arc::new(AtomicBool::new(initial_config.replay_enabled));
let audio_muted_state = Arc::new(AtomicBool::new(false));
#[cfg(unix)]
let listener = {
let socket_path = vrec::ipc::get_socket_path();
let _ = std::fs::remove_file(&socket_path);
let l = std::os::unix::net::UnixListener::bind(&socket_path)?;
println!("Daemon listening on IPC socket: {}", socket_path);
l
};
#[cfg(windows)]
let listener = {
let l = std::net::TcpListener::bind("127.0.0.1:42069")?;
println!("Daemon listening on TCP IPC: 127.0.0.1:42069");
l
};
let cmd_tx_ipc = cmd_tx.clone();
let is_recording_state_ipc = Arc::clone(&is_recording_state);
let record_start_state_ipc = Arc::clone(&record_start_state);
let replay_enabled_state_ipc = Arc::clone(&replay_enabled_state);
let audio_muted_state_ipc = Arc::clone(&audio_muted_state);
std::thread::spawn(move || {
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let _ = stream.set_read_timeout(Some(std::time::Duration::from_millis(1000)));
let _ = stream.set_write_timeout(Some(std::time::Duration::from_millis(1000)));
let mut len_buf = [0u8; 4];
if stream.read_exact(&mut len_buf).is_ok() {
let len = u32::from_le_bytes(len_buf) as usize;
let mut payload = vec![0u8; len];
if stream.read_exact(&mut payload).is_ok()
&& let Ok(cmd) = serde_json::from_slice::<Command>(&payload) {
match cmd {
Command::GetStatus => {
let rec = is_recording_state_ipc.load(Ordering::SeqCst);
let start_ts = record_start_state_ipc.load(Ordering::SeqCst);
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs();
let duration = if rec && start_ts > 0 {
now.saturating_sub(start_ts)
} else {
0
};
let cfg = vrec::config::VrecConfig::load();
let status = DaemonStatus {
is_recording: rec,
recording_duration_sec: duration,
is_replay_active: replay_enabled_state_ipc.load(Ordering::SeqCst),
audio_muted: audio_muted_state_ipc.load(Ordering::SeqCst),
audio_mode: cfg.audio_mode,
show_cursor: cfg.show_cursor,
mic_volume: cfg.mic_volume,
system_volume: cfg.system_volume,
};
if let Ok(resp) = serde_json::to_vec(&status) {
let len_resp = (resp.len() as u32).to_le_bytes();
let _ = stream.write_all(&len_resp);
let _ = stream.write_all(&resp);
}
},
Command::ToggleCursor => {
let mut cfg = vrec::config::VrecConfig::load();
cfg.show_cursor = !cfg.show_cursor;
let _ = cfg.save();
println!("Cursor display toggled to: {}. Restarting daemon for new capture session...", cfg.show_cursor);
let _ = cmd_tx_ipc.send(Command::StopRecording);
std::thread::sleep(std::time::Duration::from_millis(150));
std::process::exit(0);
},
Command::CycleAudioMode => {
let mut cfg = vrec::config::VrecConfig::load();
cfg.audio_mode = match cfg.audio_mode.as_str() {
"system" => "mic",
"mic" => "both",
"both" => "muted",
_ => "system",
}.to_string();
let _ = cfg.save();
println!("Audio mode cycled to: {}", cfg.audio_mode);
let _ = cmd_tx_ipc.try_send(Command::ReloadConfig);
},
Command::StopDaemon => {
println!("StopDaemon requested: Finalizing active recordings...");
vrec::hyprland_binds::unregister_hyprland_binds(&vrec::config::VrecConfig::load());
let _ = cmd_tx_ipc.send(Command::StopRecording);
std::thread::sleep(std::time::Duration::from_millis(350));
std::process::exit(0);
},
other => {
let _ = cmd_tx_ipc.try_send(other);
}
}
}
}
}
Err(err) => {
eprintln!("Socket error: {}", err);
}
}
}
});
let cmd_tx_sig = cmd_tx.clone();
tokio::spawn(async move {
if tokio::signal::ctrl_c().await.is_ok() {
println!("\nReceived shutdown signal. Finalizing recordings...");
let _ = cmd_tx_sig.send(Command::StopRecording);
tokio::time::sleep(std::time::Duration::from_millis(350)).await;
vrec::hyprland_binds::unregister_hyprland_binds(&vrec::config::VrecConfig::load());
std::process::exit(0);
}
});
let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "x11".to_string());
println!("Detected session type: {}", session_type);
let initial_config = vrec::config::VrecConfig::load();
let audio_capture_initial = vrec::capture::audio::AudioCapture::new_with_device_mode_and_volumes(
audio_tx.clone(),
Some(&initial_config.audio_device),
&initial_config.audio_mode,
initial_config.mic_volume,
initial_config.system_volume,
).ok();
let mut source: Box<dyn FrameSource> = if std::env::args().any(|a| a == "--mock") {
println!("Initializing MOCK capture...");
@ -120,27 +256,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Frame::D3D11Texture { .. } => (1920, 1080),
};
let (frame_tx, frame_rx) = bounded::<Frame>(5);
let (cmd_tx, cmd_rx) = bounded::<Command>(10);
let (mux_tx, mux_rx) = bounded::<Vec<Packet>>(1);
let (audio_tx, audio_rx) = bounded::<Vec<f32>>(500);
let audio_capture_initial = vrec::capture::audio::AudioCapture::new_with_device_mode_and_volumes(
audio_tx.clone(),
Some(&initial_config.audio_device),
&initial_config.audio_mode,
initial_config.mic_volume,
initial_config.system_volume,
).ok();
// Automatically register dynamic keybindings on Hyprland if active (no config edits needed)
vrec::hyprland_binds::register_hyprland_binds(&initial_config);
vrec::hyprland_binds::spawn_hyprland_reload_watcher();
let is_recording_state = Arc::new(AtomicBool::new(false));
let record_start_state = Arc::new(AtomicU64::new(0));
let replay_enabled_state = Arc::new(AtomicBool::new(true));
let audio_muted_state = Arc::new(AtomicBool::new(false));
let _ = frame_tx.send(first_frame);
let capture_tx = frame_tx.clone();
@ -167,7 +282,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
#[allow(unused_variables, unused_assignments)]
let mut audio_capture = audio_capture_initial.map(|(c, _, _)| c);
thread::spawn(move || {
let recorder_handle = thread::spawn(move || {
let mut config = vrec::config::VrecConfig::load();
replay_state_clone.store(config.replay_enabled, Ordering::SeqCst);
@ -459,110 +574,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
});
#[cfg(unix)]
let listener = {
let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
let _ = std::fs::remove_file(&socket_path);
let l = std::os::unix::net::UnixListener::bind(&socket_path)?;
println!("Daemon listening on IPC socket: {}", socket_path);
l
};
#[cfg(windows)]
let listener = {
let l = std::net::TcpListener::bind("127.0.0.1:42069")?;
println!("Daemon listening on TCP IPC: 127.0.0.1:42069");
l
};
let cmd_tx_sig = cmd_tx.clone();
tokio::spawn(async move {
if tokio::signal::ctrl_c().await.is_ok() {
println!("\nReceived shutdown signal. Finalizing recordings...");
let _ = cmd_tx_sig.send(Command::StopRecording);
tokio::time::sleep(std::time::Duration::from_millis(350)).await;
let _ = vrec::ipc::send_command(Command::StopDaemon);
}
});
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let _ = stream.set_read_timeout(Some(std::time::Duration::from_millis(1000)));
let _ = stream.set_write_timeout(Some(std::time::Duration::from_millis(1000)));
let mut len_buf = [0u8; 4];
if stream.read_exact(&mut len_buf).is_ok() {
let len = u32::from_le_bytes(len_buf) as usize;
let mut payload = vec![0u8; len];
if stream.read_exact(&mut payload).is_ok()
&& let Ok(cmd) = serde_json::from_slice::<Command>(&payload) {
match cmd {
Command::GetStatus => {
let rec = is_recording_state.load(Ordering::SeqCst);
let start_ts = record_start_state.load(Ordering::SeqCst);
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs();
let duration = if rec && start_ts > 0 {
now.saturating_sub(start_ts)
} else {
0
};
let cfg = vrec::config::VrecConfig::load();
let status = DaemonStatus {
is_recording: rec,
recording_duration_sec: duration,
is_replay_active: replay_enabled_state.load(Ordering::SeqCst),
audio_muted: audio_muted_state.load(Ordering::SeqCst),
audio_mode: cfg.audio_mode,
show_cursor: cfg.show_cursor,
mic_volume: cfg.mic_volume,
system_volume: cfg.system_volume,
};
if let Ok(resp) = serde_json::to_vec(&status) {
let len_resp = (resp.len() as u32).to_le_bytes();
let _ = stream.write_all(&len_resp);
let _ = stream.write_all(&resp);
}
},
Command::ToggleCursor => {
let mut cfg = vrec::config::VrecConfig::load();
cfg.show_cursor = !cfg.show_cursor;
let _ = cfg.save();
println!("Cursor display toggled to: {}. Restarting daemon for new capture session...", cfg.show_cursor);
let _ = cmd_tx.send(Command::StopRecording);
std::thread::sleep(std::time::Duration::from_millis(150));
std::process::exit(0);
},
Command::CycleAudioMode => {
let mut cfg = vrec::config::VrecConfig::load();
cfg.audio_mode = match cfg.audio_mode.as_str() {
"system" => "mic",
"mic" => "both",
"both" => "muted",
_ => "system",
}.to_string();
let _ = cfg.save();
println!("Audio mode cycled to: {}", cfg.audio_mode);
let _ = cmd_tx.try_send(Command::ReloadConfig);
},
Command::StopDaemon => {
println!("StopDaemon requested: Finalizing active recordings...");
vrec::hyprland_binds::unregister_hyprland_binds(&vrec::config::VrecConfig::load());
let _ = cmd_tx.send(Command::StopRecording);
std::thread::sleep(std::time::Duration::from_millis(350));
break;
},
other => {
let _ = cmd_tx.try_send(other);
}
}
}
}
}
Err(err) => {
eprintln!("Socket error: {}", err);
}
}
}
let _ = recorder_handle.join();
Ok(())
}

View file

@ -37,7 +37,7 @@ impl WaylandCapture {
let select_opts = ashpd::desktop::screencast::SelectSourcesOptions::default()
.set_multiple(false)
.set_cursor_mode(cursor_mode)
.set_sources(SourceType::Monitor | SourceType::Window)
.set_sources(ashpd::enumflags2::BitFlags::from_flag(SourceType::Monitor))
.set_persist_mode(PersistMode::DoNot);
proxy.select_sources(&session, select_opts).await.map_err(|e| {

View file

@ -126,7 +126,7 @@ impl PipeWireStream {
stream.connect(
spa::utils::Direction::Input,
Some(node_id),
pw::stream::StreamFlags::AUTOCONNECT,
pw::stream::StreamFlags::AUTOCONNECT | pw::stream::StreamFlags::MAP_BUFFERS,
&mut params,
).unwrap();

View file

@ -47,6 +47,13 @@ fn default_audio_mode_str() -> String {
"system".to_string()
}
#[cfg(unix)]
pub fn get_socket_path() -> String {
let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
.unwrap_or_else(|_| format!("/run/user/{}", unsafe { libc::getuid() }));
format!("{}/vrec.sock", runtime_dir)
}
pub fn send_command(cmd: Command) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use std::io::Write;
use std::time::Duration;
@ -63,7 +70,7 @@ pub fn send_command(cmd: Command) -> Result<(), Box<dyn std::error::Error + Send
#[cfg(unix)]
{
use std::os::unix::net::UnixStream;
let socket_path = format!("{}/vrec.sock", std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
let socket_path = get_socket_path();
match UnixStream::connect(&socket_path) {
Ok(mut stream) => {
let _ = stream.set_write_timeout(Some(Duration::from_millis(1000)));
@ -115,7 +122,7 @@ pub fn query_status() -> Result<DaemonStatus, Box<dyn std::error::Error + Send +
#[cfg(unix)]
let stream_res = {
use std::os::unix::net::UnixStream;
let socket_path = format!("{}/vrec.sock", std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
let socket_path = get_socket_path();
UnixStream::connect(&socket_path)
};