diff --git a/src/config.rs b/src/config.rs index 3bf56ea..58f952b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,8 +10,6 @@ fn default_fps() -> u32 { 60 } fn default_video_codec() -> String { "h264".to_string() } fn default_audio_device() -> String { "default".to_string() } fn default_audio_mode() -> String { "system".to_string() } -fn default_lang() -> String { "en".to_string() } -fn default_theme() -> String { "dark".to_string() } fn default_save_hotkey() -> String { "Ctrl+Shift+R".to_string() } fn default_menu_hotkey() -> String { "Alt+Z".to_string() } fn default_record_hotkey() -> String { "Ctrl+Shift+F9".to_string() } @@ -24,14 +22,9 @@ fn default_stream_service() -> String { "twitch".to_string() } fn default_stream_url() -> String { "rtmp://live.twitch.tv/app/".to_string() } fn default_screenshot_hotkey() -> String { "Ctrl+Shift+S".to_string() } fn default_screenshot_format() -> String { "png".to_string() } -fn default_capture_source() -> String { "focused".to_string() } fn default_video_quality_mode() -> String { "cbr".to_string() } fn default_color_range() -> String { "limited".to_string() } fn default_audio_codec() -> String { "opus".to_string() } -fn default_replay_storage() -> String { "ram".to_string() } -fn default_container_format() -> String { "mp4".to_string() } -fn default_res_w() -> u32 { 1920 } -fn default_res_h() -> u32 { 1080 } fn default_screenshot_quality() -> u32 { 90 } fn default_screenshot_preset() -> String { "balanced".to_string() } fn default_pause_hotkey() -> String { "Ctrl+Shift+F8".to_string() } @@ -50,8 +43,6 @@ pub struct ScytheConfig { #[serde(default = "default_replay_bitrate")] pub replay_bitrate_kbps: u32, - #[serde(default)] - pub record_enabled: bool, #[serde(default = "default_record_bitrate")] pub record_bitrate_kbps: u32, #[serde(default = "default_fps")] @@ -77,10 +68,6 @@ pub struct ScytheConfig { pub autostart_replay: bool, #[serde(default)] pub autostart_overlay: bool, - #[serde(default = "default_lang")] - pub language: String, - #[serde(default = "default_theme")] - pub ui_color_theme: String, #[serde(default = "default_save_hotkey")] pub save_hotkey: String, #[serde(default = "default_menu_hotkey")] @@ -109,40 +96,20 @@ pub struct ScytheConfig { #[serde(default)] pub screenshot_cursor: bool, - #[serde(default = "default_capture_source")] - pub capture_source: String, #[serde(default = "default_video_quality_mode")] pub video_quality_mode: String, #[serde(default = "default_color_range")] pub color_range: String, #[serde(default = "default_audio_codec")] pub audio_codec: String, - #[serde(default = "default_replay_storage")] - pub replay_storage: String, #[serde(default)] pub record_low_power: bool, #[serde(default = "default_true")] pub show_replay_notifications: bool, - #[serde(default = "default_container_format")] - pub container_format: String, - #[serde(default)] - pub change_resolution: bool, - #[serde(default = "default_res_w")] - pub resolution_w: u32, - #[serde(default = "default_res_h")] - pub resolution_h: u32, - #[serde(default)] - pub save_folder_by_game: bool, - #[serde(default)] - pub name_by_game: bool, #[serde(default = "default_true")] pub restart_replay_on_save: bool, #[serde(default)] - pub show_scroll_lock_led: bool, - #[serde(default)] - pub autostart_on_game: bool, - #[serde(default)] pub autostart_power_only: bool, #[serde(default = "default_screenshot_quality")] @@ -171,7 +138,6 @@ impl Default for ScytheConfig { replay_duration_sec: 60, replay_bitrate_kbps: 18000, - record_enabled: false, record_bitrate_kbps: 20000, fps: 60, video_codec: "h264".to_string(), @@ -185,8 +151,6 @@ impl Default for ScytheConfig { autostart: false, autostart_replay: false, autostart_overlay: false, - language: "en".to_string(), - ui_color_theme: "dark".to_string(), save_hotkey: "Ctrl+Shift+R".to_string(), menu_hotkey: "Alt+Z".to_string(), record_hotkey: "Ctrl+Shift+F9".to_string(), @@ -202,23 +166,13 @@ impl Default for ScytheConfig { screenshot_format: default_screenshot_format(), screenshot_cursor: false, - capture_source: default_capture_source(), video_quality_mode: default_video_quality_mode(), color_range: default_color_range(), audio_codec: default_audio_codec(), - replay_storage: default_replay_storage(), record_low_power: false, show_replay_notifications: true, - container_format: default_container_format(), - change_resolution: false, - resolution_w: default_res_w(), - resolution_h: default_res_h(), - save_folder_by_game: false, - name_by_game: false, restart_replay_on_save: true, - show_scroll_lock_led: false, - autostart_on_game: false, autostart_power_only: false, screenshot_quality: default_screenshot_quality(), @@ -577,21 +531,11 @@ mod tests { assert_eq!(parsed.stream_url, "rtmp://live.twitch.tv/app/"); assert_eq!(parsed.stream_key, ""); - assert_eq!(parsed.container_format, "mp4"); - assert!(!parsed.change_resolution); - assert_eq!(parsed.resolution_w, 1920); - assert_eq!(parsed.resolution_h, 1080); - assert!(!parsed.save_folder_by_game); - assert!(!parsed.name_by_game); assert!(parsed.restart_replay_on_save); - assert!(!parsed.show_scroll_lock_led); - assert!(!parsed.autostart_on_game); assert!(!parsed.autostart_power_only); - assert_eq!(parsed.capture_source, "focused"); assert_eq!(parsed.video_quality_mode, "cbr"); assert_eq!(parsed.color_range, "limited"); assert_eq!(parsed.audio_codec, "opus"); - assert_eq!(parsed.replay_storage, "ram"); assert!(!parsed.record_low_power); assert!(parsed.show_replay_notifications); assert_eq!(parsed.screenshot_hotkey, "Ctrl+Shift+S"); diff --git a/src/daemon.rs b/src/daemon.rs index c05baeb..88e5b20 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -13,6 +13,67 @@ use std::io::{Read, Write}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::Arc; +/// Returns true when the overlay process recorded in the pid file is still alive. +fn overlay_alive() -> bool { + let pid_path = crate::ipc::get_overlay_pid_path(); + let Ok(content) = std::fs::read_to_string(&pid_path) else { + return false; + }; + let Ok(pid) = content.trim().parse::() else { + return false; + }; + if pid <= 0 { + return false; + } + + #[cfg(unix)] + { + unsafe { libc::kill(pid, 0) == 0 } + } + #[cfg(windows)] + { + use windows::Win32::Foundation::{CloseHandle, STILL_ACTIVE}; + use windows::Win32::System::Threading::{ + GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION, + }; + unsafe { + let Ok(handle) = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid as u32) else { + return false; + }; + let mut code = 0u32; + let alive = GetExitCodeProcess(handle, &mut code).is_ok() && code == STILL_ACTIVE.0 as u32; + let _ = CloseHandle(handle); + alive + } + } + #[cfg(not(any(unix, windows)))] + { + false + } +} + +/// Opens the HUD overlay without toggling an already-visible one closed. +fn spawn_overlay_once() { + if overlay_alive() { + return; + } + let exe = std::env::current_exe() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|_| "scythe".to_string()); + + let mut cmd = std::process::Command::new(exe); + cmd.arg("--menu") + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()); + #[cfg(target_os = "windows")] + { + use std::os::windows::process::CommandExt; + cmd.creation_flags(0x08000000); + } + let _ = cmd.spawn(); +} + fn mask_stream_url(url: &str) -> String { if let Some((base, key)) = url.rsplit_once('/') && key.len() > 4 { @@ -278,6 +339,10 @@ async fn run_daemon_async() -> Result<(), Box { + println!("ShowOverlay requested: opening HUD overlay."); + std::thread::spawn(spawn_overlay_once); + }, Command::StopDaemon => { println!("StopDaemon requested: Finalizing active recordings..."); crate::hyprland_binds::unregister_hyprland_binds(&crate::config::ScytheConfig::load()); diff --git a/src/main.rs b/src/main.rs index 357addd..4c898d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -444,6 +444,17 @@ fn run_client(args: Vec) -> Result<(), Box { return handle_toggle_recording(); } + "--show-overlay" | "--open" => { + ensure_daemon_running(); + // Daemon owns overlay lifetime; if it is unreachable, open the HUD directly. + if send_command(Command::ShowOverlay).is_err() { + ensure_hotkeys_running(); + if !check_and_toggle_overlay() { + scythe::overlay_egui::run_egui_overlay(); + } + } + return Ok(()); + } "--toast" => { let title = args.get(2).map(|s| s.as_str()).unwrap_or("SCYTHE"); let subtitle = args.get(3).map(|s| s.as_str()).unwrap_or(""); @@ -540,6 +551,7 @@ fn run_client(args: Vec) -> Result<(), Box/dev/null 2>&1; then + echo "[0/6] Stopping existing scythe daemon..." + target/release/scythe --quit >/dev/null 2>&1 || true + for _ in $(seq 1 50); do + [ -S "$SOCKET_PATH" ] || break + sleep 0.1 + done + if target/release/scythe --status >/dev/null 2>&1; then + echo "ERROR: A scythe daemon is running and did not stop cleanly." + echo " Refusing to unlink its socket. Stop it first: scythe --quit" + exit 1 + fi +fi + rm -f "$SOCKET_PATH" rm -f record_*.mp4 replay_*.mp4