diff --git a/scratch/ui_clean.patch b/scratch/ui_clean.patch new file mode 100644 index 0000000..0caac81 --- /dev/null +++ b/scratch/ui_clean.patch @@ -0,0 +1,21 @@ +--- src/bin/vrec-ui.rs ++++ src/bin/vrec-ui.rs +@@ -1,18 +1,8 @@ +-use vrec::ipc::Command; ++use vrec::ipc::{Command, send_command}; + use std::os::unix::net::UnixStream; + use std::io::Write; + use std::env; + use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}, GlobalHotKeyEvent}; +- +-fn send_command(cmd: Command) -> Result<(), Box> { +- let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string())); +- let mut stream = UnixStream::connect(&socket_path)?; +- let payload = serde_json::to_vec(&cmd)?; +- let len_buf = (payload.len() as u32).to_le_bytes(); +- stream.write_all(&len_buf)?; +- stream.write_all(&payload)?; +- Ok(()) +-} + + fn main() -> Result<(), Box> { diff --git a/src/bin/vrec-daemon.rs b/src/bin/vrec-daemon.rs index f74481f..2ef5b95 100644 --- a/src/bin/vrec-daemon.rs +++ b/src/bin/vrec-daemon.rs @@ -4,7 +4,7 @@ use vrec::ring::Packet; use vrec::muxer::Muxer; use vrec::ipc::Command; use ringbuf::HeapRb; -use ringbuf::traits::{RingBuffer, Consumer}; +use ringbuf::traits::{RingBuffer, Consumer, Observer}; use crossbeam_channel::bounded; use std::thread; use std::env; @@ -31,7 +31,7 @@ async fn main() -> Result<(), Box> { }; let (frame_tx, frame_rx) = bounded::(5); - let (trigger_tx, trigger_rx) = bounded::<()>(1); + let (cmd_tx, cmd_rx) = bounded::(10); let (mux_tx, mux_rx) = bounded::>(1); let _ = frame_tx.send(first_frame); @@ -57,11 +57,15 @@ async fn main() -> Result<(), Box> { let mut encoder = VaapiEncoder::new(width, height).expect("Failed to init encoder"); let codec_ctx_ptr = encoder.codec_ctx() as usize; - let mut ring = HeapRb::::new(3600); + 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() { - println!("Saving replay to output.mp4..."); + 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() { @@ -72,7 +76,7 @@ async fn main() -> Result<(), Box> { if start_idx < drain.len() { let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext; - let mut muxer = unsafe { Muxer::new("output.mp4", codec_ctx).unwrap() }; + let mut muxer = unsafe { Muxer::new(&name, codec_ctx).unwrap() }; for p in drain.into_iter().skip(start_idx) { let _ = muxer.write_packet(&p); } @@ -85,15 +89,51 @@ async fn main() -> Result<(), Box> { }); while let Ok(frame) = frame_rx.recv() { - if let Ok(packets) = encoder.encode_frame(&frame) { - for pkt in packets { - ring.push_overwrite(pkt); + 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 trigger_rx.try_recv().is_ok() { - let drain = ring.iter().cloned().collect::>(); - let _ = mux_tx.try_send(drain); + + 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); + } + } } } }); @@ -113,13 +153,12 @@ async fn main() -> Result<(), Box> { if stream.read_exact(&mut payload).is_ok() && let Ok(cmd) = serde_json::from_slice::(&payload) { match cmd { - Command::SaveReplay => { - let _ = trigger_tx.try_send(()); - }, Command::StopDaemon => { break; }, - _ => {} + other => { + let _ = cmd_tx.try_send(other); + } } } } diff --git a/src/bin/vrec-ui.rs b/src/bin/vrec-ui.rs index 7935e60..168c470 100644 --- a/src/bin/vrec-ui.rs +++ b/src/bin/vrec-ui.rs @@ -1,26 +1,14 @@ -use vrec::ipc::Command; -use std::os::unix::net::UnixStream; -use std::io::Write; +use vrec::ipc::{Command, send_command}; use std::env; use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}, GlobalHotKeyEvent}; -fn send_command(cmd: Command) -> Result<(), Box> { - let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string())); - let mut stream = UnixStream::connect(&socket_path)?; - let payload = serde_json::to_vec(&cmd)?; - let len_buf = (payload.len() as u32).to_le_bytes(); - stream.write_all(&len_buf)?; - stream.write_all(&payload)?; - Ok(()) -} - fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); if args.len() > 1 { match args[1].as_str() { "--save" => { vrec::overlay::show_notification_overlay(); - return send_command(Command::SaveReplay).map_err(|e| e.into()); + return send_command(Command::SaveReplay).map_err(|e| e); } "--menu" => { vrec::overlay::show_menu_overlay(); @@ -63,8 +51,8 @@ fn main() -> Result<(), Box> { let receiver = GlobalHotKeyEvent::receiver(); loop { - if let Ok(event) = receiver.recv() { - if event.state == global_hotkey::HotKeyState::Pressed { + if let Ok(event) = receiver.recv() + && event.state == global_hotkey::HotKeyState::Pressed { if event.id == save_hotkey.id() { println!("Hotkey triggered! Sending SaveReplay command to daemon..."); vrec::overlay::show_notification_overlay(); @@ -74,6 +62,5 @@ fn main() -> Result<(), Box> { vrec::overlay::show_menu_overlay(); } } - } } } diff --git a/src/bin/vrec-ui.rs.orig b/src/bin/vrec-ui.rs.orig index de1928b..7935e60 100644 --- a/src/bin/vrec-ui.rs.orig +++ b/src/bin/vrec-ui.rs.orig @@ -15,9 +15,23 @@ fn send_command(cmd: Command) -> Result<(), Box Result<(), Box> { + let args: Vec = env::args().collect(); + if args.len() > 1 { + match args[1].as_str() { + "--save" => { + vrec::overlay::show_notification_overlay(); + return send_command(Command::SaveReplay).map_err(|e| e.into()); + } + "--menu" => { + vrec::overlay::show_menu_overlay(); + return Ok(()); + } + _ => {} + } + } + println!("Starting vrec UI/Hotkey process..."); - // Check session type let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "x11".to_string()); let desktop = env::var("XDG_CURRENT_DESKTOP").unwrap_or_else(|_| "".to_string()).to_lowercase(); @@ -26,34 +40,38 @@ fn main() -> Result<(), Box> { if session_type.to_lowercase() == "wayland" { if is_gnome || is_kde { - println!("WARNING: wlr-layer-shell unsupported on GNOME/KDE Wayland. Degrading to hotkey-only mode."); - println!("Note: Global hotkeys may also fail to register on some Wayland compositors."); + println!("WARNING: Wayland native layer shell used. Ensure your compositor allows it."); + println!("Note: Global hotkeys may fail to register on Wayland compositors."); } else { - println!("wlroots-based compositor detected. (wlr-layer-shell overlay would be spawned here)."); + println!("wlroots-based compositor detected."); } } else { - println!("X11 session detected. (X11 override-redirect overlay would be spawned here)."); + println!("X11 session detected."); } - let manager = GlobalHotKeyManager::new()?; - let hotkey = HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR); + let manager = GlobalHotKeyManager::new().unwrap(); + let save_hotkey = HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR); + let menu_hotkey = HotKey::new(Some(Modifiers::ALT), Code::KeyZ); - if let Err(e) = manager.register(hotkey) { + if let Err(e) = manager.register(save_hotkey) { eprintln!("Failed to register global hotkey! Wayland compositor might be blocking it: {}", e); eprintln!("Fallback: running without global hotkey. Send commands via socket manually."); } else { - println!("Registered global hotkey Ctrl+Shift+R to trigger replay save."); + manager.register(menu_hotkey).unwrap(); + println!("Registered global hotkeys:\n - Ctrl+Shift+R (Save Replay)\n - Alt+Z (Open GUI Menu)"); } let receiver = GlobalHotKeyEvent::receiver(); loop { if let Ok(event) = receiver.recv() { - if event.id == hotkey.id() { - if event.state == global_hotkey::HotKeyState::Pressed { + if event.state == global_hotkey::HotKeyState::Pressed { + if event.id == save_hotkey.id() { println!("Hotkey triggered! Sending SaveReplay command to daemon..."); - if let Err(e) = send_command(Command::SaveReplay) { - eprintln!("Failed to communicate with daemon: {}", e); - } + vrec::overlay::show_notification_overlay(); + let _ = send_command(Command::SaveReplay); + } else if event.id == menu_hotkey.id() { + println!("Alt+Z triggered! Opening GUI Menu Overlay..."); + vrec::overlay::show_menu_overlay(); } } } diff --git a/src/config.rs b/src/config.rs index 736e768..bcf0b56 100644 --- a/src/config.rs +++ b/src/config.rs @@ -47,13 +47,11 @@ impl VrecConfig { pub fn load() -> Self { let path = Self::config_path(); - if path.exists() { - if let Ok(data) = fs::read_to_string(path) { - if let Ok(config) = serde_json::from_str(&data) { + if path.exists() + && let Ok(data) = fs::read_to_string(path) + && let Ok(config) = serde_json::from_str(&data) { return config; } - } - } Self::default() } @@ -72,12 +70,11 @@ impl VrecConfig { use std::os::unix::net::UnixStream; use std::io::Write; let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string())); - if let Ok(mut stream) = UnixStream::connect(&socket_path) { - if let Ok(payload) = serde_json::to_vec(&crate::ipc::Command::ReloadConfig) { + if let Ok(mut stream) = UnixStream::connect(&socket_path) + && let Ok(payload) = serde_json::to_vec(&crate::ipc::Command::ReloadConfig) { let len_buf = (payload.len() as u32).to_le_bytes(); let _ = stream.write_all(&len_buf); let _ = stream.write_all(&payload); } - } } } diff --git a/src/ipc.rs b/src/ipc.rs index 779f668..f57415f 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -9,3 +9,16 @@ pub enum Command { StopDaemon, ShowOverlay, } + +pub fn send_command(cmd: Command) -> Result<(), Box> { + use std::env; + use std::os::unix::net::UnixStream; + use std::io::Write; + let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string())); + let mut stream = UnixStream::connect(&socket_path)?; + let payload = serde_json::to_vec(&cmd)?; + let len_buf = (payload.len() as u32).to_le_bytes(); + stream.write_all(&len_buf)?; + stream.write_all(&payload)?; + Ok(()) +} diff --git a/src/overlay.rs b/src/overlay.rs index 7d4f5c1..f0c6158 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -142,7 +142,7 @@ pub fn show_menu_overlay() { }); // Toggle buttons logic (just UI for now, backend IPC will be needed to actually toggle daemon state) - let btn_cfg_1 = config.clone(); + let _btn_cfg_1 = config.clone(); let replay_btn_clone = replay_btn.clone(); replay_btn.connect_clicked(move |_| { let mut cfg = VrecConfig::load(); @@ -155,16 +155,19 @@ pub fn show_menu_overlay() { } }); - let btn_cfg_2 = config.clone(); + let _btn_cfg_2 = config.clone(); let record_btn_clone = record_btn.clone(); record_btn.connect_clicked(move |_| { let mut cfg = VrecConfig::load(); cfg.record_enabled = !cfg.record_enabled; - cfg.save().unwrap(); VrecConfig::notify_daemon_reload(); + cfg.save().unwrap(); + VrecConfig::notify_daemon_reload(); if cfg.record_enabled { record_btn_clone.set_label("Recording\n🔴 ON"); + let _ = crate::ipc::send_command(crate::ipc::Command::StartRecording); } else { record_btn_clone.set_label("Recording\n⚪ OFF"); + let _ = crate::ipc::send_command(crate::ipc::Command::StopRecording); } });