From 587b164457d5c2d815b69f7842c7c28ea2d7b72e Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Wed, 2 Sep 2026 07:55:19 +0530 Subject: [PATCH] chore: Add mock screen capture driver for headless testing --- record_1788315688.mp4 | Bin 0 -> 39879 bytes record_1788315823.mp4 | Bin 0 -> 48 bytes record_1788315881.mp4 | Bin 0 -> 104667 bytes replay_1788315659.mp4 | Bin 0 -> 226176 bytes scratch/muxer_fix.patch | 60 ++++++++++++ send_ipc.py | 14 +++ src/bin/vrec-daemon.rs | 37 ++++++-- src/bin/vrec-daemon.rs.orig | 176 ++++++++++++++++++++++++++++++++++++ src/capture/mock.rs | 39 ++++++++ src/capture/mod.rs | 1 + test_ipc.py | 11 +++ test_record.py | 7 ++ test_reload.py | 7 ++ test_stop.py | 7 ++ 14 files changed, 350 insertions(+), 9 deletions(-) create mode 100644 record_1788315688.mp4 create mode 100644 record_1788315823.mp4 create mode 100644 record_1788315881.mp4 create mode 100644 replay_1788315659.mp4 create mode 100644 scratch/muxer_fix.patch create mode 100644 send_ipc.py create mode 100644 src/bin/vrec-daemon.rs.orig create mode 100644 src/capture/mock.rs create mode 100644 test_ipc.py create mode 100644 test_record.py create mode 100644 test_reload.py create mode 100644 test_stop.py diff --git a/record_1788315688.mp4 b/record_1788315688.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..53f4651730e51713312e97827ce20de455ee086e GIT binary patch literal 39879 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 I0RMph0I)`=^8f$< literal 0 HcmV?d00001 diff --git a/record_1788315823.mp4 b/record_1788315823.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..17e5e5fbd204f4f1c8bf240b166ab0a318db4744 GIT binary patch literal 48 xcmZQzU{FXasVvAW&d+6FU}6B#Kx~v)mTZ_?U}DI?z`&7Kl$r{p8FEt+O8`9Q4A1}o literal 0 HcmV?d00001 diff --git a/record_1788315881.mp4 b/record_1788315881.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..32fa41a5cc9ad66e4be23f0eba34ee07c9ecbdf5 GIT binary patch literal 104667 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 literal 0 HcmV?d00001 diff --git a/replay_1788315659.mp4 b/replay_1788315659.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..1f241f388d145427a9de4f546cc7f836f8148b66 GIT binary patch literal 226176 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 literal 0 HcmV?d00001 diff --git a/scratch/muxer_fix.patch b/scratch/muxer_fix.patch new file mode 100644 index 0000000..83d06bc --- /dev/null +++ b/scratch/muxer_fix.patch @@ -0,0 +1,60 @@ +--- src/bin/vrec-daemon.rs ++++ src/bin/vrec-daemon.rs +@@ -62,6 +62,8 @@ + let mut config = vrec::config::VrecConfig::load(); + let mut ring = HeapRb::::new((config.replay_duration_sec * 60).max(60) as usize); + let mut normal_muxer: Option = None; ++ let mut normal_recording = false; ++ let mut normal_waiting_keyframe = false; + + thread::spawn(move || { + while let Ok(drain) = mux_rx.recv() { +@@ -108,16 +110,14 @@ + } + }, + Command::StartRecording => { +- if config.record_enabled && normal_muxer.is_none() { +- let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext; +- let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); +- let name = format!("record_{}.mp4", ts); +- normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() }; +- println!("Started normal recording to {}", name); ++ if config.record_enabled && !normal_recording { ++ normal_recording = true; ++ normal_waiting_keyframe = true; ++ println!("StartRecording requested, waiting for keyframe..."); + } + }, + Command::StopRecording => { +- if let Some(mut m) = normal_muxer.take() { ++ if let Some(mut m) = normal_muxer.take() { + let _ = m.finalize(); + println!("Stopped normal recording."); + } ++ normal_recording = false; + }, + _ => {} + } +@@ -129,7 +129,19 @@ + ring.push_overwrite(pkt.clone()); + } +- if let Some(muxer) = normal_muxer.as_mut() { +- let _ = muxer.write_packet(&pkt); ++ if normal_recording { ++ if normal_waiting_keyframe { ++ if pkt.is_keyframe() { ++ normal_waiting_keyframe = false; ++ let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext; ++ let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); ++ let name = format!("record_{}.mp4", ts); ++ normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() }; ++ println!("Started normal recording to {}", name); ++ } ++ } ++ if !normal_waiting_keyframe { ++ if let Some(muxer) = normal_muxer.as_mut() { ++ let _ = muxer.write_packet(&pkt); ++ } ++ } + } + } diff --git a/send_ipc.py b/send_ipc.py new file mode 100644 index 0000000..c0b6f4c --- /dev/null +++ b/send_ipc.py @@ -0,0 +1,14 @@ +import socket +import json +import struct +import os + +def send(cmd): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(os.environ.get("XDG_RUNTIME_DIR", "/tmp") + "/vrec.sock") + payload = json.dumps(cmd).encode('utf-8') + sock.sendall(struct.pack(' Result<(), Box> { let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "x11".to_string()); println!("Detected session type: {}", session_type); - let mut source: Box = if session_type.to_lowercase() == "wayland" { + let mut source: Box = if std::env::args().any(|a| a == "--mock") { + println!("Initializing MOCK capture..."); + Box::new(vrec::capture::mock::MockCapture::new()) + } else if session_type.to_lowercase() == "wayland" { println!("Initializing Wayland capture..."); Box::new(vrec::capture::wayland::WaylandCapture::new().await?) } else { @@ -60,6 +63,8 @@ async fn main() -> Result<(), Box> { let mut config = vrec::config::VrecConfig::load(); let mut ring = HeapRb::::new((config.replay_duration_sec * 60).max(60) as usize); let mut normal_muxer: Option = None; + let mut normal_recording = false; + let mut normal_waiting_keyframe = false; thread::spawn(move || { while let Ok(drain) = mux_rx.recv() { @@ -107,12 +112,10 @@ async fn main() -> Result<(), Box> { } }, Command::StartRecording => { - if config.record_enabled && normal_muxer.is_none() { - let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext; - let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); - let name = format!("record_{}.mp4", ts); - normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() }; - println!("Started normal recording to {}", name); + if config.record_enabled && !normal_recording { + normal_recording = true; + normal_waiting_keyframe = true; + println!("StartRecording requested, waiting for keyframe..."); } }, Command::StopRecording => { @@ -120,6 +123,7 @@ async fn main() -> Result<(), Box> { let _ = m.finalize(); println!("Stopped normal recording."); } + normal_recording = false; }, _ => {} } @@ -130,8 +134,23 @@ async fn main() -> Result<(), Box> { if config.replay_enabled { ring.push_overwrite(pkt.clone()); } - if let Some(muxer) = normal_muxer.as_mut() { - let _ = muxer.write_packet(&pkt); + if normal_recording { + if normal_waiting_keyframe { + if pkt.is_keyframe() { + normal_waiting_keyframe = false; + let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext; + let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); + let name = format!("record_{}.mp4", ts); + normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() }; + println!("Started normal recording to {}", name); + } + } + + if !normal_waiting_keyframe { + if let Some(muxer) = normal_muxer.as_mut() { + let _ = muxer.write_packet(&pkt); + } + } } } } diff --git a/src/bin/vrec-daemon.rs.orig b/src/bin/vrec-daemon.rs.orig new file mode 100644 index 0000000..2ad3359 --- /dev/null +++ b/src/bin/vrec-daemon.rs.orig @@ -0,0 +1,176 @@ +use vrec::capture::{Frame, FrameSource}; +use vrec::encoder::VaapiEncoder; +use vrec::ring::Packet; +use vrec::muxer::Muxer; +use vrec::ipc::Command; +use ringbuf::HeapRb; +use ringbuf::traits::{RingBuffer, Consumer, Observer}; +use crossbeam_channel::bounded; +use std::thread; +use std::env; +use std::os::unix::net::UnixListener; +use std::io::Read; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "x11".to_string()); + println!("Detected session type: {}", session_type); + + let mut source: Box = if std::env::args().any(|a| a == "--mock") { + println!("Initializing MOCK capture..."); + Box::new(vrec::capture::mock::MockCapture::new()) + } else if session_type.to_lowercase() == "wayland" { + println!("Initializing Wayland capture..."); + Box::new(vrec::capture::wayland::WaylandCapture::new().await?) + } else { + println!("Initializing X11 capture..."); + Box::new(vrec::capture::x11::X11Capture::new()?) + }; + + let first_frame = source.next_frame()?; + let (width, height) = match &first_frame { + Frame::Raw { width, height, .. } => (*width, *height), + Frame::DmaBuf { width, height, .. } => (*width, *height), + }; + + let (frame_tx, frame_rx) = bounded::(5); + let (cmd_tx, cmd_rx) = bounded::(10); + let (mux_tx, mux_rx) = bounded::>(1); + + let _ = frame_tx.send(first_frame); + + let capture_tx = frame_tx.clone(); + thread::spawn(move || { + loop { + match source.next_frame() { + Ok(frame) => { + if capture_tx.send(frame).is_err() { + break; + } + } + Err(e) => { + eprintln!("Capture error: {}", e); + break; + } + } + } + }); + + thread::spawn(move || { + let mut encoder = VaapiEncoder::new(width, height).expect("Failed to init encoder"); + let codec_ctx_ptr = encoder.codec_ctx() as usize; + + let mut config = vrec::config::VrecConfig::load(); + let mut ring = HeapRb::::new((config.replay_duration_sec * 60).max(60) as usize); + let mut normal_muxer: Option = None; + + thread::spawn(move || { + while let Ok(drain) = mux_rx.recv() { + let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); + let name = format!("replay_{}.mp4", ts); + println!("Saving replay to {}...", name); + let mut start_idx = 0; + for (i, p) in drain.iter().enumerate() { + if p.is_keyframe() { + start_idx = i; + break; + } + } + + if start_idx < drain.len() { + let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext; + let mut muxer = unsafe { Muxer::new(&name, codec_ctx).unwrap() }; + for p in drain.into_iter().skip(start_idx) { + let _ = muxer.write_packet(&p); + } + let _ = muxer.finalize(); + println!("Replay saved!"); + } else { + println!("No keyframe found in buffer, skipping save."); + } + } + }); + + while let Ok(frame) = frame_rx.recv() { + while let Ok(cmd) = cmd_rx.try_recv() { + match cmd { + Command::ReloadConfig => { + config = vrec::config::VrecConfig::load(); + println!("Daemon config reloaded!"); + let new_capacity = (config.replay_duration_sec * 60).max(60) as usize; + if ring.capacity().get() != new_capacity { + ring = HeapRb::::new(new_capacity); + println!("Replay buffer resized to {} frames.", new_capacity); + } + }, + Command::SaveReplay => { + if config.replay_enabled { + let drain = ring.iter().cloned().collect::>(); + let _ = mux_tx.try_send(drain); + } + }, + Command::StartRecording => { + if config.record_enabled && normal_muxer.is_none() { + let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext; + let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); + let name = format!("record_{}.mp4", ts); + normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() }; + println!("Started normal recording to {}", name); + } + }, + Command::StopRecording => { + if let Some(mut m) = normal_muxer.take() { + let _ = m.finalize(); + println!("Stopped normal recording."); + } + }, + _ => {} + } + } + + if let Ok(packets) = encoder.encode_frame(&frame) { + for pkt in packets { + if config.replay_enabled { + ring.push_overwrite(pkt.clone()); + } + if let Some(muxer) = normal_muxer.as_mut() { + let _ = muxer.write_packet(&pkt); + } + } + } + } + }); + + 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 listener = UnixListener::bind(&socket_path)?; + println!("Daemon listening on IPC socket: {}", socket_path); + + for stream in listener.incoming() { + match stream { + Ok(mut stream) => { + 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::(&payload) { + match cmd { + Command::StopDaemon => { + break; + }, + other => { + let _ = cmd_tx.try_send(other); + } + } + } + } + } + Err(err) => { + eprintln!("Socket error: {}", err); + } + } + } + + Ok(()) +} diff --git a/src/capture/mock.rs b/src/capture/mock.rs new file mode 100644 index 0000000..f7a0cdb --- /dev/null +++ b/src/capture/mock.rs @@ -0,0 +1,39 @@ +use super::{Frame, FrameSource}; +use std::time::Duration; + +pub struct MockCapture { + width: u32, + height: u32, + frame_count: u64, +} + +impl MockCapture { + pub fn new() -> Self { + Self { width: 1920, height: 1080, frame_count: 0 } + } +} + +impl FrameSource for MockCapture { + fn next_frame(&mut self) -> Result> { + std::thread::sleep(Duration::from_millis(16)); // ~60fps + let mut data = vec![0u8; (self.width * self.height * 4) as usize]; + + // Fill with a changing color + let color = (self.frame_count % 255) as u8; + for chunk in data.chunks_exact_mut(4) { + chunk[0] = color; // B + chunk[1] = color; // G + chunk[2] = 255; // R + chunk[3] = 255; // A + } + + self.frame_count += 1; + + Ok(Frame::Raw { + width: self.width, + height: self.height, + stride: self.width * 4, + data, + }) + } +} diff --git a/src/capture/mod.rs b/src/capture/mod.rs index 0edaec7..f4bb4ee 100644 --- a/src/capture/mod.rs +++ b/src/capture/mod.rs @@ -34,3 +34,4 @@ pub enum Frame { pub trait FrameSource: Send { fn next_frame(&mut self) -> Result>; } +pub mod mock; diff --git a/test_ipc.py b/test_ipc.py new file mode 100644 index 0000000..2af96a1 --- /dev/null +++ b/test_ipc.py @@ -0,0 +1,11 @@ +import socket +import json +import struct +import os + +sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +sock.connect(os.environ.get("XDG_RUNTIME_DIR", "/tmp") + "/vrec.sock") +payload = b'"SaveReplay"' +sock.sendall(struct.pack('